From 689ea0db6e92cf566e34391064d208c31cd748d0 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 28 Feb 2020 18:45:25 -0800 Subject: [PATCH] init commit --- .gitignore | 9 +- .pylintrc | 433 ++++++++++++++++++ .readthedocs.yml | 3 + CODE_OF_CONDUCT.md | 127 ++++++ README.rst | 102 +++++ circuitpython_cirque_pinnacle.py | 495 +++++++++++++++++++++ docs/_static/darkness.css | 274 ++++++++++++ docs/_static/examples.rst | 8 + docs/_static/favicon.ico | Bin 0 -> 11062 bytes docs/api.rst | 23 + docs/conf.py | 169 +++++++ docs/index.rst | 40 ++ examples/cirque_pinnacle_spi_simpletest.py | 22 + requirements.txt | 2 + setup.py | 66 +++ 15 files changed, 1768 insertions(+), 5 deletions(-) create mode 100644 .pylintrc create mode 100644 .readthedocs.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 README.rst create mode 100644 circuitpython_cirque_pinnacle.py create mode 100644 docs/_static/darkness.css create mode 100644 docs/_static/examples.rst create mode 100644 docs/_static/favicon.ico create mode 100644 docs/api.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 examples/cirque_pinnacle_spi_simpletest.py create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index b6e4761..189ee05 100644 --- a/.gitignore +++ b/.gitignore @@ -91,12 +91,8 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff +# celery beat schedule file celerybeat-schedule -celerybeat.pid # SageMath parsed files *.sage.py @@ -127,3 +123,6 @@ dmypy.json # Pyre type checker .pyre/ + +# VS Code folder +.vscode diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..a95e6a0 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,433 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +# jobs=1 +jobs=2 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call +disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +# notes=FIXME,XXX,TODO +notes=FIXME,XXX + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=board + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +# expected-line-ending-format= +expected-line-ending-format=LF + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming hint for argument names +argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct argument names +argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for attribute names +attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct attribute names +attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class names +# class-name-hint=[A-Z_][a-zA-Z0-9]+$ +class-name-hint=[A-Z_][a-zA-Z0-9_]+$ + +# Regular expression matching correct class names +# class-rgx=[A-Z_][a-zA-Z0-9]+$ +class-rgx=[A-Z_][a-zA-Z0-9_]+$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming hint for function names +function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct function names +function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Good variable names which should always be accepted, separated by a comma +# good-names=i,j,k,ex,Run,_ +good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for method names +method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct method names +method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming hint for variable names +variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct variable names +variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +# max-attributes=7 +max-attributes=11 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=1 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..e311bcb --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,3 @@ +python: + version: 3 +requirements_file: requirements.txt diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3c7d89a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,127 @@ +# Adafruit Community Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and leaders pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level or type of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +We are committed to providing a friendly, safe and welcoming environment for +all. + +Examples of behavior that contributes to creating a positive environment +include: + +* Be kind and courteous to others +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Collaborating with other community members +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and sexual attention or advances +* The use of inappropriate images, including in a community member's avatar +* The use of inappropriate language, including in a community member's nickname +* Any spamming, flaming, baiting or other attention-stealing behavior +* Excessive or unwelcome helping; answering outside the scope of the question + asked +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate + +The goal of the standards and moderation guidelines outlined here is to build +and maintain a respectful community. We ask that you don’t just aim to be +"technically unimpeachable", but rather try to be your best self. + +We value many things beyond technical expertise, including collaboration and +supporting others within our community. Providing a positive experience for +other community members can have a much more significant impact than simply +providing the correct answer. + +## Our Responsibilities + +Project leaders are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project leaders have the right and responsibility to remove, edit, or +reject messages, comments, commits, code, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any community member for other behaviors that they deem +inappropriate, threatening, offensive, or harmful. + +## Moderation + +Instances of behaviors that violate the Adafruit Community Code of Conduct +may be reported by any member of the community. Community members are +encouraged to report these situations, including situations they witness +involving other community members. + +You may report in the following ways: + +In any situation, you may send an email to . + +On the Adafruit Discord, you may send an open message from any channel +to all Community Helpers by tagging @community moderators. You may also send an +open message from any channel, or a direct message to @kattni#1507, +@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or +@Andon#8175. + +Email and direct message reports will be kept confidential. + +In situations on Discord where the issue is particularly egregious, possibly +illegal, requires immediate action, or violates the Discord terms of service, +you should also report the message directly to Discord. + +These are the steps for upholding our community’s standards of conduct. + +1. Any member of the community may report any situation that violates the +Adafruit Community Code of Conduct. All reports will be reviewed and +investigated. +2. If the behavior is an egregious violation, the community member who +committed the violation may be banned immediately, without warning. +3. Otherwise, moderators will first respond to such behavior with a warning. +4. Moderators follow a soft "three strikes" policy - the community member may +be given another chance, if they are receptive to the warning and change their +behavior. +5. If the community member is unreceptive or unreasonable when warned by a +moderator, or the warning goes unheeded, they may be banned for a first or +second offense. Repeated offenses will result in the community member being +banned. + +## Scope + +This Code of Conduct and the enforcement policies listed above apply to all +Adafruit Community venues. This includes but is not limited to any community +spaces (both public and private), the entire Adafruit Discord server, and +Adafruit GitHub repositories. Examples of Adafruit Community spaces include +but are not limited to meet-ups, audio chats on the Adafruit Discord, or +interaction at a conference. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. As a community +member, you are representing our community, and are expected to behave +accordingly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +, +and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). + +For other projects adopting the Adafruit Community Code of +Conduct, please contact the maintainers of those projects for enforcement. +If you wish to use this code of conduct for your own project, consider +explicitly mentioning your moderation policy or making a copy with your +own moderation policy so as to avoid confusion. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..830ece4 --- /dev/null +++ b/README.rst @@ -0,0 +1,102 @@ +Introduction +============ + +.. .. image:: https://readthedocs.org/projects/circuitpython-cirque-pinnacle/badge/?version=latest +.. :target: https://circuitpython-cirque-pinnacle.readthedocs.io/en/latest/?badge=latest +.. :alt: Documentation Status + +.. .. image:: https://travis-ci.org/2bndy5/CircuitPython_Cirque_Pinnacle.svg?branch=master +.. :target: https://travis-ci.org/2bndy5/CircuitPython_Cirque_Pinnacle +.. :alt: Build Status + +A CircuitPython driver library that implements the Adafruit_BusDevice library +for interfacing with the Cirque Pinnacle (1CA027) touch controller used in Cirque Circle Trackpads. + +Pinout +======== + +.. image:: https://www.mouser.com/images/marketingid/2018/img/118816186_Cirque_GlidePoint-Circle-Trackpads.png + :target: https://www.mouser.com/new/cirque/glidepoint-circle-trackpads/ + +The above picture is a example of the Cirque GlidePoint circle trackpad. This picture +is chosen as the test pads (larger copper circular pads) are clearly labeled. The test pads +are extended to the 12-pin J1 ribbon cable connector (the white block near the bottom) for which +you should refer to the `datasheet `_ for more detail. The Cirque +circle trackpad models use the following labeling scheme: + +``TMyyyxxx-202i-30o`` +--------------------- + - ``yyy`` stands for the horizontal width of the trackpad + - ``xxx`` stands for the vertical width of the trackpad + - ``i`` stands for the hardwired interface protocol (3 = I2C, 4 = SPI). Notice if there is a + resistor populated at the R1 (470K ohm) junction (just above the Pinnacle ASIC touch + controller), it is configured for SPI, otherwise it is configured for I2C. + - ``o`` stands for the overlay type (0 = none, 1 = adhesive, 2 = flat, 3 = curved) + +.. tip:: The SPI protocol is the preferred method for interfacing with more than 1 Cirque circle + trackpad from the same MCU (microcontroller). The Cirque Pinnacle does not seem to allow + changing the I2C slave device address; this means only 1 Cirque circle trackpad can be accessed over + the I2C bus at a time (unless I missed something in the many datasheets provided for this device). + +.. note:: Cirque's circle trackpads ship with the non-AG (Advanced Gestures) variant of the Pinnacle + touch controller ASIC (Application Specific Integrated Circuit). Thus, library focuses on the the non-AG + variant's functionality via testing, but it does provide access to the AG vaiant's features (though untested). + +Dependencies +============= +This driver depends on: + +* `Adafruit CircuitPython `_ +* `Bus Device `_ + +Please ensure all dependencies are available on the CircuitPython filesystem. +This is easily achieved by downloading +`the Adafruit library and driver bundle `_. + +How to Install +===================== +This library isn't getting deployed to pypi.org yet. Use the following commands to install this library: + +.. code-block:: shell + + git clone https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle.git + cd CircuitPython_Cirque_Pinnacle + python3 setup.py install + +To install globally, prefix the last command with ``sudo``. + +Usage Example +============= + +Ensure you've connected the TM0dd0dd-202i-30x correctly by running the simple test located in the `examples folder of this library `_. See also the examples section. + +Contributing +============ + +Contributions are welcome! Please read our `Code of Conduct +`_ +before contributing to help this project stay welcoming. + +Sphinx documentation +----------------------- + +Sphinx is used to build the documentation based on rST files and comments in the code. First, +install dependencies (feel free to reuse the virtual environment from above): + +.. code-block:: shell + + python3 -m venv .env + source .env/bin/activate + pip install Sphinx sphinx-rtd-theme + +Now, once you have the virtual environment activated: + +.. code-block:: shell + + cd docs + sphinx-build -E -W -b html . _build/html + +This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to +view them. It will also (due to -W) error out on any warning like Travis will. This is a good way to +locally verify it will pass. diff --git a/circuitpython_cirque_pinnacle.py b/circuitpython_cirque_pinnacle.py new file mode 100644 index 0000000..08ba25a --- /dev/null +++ b/circuitpython_cirque_pinnacle.py @@ -0,0 +1,495 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 Brendan Doherty +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +PinnacleTouch API +================= + +A driver class for the Pinnacle touch controller ASIC on the Cirque capacitve touch based circular +trackpads. +""" +import time +from adafruit_bus_device.spi_device import SPIDevice +from adafruit_bus_device.i2c_device import I2CDevice + +# internal registers +# pylint: disable=bad-whitespace +PINNACLE_FIRMWARE_ID = 0x00 # Firmware ASIC ID +PINNACLE_FIRMWARE_VER = 0x01 # Firmware revision number +PINNACLE_STATUS = 0x02 # Contains status flags about the state of Pinnacle +PINNACLE_SYS_CONFIG = 0x03 # Contains system operation and configuration bits +PINNACLE_FEED_CONFIG1 = 0x04 # Contains feed operation and configuration bits +PINNACLE_FEED_CONFIG2 = 0x05 # Contains feed operation and configuration bits +PINNACLE_FEED_CONFIG3 = 0x06 # Contains feed operation and configuration bits +PINNACLE_CALIBRATE_CONFIG = 0x07 # Contains calibration configuration bits +PINNACLE_PS_2_AUX_CTRL = 0x08 # Contains Data register for PS/2 Aux Control +PINNACLE_SAMPLE_RATE = 0x09 # Number of samples generated per second +PINNACLE_Z_IDLE = 0x0A # Number of Z=0 packets sent when Z goes from >0 to 0 +PINNACLE_Z_SCALAR = 0x0B # Contains the pen Z_On threshold +PINNACLE_SLEEP_INTERVAL = 0x0C # No description +PINNACLE_SLEEP_TIMER = 0x0D # No description +PINNACLE_EMI_THRESHOLD = 0x0E # Threshold to adjust EMI settings +PINNACLE_PACKET_BYTE_0 = 0x12 # trackpad Data +PINNACLE_PACKET_BYTE_1 = 0x13 # trackpad Data +PINNACLE_PACKET_BYTE_2 = 0x14 # trackpad Data +PINNACLE_PACKET_BYTE_3 = 0x15 # trackpad Data +PINNACLE_PACKET_BYTE_4 = 0x16 # trackpad Data +PINNACLE_PACKET_BYTE_5 = 0x17 # trackpad Data +PINNACLE_PORTA_GPIO_CTRL = 0x18 # Control of Port A GPIOs +PINNACLE_PORTA_GPIO_DATA = 0x19 # Data of Port A GPIOs +PINNACLE_PORTB_GPIO_CTRL_DATA = 0x1A # Control and Data of PortB GPIOs +PINNACLE_ERA_VALUE = 0x1B # Value for extended register access +PINNACLE_ERA_ADDR_HIGH = 0x1C # High byte of 16 bit extended register address +PINNACLE_ERA_ADDR_LOW = 0x1D # Low byte of 16 bit extended register address +PINNACLE_ERA_CTRL = 0x1E # Control of extended register access +PINNACLE_PRODUCT_ID = 0x1F # Product ID +# pylint: enable=bad-whitespace +# pylint: disable=too-many-arguments + +def twos_comp(data, bits): + """return integer representation of ``data`` in 2's compliment form using a + specified number of ``bits`` """ + mask = 1 << (bits - 1) + if data & mask: + return -1 * mask + (data & ~mask) + return data + +class PinnacleTouch: + """ + The abstract base class for driving the Pinnacle touch controller. + + :param ~microcontroller.Pin dr_pin: The input pin connected to the touch controller's "Data + Ready" pin. + :param int z_idle_count: The number of empty packets to report (every 10 milliseconds) when + z-axis is idle (no touch detected). Default is 5. + :param bool relative: Specifies if the data reported is relative (`True` -- change since + last event) or absolute (`False` -- exact position on sensor) modes. Default is `True`. + + .. note:: Relative data mode is also referred to as "Mouse Mode" in the datasheet. + :param bool invert_x: Specifies if the x-axis data is to be inverted before reporting it. + Default is `False`. + :param bool invert_y: Specifies if the y-axis data is to be inverted before reporting it. + Default is `False`. + :param bool feed_enable: Specifies if data reporting is enabled (`True`) or not (`False`). + Default is `True`. + """ + def __init__(self, dr_pin, relative=True, invert_x=False, invert_y=False, + feed_enable=True, allow_sleep=False, z_idle_count=30): + self.dr_pin = dr_pin + self.dr_pin.switch_to_input() + # init internal attribute + self._sample_rate, self._z_idle_count = (0, 0) + self._feed_config1, self._feed_config2 = (0, 0) + self._sys_config = 0 + # read defaults and set user defined values + self._read_init_values() # reads default values from Pinnacle registers + self._feed_config1 = (self._feed_config1 & 0xFE) | invert_y << 7 | invert_x << 6 | ( + not relative) << 1 | feed_enable + self._sys_config = (self._sys_config & 0xFB) | allow_sleep << 2 + self._z_idle_count = z_idle_count + with self: + self.clear_flags() # clear any "Command Complete" and "Data Ready" flags + + def __enter__(self): + self._rap_write_bytes(PINNACLE_FEED_CONFIG1, [self._feed_config1, self._feed_config2]) + self._rap_write_bytes(PINNACLE_SAMPLE_RATE, [self._sample_rate, self._z_idle_count]) + self._rap_write(PINNACLE_SYS_CONFIG, self._sys_config) + + def __exit__(self, *exc): + return False + + def _read_init_values(self): + # this is called on init() and reset() + self._feed_config1, self._feed_config2 = self._rap_read_bytes(PINNACLE_FEED_CONFIG1, 2) + self._sample_rate, self._z_idle_count = self._rap_read_bytes(PINNACLE_SAMPLE_RATE, 2) + self._sys_config = self._rap_read(PINNACLE_SYS_CONFIG) + + def set_data_mode(self, relative=True, invert_x=False, invert_y=False): + """ Set the mode in which the data is reported. (write only) + + :param bool relative: Specifies if the data reported is relative (`True` -- change since + last event) or absolute (`False` -- exact position on sensor). Default is `True`. + + .. note:: Relative mode is also referred to as "Mouse Mode" in the datasheet. + :param bool invert_x: Specifies if the x-axis data is to be inverted before reporting it. + Default is `False`. + :param bool invert_y: Specifies if the y-axis data is to be inverted before reporting it. + Default is `False`. + """ + self._feed_config1 = (self._feed_config1 & 0xFE) | invert_y << 7 | invert_x << 6 | ( + not relative) << 1 + self._rap_write(PINNACLE_FEED_CONFIG1, self._feed_config1) + + def relative_mode_config(self, rotate90=False, glide_extend=True, scroll_disable=False, + secondary_tap=True, disable_taps=True, intellimouse=False): + """set the configuration register for features specific to relative mode data reporting. + + :param bool rotate90: Specifies if the axis data is altered for 90 degree rotation before + reporting it. Default is `False`. + :param bool glide_extend: A patended feature that allows the user to glide their finger off + the edge of the sensor and continue gesture with the touch event. Default is `True`. + This feature is only implemented on the AG variant of the Pinnacle touch controller + ASIC. + :param bool scroll_disable: Specifies if the scrolling data is enabled (`True`) or + disabled (`False`). Default is `False`. This feature is only implemented on the AG + variant of the Pinnacle touch controller ASIC. + :param bool secondary_tap: Specifies if tapping in the top-left corner (depending on + orientation) triggers the secondary button data. Defaults to `True`. This feature is + only implemented on the AG variant of the Pinnacle touch controller ASIC. + :param bool disable_taps: Specifies if all taps should be reported (`True`) or not + (`False`). Default is `True`. This affects secondary tap option as well. Only the + primary button is emulated with a tap on the non-AG variant of the Pinnacle touch + controller ASIC. + :param bool intellimouse: Specifies if the data reported includes a byte about scroll data. + Default is `False`. Because this flag is specific to scroll data, this feature is only + implemented on the AG variant of the Pinnacle touch controller ASIC. + """ + self._feed_config2 = rotate90 << 7 | (not glide_extend) << 4 | scroll_disable << 3 | ( + not secondary_tap) << 2 | (not disable_taps) << 1 | intellimouse + self._rap_write(PINNACLE_FEED_CONFIG2, self._feed_config2) + + @property + def feed_enable(self): + """This `bool` attribute controls if the touch data is reported (`True`) or not + (`False`).""" + return bool(self._feed_config1 & 1) + + @feed_enable.setter + def feed_enable(self, is_on): + if self.feed_enable != is_on: # save ourselves the unnecessary transaction + self._feed_config1 = self._rap_read(PINNACLE_FEED_CONFIG1) + self._feed_config1 = (self._feed_config1 & 0xFE) | is_on + self._rap_write(PINNACLE_FEED_CONFIG1, self._feed_config1) + + @property + def sample_rate(self): + """This attribute controls how many samples (of data) per second are reported. Valid values + are ``100``, ``80``, ``60``, ``40``, ``20``, ``10``. Any other input values automatically + set the sample rate to 100 sps (samples per second). + """ + return self._sample_rate + + @sample_rate.setter + def sample_rate(self, val): + self._rap_write(PINNACLE_SAMPLE_RATE, val) + self._sample_rate = self._rap_read(PINNACLE_SAMPLE_RATE) + + def calibrate(self, run, tap=True, track_error=True, nerd=True, background=True): + """Set calibration parameters when the Pinnacle ASIC calibrates itself. + + :param bool run: If `True`, this function forces a calibration of the sensor. If `False`, + this function just writes the following parameters to the Pinnacle ASIC's "CalConfig1" + register. This parameter is required while the rest are optional keyword parameters. + :param bool tap: Enable dynamic tap compensation? Default is `True`. + :param bool track_error: Enable dynamic track error compensation? Default is `True`. + :param bool nerd: Enable dynamic NERD compensation? Default is `True`. This parameter has + something to do with palm detection/compensation. + :param bool background: Enable dynamic background compensation? Default is `True`. + + .. note:: According to the datasheet, calibration of the sensor takes about 100 + milliseconds. This function will block until calibration is complete (if ``run`` is + `True`). It is recommended for typical applications to leave all optional parameters + in their default states. + """ + self._rap_write(PINNACLE_CALIBRATE_CONFIG, + tap << 4 | track_error << 3 | nerd << 2 | background << 1 | run) + while self._rap_read(PINNACLE_CALIBRATE_CONFIG) & 1: + pass # calibration is running + if run: + self.clear_flags() + + def report(self): + """This function will return touch event data from the touch controller (if there is any + new data ready to report -- including empty packets on ending of a touch event). + + :Returns: A `list` of parameters that describe the touch event. The list indices are as + follows: + + #. value of x-axis (signed if using relative mode data reports) + #. value of y-axis (signed if using relative mode data reports) + #. value of z-axis (signed and only available in absolute mode data reports) + #. value of scroll wheel () + """ + temp = [] # placeholder for data reception + return_vals = {} + if self.dr_pin.value: + if self._feed_config1 & 2: # if absolute mode + temp = self._rap_read_bytes(PINNACLE_PACKET_BYTE_0, 6) + return_vals = {'x': ((temp[4] & 0x0F) << 8) | temp[2], + 'y': ((temp[4] & 0xF0) << 4) | temp[3], + 'z': temp[5] & 0x3F, + 'buttons': temp[0] & 0x3F} + else: # if in relative mode + is_intellimouse = self._feed_config2 & 1 + # get relative data packets + temp = self._rap_read_bytes(PINNACLE_PACKET_BYTE_0, 3 + is_intellimouse) + return_vals = {'x': twos_comp(temp[1] | ((temp[0] & 16) << 4), 9), + 'y': twos_comp(temp[2] | ((temp[0] & 32) << 3), 9), + 'left': bool(temp[0] & 1), + 'right': bool(temp[0] & 2), + 'middle': bool(temp[0] & 4),} + if is_intellimouse: # scroll wheel data is captured + return_vals['wheel'] = twos_comp(temp[3], 8) + self.clear_flags() + return return_vals + + def set_adc_gain(self, sensitivity): + """Sets the ADC gain in range [0,3] to enhance performance based on the overlay type. + (write-only) + + :param int sensitivity: This int specifies how sensitive the ADC (Analog to Digital + Converter) component is. ``0`` means most sensitive, and ``3`` means least sensitive. + A value outside this range will raise a `ValueError` exception. + + .. tip:: The official example code from Cirque for a curved overlay uses a value of ``1``. + """ + if 0 <= sensitivity < 4: + prev_feed_state = self.feed_enable + self.feed_enable = False # accessing raw memory, so do this + val = self._era_read(0x0187) & 0x3F + val |= sensitivity << 6 + self._era_write(0x0187, val) + self.feed_enable = prev_feed_state # resume normal operation + else: + raise ValueError("{} is out of bounds [0,3]".format(sensitivity)) + + def tune_edge_sensitivity(self, x_axis_wide_z_min=0x04, y_axis_wide_z_min=0x03): + """According to the official exmaple code from Cirque, + this function "Changes thresholds to improve detection of fingers." + + This function was ported from Cirque's example code and doesn't seem to have corresponding + documentation. I'm having trouble finding a memory map of the Pinnacle ASIC as this + function directly alters values in the Pinnacle ASIC's memory. USE AT YOUR OWN RISK! + """ + prev_feed_state = self.feed_enable + self.feed_enable = False # accessing raw memory, so do this + # write x_axis_wide_z_min value + # self._era_read(0x0149) # this was used for printing unaltered value to serial monitor + self._era_write(0x0149, x_axis_wide_z_min) + # ERA_ReadBytes(0x0149) # this was used for printing verified value to serial monitor + # write y_axis_wide_z_min value + # self._era_read(0x0168) # this was used for printing unaltered value to serial monitor + self._era_write(0x0168, y_axis_wide_z_min) + # ERA_ReadBytes(0x0168) # this was used for printing verified value to serial monitor + self.feed_enable = prev_feed_state # resume normal operation + + @property + def z_idle_count(self): + """The number of empty packets (x-axis & y-axis both are ``0``) reported (every 10 + milliseconds) when there is no touch detected. Defaults to 30.""" + return self._z_idle_count + + @z_idle_count.setter + def z_idle_count(self, val): + self._z_idle_count = val + self._rap_write(PINNACLE_Z_IDLE, self._z_idle_count) + + def clear_flags(self): + """This function clears the "Data Ready" flag which is reflected with the ``dr_pin``.""" + self._rap_write(0x02, 0) # 0x02 = Status1 register + time.sleep(0.00005) # delay 50 microseconds per official example from Cirque + + def reset_device(self): + """Resets the touch controller. (write only) + + .. warning:: Resetting the touch controller will change all register configurations to + their default values which will be reflected in the `PinnacleTouch` object's + attributes. Calibration is also automatically performed as it part of the touch + controller's start-up sequence (unavoidable). + """ + self._sys_config = self._rap_read(PINNACLE_SYS_CONFIG) + self._sys_config = (self._sys_config & 0xFE) | 1 + self._rap_write(PINNACLE_SYS_CONFIG, self._sys_config) + while not self.dr_pin.value: + pass # wait for power-on & calibration to be performed + self._read_init_values() + self.clear_flags() + + @property + def allow_sleep(self): + """Set this attribute to `True` if you want the touch controller to enter sleep (low power) + mode after about 5 seconds of inactivity. While the touch controller is in sleep mode, if a + touch event or button press is detected, the Pinnacle ASIC will take about 300 milliseconds + to wake up (does not include handling the touch event or button press data).""" + return bool(self._sys_config & 4) + + @allow_sleep.setter + def allow_sleep(self, is_enabled): + self._sys_config = (self._sys_config & 0xFB) | is_enabled << 2 + self._rap_write(PINNACLE_SYS_CONFIG, self._sys_config) + + @property + def shutdown(self): + """This attribute controls power of the touch controller. `True` means powered down (AKA + standby mode), and `False` means not powered down (Active, Idle, or Sleep mode). + + .. note:: The touch controller will take about 300 milliseconds to complete the transition + from powered down mode to active mode. No touch events or button presses will be + monitored while powered down. + """ + return bool(self._sys_config & 2) + + @shutdown.setter + def shutdown(self, is_off): + self._sys_config = (self._sys_config & 0xFD) | is_off << 1 + self._rap_write(PINNACLE_SYS_CONFIG, self._sys_config) + + def _rap_read(self, reg): + """This function is overridden by the appropriate parent class based on interface type.""" + raise NotImplementedError() + + def _rap_read_bytes(self, reg, numb_bytes): + """This function is overridden by the appropriate parent class based on interface type.""" + raise NotImplementedError() + + def _rap_write(self, reg, value): + """This function is overridden by the appropriate parent class based on interface type.""" + raise NotImplementedError() + + def _rap_write_bytes(self, reg, value): + """This function is overridden by the appropriate parent class based on interface type.""" + raise NotImplementedError() + + def _era_read(self, reg): + self._rap_write_bytes(PINNACLE_ERA_ADDR_HIGH, [reg >> 8, reg & 0xff]) + self._rap_write(PINNACLE_ERA_CTRL, 1) # indicate reading only 1 byte + while self._rap_read(PINNACLE_ERA_CTRL): # read until reg == 0 + pass # also sets Command Complete flag in Status register + buf = self._rap_read(PINNACLE_ERA_VALUE) # get value + self.clear_flags() + return buf + + def _era_read_bytes(self, reg, numb_bytes): + buf = [] + self._rap_write_bytes(PINNACLE_ERA_ADDR_HIGH, [reg >> 8, reg & 0xff]) + self._rap_write(PINNACLE_ERA_CTRL, 5) # indicate reading sequential bytes + for _ in range(numb_bytes): + while self._rap_read(PINNACLE_ERA_CTRL): # read until reg == 0 + pass # also sets Command Complete flag in Status register + buf.append(self._rap_read(PINNACLE_ERA_VALUE)) # get value + self.clear_flags() + return buf + + def _era_write(self, reg, value): + self._rap_write(PINNACLE_ERA_VALUE, value) # write value + self._rap_write_bytes(PINNACLE_ERA_ADDR_HIGH, [reg >> 8, reg & 0xff]) + self._rap_write(PINNACLE_ERA_CTRL, 2) # indicate writing only 1 byte + while self._rap_read(PINNACLE_ERA_CTRL): # read until reg == 0 + pass # also sets Command Complete flag in Status register + self.clear_flags() + + def _era_write_bytes(self, reg, value, numb_bytes): + self._rap_write(PINNACLE_ERA_VALUE, value) # write value + self._rap_write_bytes(PINNACLE_ERA_ADDR_HIGH, [reg >> 8, reg & 0xff]) + self._rap_write(PINNACLE_ERA_CTRL, 0x0A) # indicate writing sequential bytes + for _ in range(numb_bytes): + while self._rap_read(PINNACLE_ERA_CTRL): # read until reg == 0 + pass # also sets Command Complete flag in Status register + self.clear_flags() + +class PinnacleTouchI2C(PinnacleTouch): + """ + Varaiant of the base class, `PinnacleTouch`, for interfacing with the touch controller via + the I2C protocol. + + :param ~busio.I2C i2c: The object of the I2C bus to use. This object must be shared among + other driver classes that use the same I2C bus (SDA & SCL pins). + :param int address: The slave I2C address of the touch controller. Defaults to ``0x2A``. + + See the base class for other instantiating parameters. + """ + + def __init__(self, i2c, dr_pin, address=0x2A, **kwargs): + self._i2c = I2CDevice(i2c, (address << 1)) + super(PinnacleTouchI2C, self).__init__(dr_pin, **kwargs) + + def _rap_read(self, reg): + return self._rap_read_bytes(reg) + + def _rap_read_bytes(self, reg, numb_bytes=1): + self._i2c.device_address &= 0 # set write flag + buf = bytearray([reg | 0xA0]) # per datasheet + with self._i2c as i2c: + i2c.write(buf) # includes a STOP condition + self._i2c.device_address &= 1 # set read flag + return_buf = b'' # for accumulating response(s) + buf = bytearray(1) + with self._i2c as i2c: + # need to send the ((address << 1) & read flag) for each byte read + for _ in range(numb_bytes): # increments register for each read command + i2c.readinto(buf) # I assume this includes a STOP condition + return_buf += buf # save response + return list(return_buf) + + def _rap_write(self, reg, value): + self._i2c.device_address &= 0 # set write flag + buf = bytearray([reg | 0x80, value & 0xFF]) # assumes value is an int + with self._i2c as i2c: + i2c.write(buf) # includes STOP condition + + def _rap_write_bytes(self, reg, value): + self._i2c.device_address &= 0 # set write flag + buf = b'' + for byte in value: # works for bytearrays/lists/tuples + buf += bytearray([reg | 0x80, byte]) + with self._i2c as i2c: + # need only 1 STOP condition for multiple write operations + i2c.write(buf) + +class PinnacleTouchSPI(PinnacleTouch): + """ + Varaiant of the base class, `PinnacleTouch`, for interfacing with the touch controller via + the SPI protocol. + + :param ~busio.SPI spi: The object of the SPI bus to use. This object must be shared among + other driver classes that use the same SPI bus (MOSI, MISO, & SCK pins). + :param ~microcontroller.Pin ss_pin: The "slave select" pin output to the touch controller. + + See the base class for other instantiating parameters. + """ + + def __init__(self, spi, ss_pin, dr_pin, **kwargs): + # MAX baudrate is up to 13MHz; use 10MHz to be safe + self._spi = SPIDevice(spi, chip_select=ss_pin, baudrate=12000000, phase=1) + super(PinnacleTouchSPI, self).__init__(dr_pin, **kwargs) + + def _rap_read(self, reg): + buf_out = bytearray([reg | 0xA0]) + b'\xFB' * 3 + buf_in = bytearray(len(buf_out)) + with self._spi as spi: + spi.write_readinto(buf_out, buf_in) + return buf_in[3] + + def _rap_read_bytes(self, reg, numb_bytes): + buf_out = bytearray([reg | 0xA0]) + b'\xFC' * (1 + numb_bytes) + b'\xFB' + buf_in = bytearray(len(buf_out)) + with self._spi as spi: + spi.write_readinto(buf_out, buf_in) + return list(buf_in[3:]) + + def _rap_write(self, reg, value): + buf = bytearray([(reg | 0x80), value]) + with self._spi as spi: + spi.write(buf) + + def _rap_write_bytes(self, reg, value): + for i, val in enumerate(value): + self._rap_write(reg + i, val) diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css new file mode 100644 index 0000000..d9bed55 --- /dev/null +++ b/docs/_static/darkness.css @@ -0,0 +1,274 @@ +/* -----------------------------code sections------------------------------ */ +.highlight { + background: #202020; +} + +.highlight .s1 { + color: #da8c00; +} + +.highlight .mi { + color: #9ff1c6; +} + +.highlight .nb { + color: #f7ef84; +} + +.highlight .ow { + color: #1f95cb; +} + +.highlight .kc { + color: #2886e4; +} + +.highlight .s2 { + color: #da8c00; +} + +.highlight .mf { + color: #9ff1c6; +} + +.highlight .se { + color: #e2c458; +} + +.highlight .si { + color: #2886e4; +} + +.highlight .sa { + color: #2886e4; +} + +.highlight .nf { + color: #f7ef84; +} + +.highlight .k { + color: #af63ab; +} + +.highlight .c1 { + color: #4bbc3f; +} + +.highlight .o { + color: #fefefe; +} + +.highlight .sd { + color: #da8c00; +} + +.highlight .nn { + color: #fff; +} + +.highlight .kn { + color: #af63ab; +} + +.highlight .bp { + color: #1f95cb; +} + +.highlight .nd { + color: #f7ef84; +} + +.highlight .ne { + color: #13d297; +} + +.highlight .mh { + color: #9ff1c6; +} + +/* ----------------------table sections---------------------------------------- */ +.wy-table thead, +.rst-content table.docutils thead, +.rst-content table.field-list thead { + color:#fcfcfc; + text-align:center; + background-color: #1a4228; +} + +.wy-table-odd td, +.wy-table-striped tr:nth-child(2n-1) td, +.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color:#3d3d3d; +} + +.wy-table-bordered-all td, +.rst-content table.docutils td { + border-bottom:1px solid #e1e4e5; + border-left:1px solid #e1e4e5; + text-align: center; +} + +/* ------------------------admonition sections------------------------------- */ +.wy-alert.wy-alert-success .wy-alert-title, +.rst-content .wy-alert-success.note .wy-alert-title, +.rst-content .wy-alert-success.attention .wy-alert-title, +.rst-content .wy-alert-success.caution .wy-alert-title, +.rst-content .wy-alert-success.danger .wy-alert-title, +.rst-content .wy-alert-success.error .wy-alert-title, +.rst-content .hint .wy-alert-title, +.rst-content .important .wy-alert-title, +.rst-content .tip .wy-alert-title, +.rst-content .wy-alert-success.warning .wy-alert-title, +.rst-content .wy-alert-success.seealso .wy-alert-title, +.rst-content .wy-alert-success.admonition-todo .wy-alert-title, +.rst-content .wy-alert-success.admonition .wy-alert-title, +.wy-alert.wy-alert-success .rst-content .admonition-title, +.rst-content .wy-alert.wy-alert-success .admonition-title, +.rst-content .wy-alert-success.note .admonition-title, +.rst-content .wy-alert-success.attention .admonition-title, +.rst-content .wy-alert-success.caution .admonition-title, +.rst-content .wy-alert-success.danger .admonition-title, +.rst-content .wy-alert-success.error .admonition-title, +.rst-content .hint .admonition-title, +.rst-content .important .admonition-title, +.rst-content .tip .admonition-title, +.rst-content .wy-alert-success.warning .admonition-title, +.rst-content .wy-alert-success.seealso .admonition-title, +.rst-content .wy-alert-success.admonition-todo .admonition-title, +.rst-content .wy-alert-success.admonition .admonition-title { + background:#05886e; +} + +.wy-alert.wy-alert-success, +.rst-content .wy-alert-success.note, +.rst-content .wy-alert-success.attention, +.rst-content .wy-alert-success.caution, +.rst-content .wy-alert-success.danger, +.rst-content .wy-alert-success.error, +.rst-content .hint, +.rst-content .important, +.rst-content .tip, +.rst-content .wy-alert-success.warning, +.rst-content .wy-alert-success.seealso, +.rst-content .wy-alert-success.admonition-todo, +.rst-content .wy-alert-success.admonition { + background: #28443e; +} + +.wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title { + background: #064873; +} + +.wy-alert.wy-alert-info, .rst-content .note, .rst-content .wy-alert-info.attention, .rst-content .wy-alert-info.caution, .rst-content .wy-alert-info.danger, .rst-content .wy-alert-info.error, .rst-content .wy-alert-info.hint, .rst-content .wy-alert-info.important, .rst-content .wy-alert-info.tip, .rst-content .wy-alert-info.warning, .rst-content .seealso, .rst-content .wy-alert-info.admonition-todo, .rst-content .wy-alert-info.admonition { + background: #2d3d48; +} + +.wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title { + background: #ed2222; +} + +.wy-alert.wy-alert-warning, .rst-content .wy-alert-warning.note, .rst-content .attention, .rst-content .caution, .rst-content .wy-alert-warning.danger, .rst-content .wy-alert-warning.error, .rst-content .wy-alert-warning.hint, .rst-content .wy-alert-warning.important, .rst-content .wy-alert-warning.tip, .rst-content .warning, .rst-content .wy-alert-warning.seealso, .rst-content .admonition-todo, .rst-content .wy-alert-warning.admonition { + background: #490707; +} + +/* -------------------------------------general page styling-------------------------------------------- */ +body { + color:#fcfcfc; +} + +.wy-nav-content-wrap, .wy-nav-content { + background:#242424; +} + +/* -------------------------------------sidebar sections------------------------------------------------ */ +.wy-side-nav-search { + background-color:#006B09; + color:#fcfcfc; +} + +.wy-side-nav-search input[type=text] { + background: #313131; + color: #fcfcfc; +} + +.wy-menu-vertical li.on a, +.wy-menu-vertical li.current>a { + color:#fcfcfc; + background:#242424; +} + +.wy-nav-side { + background:#172353; +} + +.wy-menu-vertical li.on a:hover, +.wy-menu-vertical li.current>a:hover { + background:#395c62; +} + +.wy-menu-vertical li.on a:hover, +.wy-menu-vertical li.current>a:hover { + background:#395c62; +} + +.wy-side-nav-search>div.version { + color:rgba(255, 255, 255, 0.69); +} + +.wy-menu-vertical header, +.wy-menu-vertical p.caption { + color:#43df96; +} + +.wy-nav-top { + background:#006B09; +} + +/* -----------------------------------------API sections-------------------------------------- */ +.rst-content dl:not(.docutils) dl dt { + background: #343434; + color: #e5df8e; +} + +.rst-content tt, .rst-content tt, .rst-content code { + color: #f3f3f3; +} + +.rst-content dl:not(.docutils) dt { + background: #2e363c; +} + +.rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { + color: #16FF00; + border-color: #303030; +} + +code, .rst-content tt, .rst-content code { + background: #000; +} + +.rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, .rst-content code.xref, a .rst-content tt, a .rst-content code { + color: #fff; +} + +.rst-content dl:not(.docutils) dl dt { + background: #343434; + color: #e5df8e; +} + +.rst-content dl:not(.docutils) dl dt .headerlink { + color: #29ae5b; +} + +.rst-content dl:not(.docutils) dt .headerlink { + color: #29ae5b4d !important; +} + +.rst-content dl dt .headerlink::after { + visibility: visible; +} + +.rst-content h1:hover .headerlink::after, .rst-content h2:hover .headerlink::after, .rst-content .toctree-wrapper p.caption:hover .headerlink::after, .rst-content h3:hover .headerlink::after, .rst-content h4:hover .headerlink::after, .rst-content h5:hover .headerlink::after, .rst-content h6:hover .headerlink::after, .rst-content dl dt:hover .headerlink::after, .rst-content p.caption:hover .headerlink::after, .rst-content table > caption:hover .headerlink::after, .rst-content .code-block-caption:hover .headerlink::after { + color: #29ae5b !important; +} diff --git a/docs/_static/examples.rst b/docs/_static/examples.rst new file mode 100644 index 0000000..6293b1b --- /dev/null +++ b/docs/_static/examples.rst @@ -0,0 +1,8 @@ +Simple Test via SPI +------------------- + +Ensure your device works with this simple test. + +.. literalinclude:: ../examples/cirque_pinnacle_spi_simpletest.py + :caption: examples/cirque_pinnacle_spi_simpletest.py + :linenos: diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c275d61b0b898cc4d6d7cc253992c29037e2b0b GIT binary patch literal 11062 zcmcIqJ&xNt5T;|`ImH7uRglgq13X}3JRm?i`v^ILiyXjZy44YKgw%F`mm~NHIf9Si z(#2-==A$(6csxUqlD&~KBsH8relw)B8^VPCef&4j|5d2|3E^!B;XU0S>Hb1D#&7wv zsJ4s8YP$!$rCgS^sCM;ZwfniK{(Hk?_og4J?XSoAwgJ7hF7l&-Y~1ke*77ZRq-VoA zTfN)0#W!>e>(BBjW=HrqR94PBU;%nqCupblCKy>%KR!#@v0d%H=h!xshhq5cO6LtR z@}b%t9QmuEHmBxEoZEWGVI-4SuTI-z|A^?3Q^8ogU7mPPD-X{*s@#*EyKe@`2A1*&eTl^ThbZlVy!t zn2s@|Jy-jL{@B{?;+*usKHHwSr)1T3jjx;|+c`Lw{M^P~uj6T9BoX(TWoe3a z)w0`l?ay1t%EkG$-aL%t`fkXtEx(ZWnpgHqa4zSeO|j^$QKcA_I$Hc(z7uq7wU>Ol zU5ay_pQA5_hvj*+G$uoE&bqN5*|<2L)qPB-vBNr;mSDGpm*s;3&$d73eGHhkcz_Ej zE2o_s`_eU)x^wC*+5S`eL@~sB`5P%Kx7{ai49;`*u|!;p_Dtz7)*Ug?apH9!gLC`b za``RE<|%(Ckv^gA+imYD?5~z1`!SE`ycw}4D8`wO^lq+V8P6l z_qPkbGJgM4>YsPsX1#b`DpT@XdCRhgU|PyfwXND7^@{iL)cn-5RNm4$UhTyxStZYa zXO5kux5BxVw_`A!>)RDZZisU$Z;@-cwhiTL^|pJ;J#c=JxAgsXsGX(r7$f(>Ip-~M ztkkwK{P)&3Mt*Z@=sE?^Bk1voE~1BJb6~op{(j!tLaNe zpZ}Vk!t-qMJZtIcVR8zmhZ%8HEI`SQs2>#e zz&NI`H`-5>{EGg-zgOfZIUD_qes^(b<1ueyd=M|hiQ*XJ#_^>1#<(KR4L#@h|8AP! G)c+sKrn>+D literal 0 HcmV?d00001 diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..d9a34f3 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,23 @@ + +.. If you created a package, create one automodule per module in the package. + +.. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) +.. use this format as the module name: "adafruit_foo.foo" + +PinnacleTouch +------------- + +.. autoclass:: circuitpython_cirque_pinnacle.PinnacleTouch + :members: + +PinnacleTouchSPI +---------------- + +.. autoclass:: circuitpython_cirque_pinnacle.PinnacleTouchSPI + :members: + +PinnacleTouchI2C +---------------- + +.. autoclass:: circuitpython_cirque_pinnacle.PinnacleTouchI2C + :members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..1712c3b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +import os +import sys +sys.path.insert(0, 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.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', +] + +# TODO: Please Read! +# Uncomment the below if you use native CircuitPython modules such as +# digitalio, micropython and busio. List the modules you use. Without it, the +# autodoc module docs will fail to generate with a warning. +autodoc_mock_imports = ["digitalio", "busio"] +autodoc_member_order = 'bysource' + + +intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'BusDevice': ('https://circuitpython.readthedocs.io/projects/busdevice/en/latest/', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Circuitpython Cirque-Pinnacle Library' +copyright = u'2020 Brendan Doherty' +author = u'Brendan Doherty' + +# 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 = u'1.0' +# The full version, including alpha/beta/rc tags. +release = u'1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.env', 'CODE_OF_CONDUCT.md'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +default_role = "any" + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +add_function_parentheses = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# If this is True, todo emits a warning for each TODO entries. The default is False. +todo_emit_warnings = False + +napoleon_numpy_docstring = False + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] + except: + html_theme = 'default' + html_theme_path = ['.'] +else: + html_theme_path = ['.'] + +# 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'] + + +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + 'darkness.css', +] + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +html_favicon = '_static/favicon.ico' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'CircuitpythonPinnacle1ca027Librarydoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'CircuitPythonPinnacle1ca027Library.tex', u'CircuitPython Pinnacle 1ca027 Library Documentation', + author, 'manual'), +] + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'CircuitPythonPinnacle1ca027library', u'CircuitPython Pinnacle 1ca027 Library Documentation', + [author], 1) +] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'CircuitpythonPinnacle1ca027Library', u'Circuitpython Pinnacle 1ca027 Library Documentation', + author, 'CircuitpythonPinnacle1ca027Library', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..d0add94 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,40 @@ +.. include:: ../README.rst + +Table of Contents +================= + +.. toctree:: + :maxdepth: 4 + :hidden: + + self + +.. toctree:: + :caption: API Reference + :maxdepth: 3 + + api + +.. toctree:: + :caption: Related Products + +.. todo:: Add any product links here. If there are none, then simply delete this todo and leave + the toctree above for use later. + +.. toctree:: + :caption: Other Links + + Download + CircuitPython Reference Documentation + CircuitPython Support Forum + Discord Chat + Adafruit Learning System + Adafruit Blog + Adafruit Store + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/examples/cirque_pinnacle_spi_simpletest.py b/examples/cirque_pinnacle_spi_simpletest.py new file mode 100644 index 0000000..d27f528 --- /dev/null +++ b/examples/cirque_pinnacle_spi_simpletest.py @@ -0,0 +1,22 @@ +""" a simple test to debug the library's API """ +import time +import board +from digitalio import DigitalInOut +import circuitpython_cirque_pinnacle as pinnacle + +SPI = board.SPI() +ss_pin = DigitalInOut(board.D7) +dr_pin = DigitalInOut(board.D2) + +trackpad = pinnacle.PinnacleTouchSPI(SPI, ss_pin, dr_pin, allow_sleep=1) +trackpad.set_adc_gain(1) # for curved overlay type + +def print_data(timeout=10): + """Print available data reports from the Pinnacle touch controller + for a period of ``timeout`` seconds. + """ + start = time.monotonic() + while time.monotonic() - start < timeout: + data = trackpad.report() + if data: + print(data) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..869ca63 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Adafruit-Blinka +adafruit-circuitpython-busdevice diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5a70041 --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +"""A setuptools based setup module. + +See: +https://packaging.python.org/en/latest/distributing.html +https://github.com/pypa/sampleproject +""" + +from setuptools import setup, find_packages +# To use a consistent encoding +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.rst'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name='circuitpython-cirque-pinnacle', + + use_scm_version=True, + setup_requires=['setuptools_scm'], + + description='A CircuitPython driver for the Pinnacle (1CA027) touch ' + 'controller used in Cirque Trackpads implementing the Adafruit_BusDevice library.', + long_description=long_description, + long_description_content_type='text/x-rst', + + # The project's main homepage. + url='https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle', + + # Author details + author='Brendan Doherty', + author_email='2bndy5@gmail.com', + + install_requires=[ + 'Adafruit-Blinka', + 'adafruit-circuitpython-busdevice' + ], + + # Choose your license + license='MIT', + + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Libraries', + 'Topic :: System :: Hardware', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ], + + # What does your project relate to? + keywords='adafruit blinka circuitpython Pinnacle ' + 'touch sensor driver Cirque trackpad', + + # You can just specify the packages manually here if your project is + # simple. Or you can use find_packages(). + # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, + # CHANGE `py_modules=['...']` TO `packages=['...']` + py_modules=['circuitpython_cirque_pinnacle'], +)