diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 699adec..2c84b88 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,58 +1,141 @@ name: Build CI -on: [pull_request, push] +on: + pull_request: + types: [opened, reopened] + push: jobs: - test: + build-wheel: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.6 - uses: actions/setup-python@v1 - with: - python-version: 3.6 - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install deps - run: | - source actions-ci/install.sh - - name: Pip install pylint & Sphinx - run: | - pip install --force-reinstall pylint==2.4.1 Sphinx sphinx-rtd-theme - - name: Library version - run: git describe --dirty --always --tags - - name: PyLint - run: | - pylint --disable=too-many-arguments circuitpython_cirque_pinnacle/*.py - ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace,too-few-public-methods $( find . -path "./examples/*.py" )) - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix circuitpython_cirque_pinnacle - - name: Archive bundles - uses: actions/upload-artifact@v2 - with: - name: bundles - path: ${{ github.workspace }}/bundles/ - - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - uses: actions/checkout@v3 + + - name: Build wheel + run: pip wheel -w dist --no-deps . + + - name: check dist + run: pipx run twine check dist/* + + - name: Archive wheel + uses: actions/upload-artifact@v3 + with: + name: wheel + path: ${{ github.workspace }}/dist/ + + linters: + runs-on: ubuntu-latest + steps: + + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - uses: actions/checkout@v3 + + - name: Install pre-commit and deps + run: pip install pre-commit -r requirements.txt + + - name: Setup problem matchers + uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 + + - name: Pre-commit hooks + run: pre-commit run --all-files + + # test-coverage: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + + # - uses: actions/setup-python@v4 + # with: + # python-version: "3.x" + + # - name: Install testing tools + # run: pip install -r requirements.txt -r tests/requirements.txt + + # - name: Collect coverage + # run: | + # coverage run -m pytest + # coverage report + # coverage xml + + # - name: Upload coverage reports to Codecov + # uses: codecov/codecov-action@v3 + # with: + # files: ./coverage.xml + # verbose: true # optional (default = false) + + build-bundles: + runs-on: ubuntu-latest + steps: + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo repo-name=$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) >> $GITHUB_OUTPUT + + - name: Translate Repo Name For Build Tools package_prefix + id: pkg-name + run: | + echo pkg-name=$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' + ) >> $GITHUB_OUTPUT + + - uses: actions/checkout@v3 + + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Checkout tools repo + uses: actions/checkout@v3 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + + - name: Install deps + run: | + source actions-ci/install.sh + + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --package_folder_prefix ${{ steps.pkg-name.outputs.pkg-name }} --library_location . + + - name: Archive bundles + uses: actions/upload-artifact@v3 + with: + name: bundles + path: ${{ github.workspace }}/bundles/ + + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install deps + run: | + pip install -r docs/requirements.txt -r requirements.txt + + - name: Build docs + working-directory: docs + run: sphinx-build -E -W -b html . _build/html + + - name: Archive docs + uses: actions/upload-artifact@v3 + with: + name: docs + path: ${{ github.workspace }}/docs/_build/html diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d77b82..1ca8b0b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,79 +3,98 @@ name: Release Actions on: release: types: [published] + workflow_dispatch: jobs: upload-release-assets: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.6 - uses: actions/setup-python@v1 - with: - python-version: 3.6 - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install deps - run: | - source actions-ci/install.sh - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix circuitpython_cirque_pinnacle - - name: Upload Release Assets - # the 'official' actions version does not yet support dynamically - # supplying asset names to upload. @csexton's version chosen based on - # discussion in the issue below, as its the simplest to implement and - # allows for selecting files with a pattern. - # https://github.com/actions/upload-release-asset/issues/4 - #uses: actions/upload-release-asset@v1.0.1 - uses: csexton/release-asset-action@master - with: - pattern: "bundles/*" - github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo repo-name=$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) >> $GITHUB_OUTPUT + + - name: Translate Repo Name For Build Tools package_prefix + id: pkg-name + run: | + echo pkg-name=$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' + ) >> $GITHUB_OUTPUT + + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Checkout Current Repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Checkout tools repo + uses: actions/checkout@v3 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + + - name: Install deps + run: | + source actions-ci/install.sh + + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --package_folder_prefix ${{ steps.pkg-name.outputs.pkg-name }} --library_location . + + - name: Archive bundles + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v3 + with: + name: bundles + path: ${{ github.workspace }}/bundles/ + + - name: Upload Release Assets + if: github.event_name == 'release' + uses: shogo82148/actions-upload-release-asset@v1 + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: "bundles/*" upload-pypi: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Check For setup.py - id: need-pypi - run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - - name: Set up Python - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - uses: actions/setup-python@v1 - with: - python-version: '3.x' - - name: Install dependencies - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.pypi_token }} - run: | - python setup.py sdist - twine upload dist/* + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install build tools + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build distributions + run: python -m build + + - name: Check distributions + run: twine check dist/* + + - name: Publish package (to TestPyPI) + if: github.event_name == 'workflow_dispatch' && github.repository == '2bndy5/CircuitPython_Cirque_Pinnacle' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN }} + run: twine upload --repository testpypi dist/* + + - name: Publish package (to PyPI) + if: github.event_name != 'workflow_dispatch' && github.repository == '2bndy5/CircuitPython_Cirque_Pinnacle' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: twine upload dist/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..529b734 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +repos: + - repo: https://github.com/python/black + rev: 22.8.0 + hooks: + - id: black + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/pycqa/pylint + rev: v2.15.3 + hooks: + - id: pylint + name: pylint (library code) + types: [python] + args: + - --disable=consider-using-f-string,duplicate-code,too-many-public-methods,too-few-public-methods + exclude: "^(docs/|examples/|tests/|setup.py$)" + additional_dependencies: ["adafruit-circuitpython-busdevice"] + - id: pylint + name: pylint (example code) + description: Run pylint rules on "examples/*.py" files + types: [python] + files: "^examples/" + args: + - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code,import-error + - id: pylint + name: pylint (test code) + description: Run pylint rules on "tests/*.py" files + types: [python] + additional_dependencies: [pytest] + files: "^tests/" + args: + - --disable=import-error,invalid-name,protected-access,too-few-public-methods + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.981 + hooks: + - id: mypy + name: mypy (library code) + exclude: "^(docs/|examples/|tests/|setup.py$)" + types: [python] diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index a95e6a0..0000000 --- a/.pylintrc +++ /dev/null @@ -1,433 +0,0 @@ -[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 index e311bcb..2ed8c42 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,3 +1,8 @@ +version: 2 + python: - version: 3 -requirements_file: requirements.txt + version: 3 + install: + - requirements: docs/requirements.txt + - method: pip + path: . diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..e63668a --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,50 @@ + +Contributing Guidelines +======================= + +Linting the source code +----------------------- + +.. _pre-commit: https://pre-commit.com/ + +This library uses pre-commit_ for some linting tools like + +- `black `_ +- `pylint `_ +- `mypy `_ + +To use pre-commit_, you must install it and create the cached environments that it needs. + +.. code-block:: shell + + pip install pre-commit + pre-commit install + +Now, every time you commit something it will run pre-commit_ on the changed files. You can also +run pre-commit_ on staged files: + +.. code-block:: shell + + pre-commit run + +.. note:: + Use the ``--all-files`` argument to run pre-commit on all files in the repository. + + +Building the Documentation +-------------------------- + +To build library documentation, you need to install the documentation dependencies. + +.. code-block:: shell + + pip install -r docs/requirements.txt + +Finally, build the documentation with Sphinx: + +.. code-block:: shell + + sphinx-build -E -W docs docs/_build/html + +The rendered HTML files should now be located in the ``docs/_build/html`` folder. Point your +internet browser to this path and check the changes have been rendered properly. diff --git a/README.rst b/README.rst index 735714c..ddc252c 100644 --- a/README.rst +++ b/README.rst @@ -11,162 +11,10 @@ :alt: latest version on PyPI :target: https://pypi.python.org/pypi/circuitpython-cirque-pinnacle -.. image:: https://pepy.tech/badge/circuitpython-cirque-pinnacle +.. image:: https://static.pepy.tech/personalized-badge/circuitpython-cirque-pinnacle?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Pypi%20Downloads :alt: Total PyPI downloads :target: https://pepy.tech/project/circuitpython-cirque-pinnacle +A circuitpython library for using the Cirque Glidepoint circle trackpads (as seen in the Steam Controller and Valve\HTC Vive VR controllers). -Introduction -============ - -A CircuitPython driver library that implements the Adafruit_BusDevice library -for interfacing with the Cirque Pinnacle (1CA027) touch controller used in Cirque Circle Trackpads. - -Supported Features ------------------- - -* Use SPI or I2C bus protocols to interface with the Pinnacle touch controller ASIC (Application - Specific Integrated Circuit). -* Relative mode data reporting (AKA Mouse mode) with optional tap detection. -* Absolute mode data reporting (x, y, & z axis positions). -* AnyMeas mode data reporting. This mode exposes the ADC (Analog to Digital Converter) values and is - not well documented in the numerous datasheets provided by the Cirque corporation about the - Pinnacle (1CA027), thus this is a rather experimental mode. -* Hardware input buttons' states included in data reports. There are 3 button input lines on - the Cirque circle trackpads -- see `Pinout`_ section. -* Configure measurements for finger or stylus (or automatically detirmine either) touch - events. The Cirque circle trackpads are natively capable of measuring only 1 touch - point per event. -* Download/upload the underlying compensation matrix for ADC measurements. -* Adjust the ADC matrix gain (sensitivity). - -.. 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 allow - changing the I2C slave device address (via software); this means only 1 Cirque circle trackpad - can be accessed over the I2C bus in the lifecycle of an application. That said, you could change - the I2C address from ``0x2A`` to ``0x2C`` by soldering a 470K ohm resistor at the junction - labeled "ADR" (see picture in `Pinout`_ section), although this is untested. - -Unsupported Features --------------------- - -* The legacy PS\\2 interface is pretty limited and not accessible by some CircuitPython MCUs. - Therefore, it has been neglected in this library. -* Cirque's circle trackpads ship with the newer non-AG (Advanced Gestures) variant of the - Pinnacle touch controller ASIC. Thus, this library focuses on the the non-AG variant's - functionality via testing, and it does not provide access to the older AG variant's features - (register addresses slightly differ which breaks compatibility). - -Pinout -====== - -.. image:: https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle/raw/master/docs/_static/Cirque_GlidePoint-Circle-Trackpad.png - :target: https://www.mouser.com/new/cirque/glidepoint-circle-trackpads/ - -The above picture is an 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 FFC/FPC cable `_ connector (the white block near the -bottom). The following table shows how the pins are connected in the `examples `_ (tested on an `ItsyBitys M4 `_) - -.. csv-table:: pinout (ordered the same as the FFC/FPC cable connector) - :header: Label,"MCU pin",Description - :widths: 5,5,13 - - SCK,SCK,"SPI clock line" - SO,MISO,"Master Input Slave Output" - SS,D7,"Slave Select (AKA Chip Select)" - DR,D2,"""data ready"" interrupt" - SI,MOSI,"SPI Master Output Slave Input" - B2,N/A,"Hardware input button #2" - B3,N/A,"Hardware input button #3" - B1,N/A,"Hardware input button #1" - SCL,SCL,"I2C clock line" - SDA,SDA,"I2C data line" - GND,GND,"Ground" - VDD,3V,"3V power supply" - -.. tip:: Of course, you can capture button data manually (if your application utilizes more - than 3 buttons), but if you connect the pins B1, B2, B3 to momentary push buttons that - (when pressed) provide a path to ground, the Pinnacle touch controller will report all 3 - buttons' states for each touch (or even button only) events. - -Model Labeling Scheme ---------------------- - - TM\ [yyyxxx]_\ -202\ [i]_\ -\ [cc]_\ [o]_ - - .. [yyyxxx] stands for the vertical & horizontal width of the trackpad, respectively. - .. [i] stands for the hardwired interface protocol (3 = I2C, 4 = SPI). Notice, if there is a - resistor populated at the R1 (470K ohm) junction (located just above the Pinnacle ASIC), it - is configured for SPI, otherwise it is configured for I2C. - .. [cc] stands for Custom Configuration which describes if a 470K ohm resistor is populated at - junction R4. "30" (resistor at R4 exists) means that the hardware is configured to disable - certain features despite what this library does. "00" (no resistor at R4) means that the - hardware is configured to allow certain features to be manipulated by this library. These - features include "secondary tap" (thought of as "right mouse button" in relative data mode), - Intellimouse scrolling (Microsoft patented scroll wheel behavior -- a throw back to when - scroll wheels were first introduced), and 180 degree orientation (your application can invert - the axis data anyway). - .. [o] stands for the overlay type (0 = none, 1 = adhesive, 2 = flat, 3 = curved) - -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 is deployed to pypi.org, so you can easily install this library -using ``pip3 install circuitpython-cirque-pinnacle`` or use the following -commands: - -.. 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 TMyyyxxx correctly by running the `examples/` located in the `examples -folder of this library `_. - -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. +Read the `Documentation at ReadTheDocs.org `_ diff --git a/circuitpython_cirque_pinnacle/glidepoint.py b/circuitpython_cirque_pinnacle/glidepoint.py index e4fbb92..784db8d 100644 --- a/circuitpython_cirque_pinnacle/glidepoint.py +++ b/circuitpython_cirque_pinnacle/glidepoint.py @@ -1,49 +1,72 @@ """ -A driver class for the Cirque Pinnacle ASIC on the Cirque capacitve touch +A driver class for the Cirque Pinnacle ASIC on the Cirque capacitive touch based circular trackpads. """ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle.git" import time -from struct import pack, unpack -from micropython import const +import struct + try: - from ubus_device import SPIDevice, I2CDevice + from typing import Optional, List, Union except ImportError: - from adafruit_bus_device.spi_device import SPIDevice - from adafruit_bus_device.i2c_device import I2CDevice - -RELATIVE = const(0x00) -ANYMEAS = const(0x01) -ABSOLUTE = const(0x02) -GAIN_100 = const(0xC0) -GAIN_133 = const(0x80) -GAIN_166 = const(0x40) -GAIN_200 = const(0x00) -FREQ_0 = const(0x02) -FREQ_1 = const(0x03) -FREQ_2 = const(0x04) -FREQ_3 = const(0x05) -FREQ_4 = const(0x06) -FREQ_5 = const(0x07) -FREQ_6 = const(0x09) -FREQ_7 = const(0x0B) -MUX_REF1 = const(0x10) -MUX_REF0 = const(0x08) -MUX_PNP = const(0x04) -MUX_NPN = const(0x01) -CRTL_REPEAT = const(0x80) -CRTL_PWR_IDLE = const(0x40) + pass + +from micropython import const +import digitalio +import busio +from adafruit_bus_device.spi_device import SPIDevice +from adafruit_bus_device.i2c_device import I2CDevice + +RELATIVE: int = const(0x00) +ANYMEAS: int = const(0x01) +ABSOLUTE: int = const(0x02) +GAIN_100: int = const(0xC0) #: around 100% gain +GAIN_133: int = const(0x80) #: around 133% gain +GAIN_166: int = const(0x40) #: around 166% gain +GAIN_200: int = const(0x00) #: around 200% gain +FREQ_0: int = const(0x02) #: frequency around 500,000Hz +FREQ_1: int = const(0x03) #: frequency around 444,444Hz +FREQ_2: int = const(0x04) #: frequency around 400,000Hz +FREQ_3: int = const(0x05) #: frequency around 363,636Hz +FREQ_4: int = const(0x06) #: frequency around 333,333Hz +FREQ_5: int = const(0x07) #: frequency around 307,692Hz +FREQ_6: int = const(0x09) #: frequency around 267,000Hz +FREQ_7: int = const(0x0B) #: frequency around 235,000Hz +MUX_REF1: int = const(0x10) #: enables a builtin capacitor (~0.5pF). +MUX_REF0: int = const(0x08) #: enables a builtin capacitor (~0.25pF). +MUX_PNP: int = const(0x04) #: enable PNP sense line +MUX_NPN: int = const(0x01) #: enable NPN sense line +CRTL_REPEAT: int = const(0x80) #: required for more than 1 measurement +CRTL_PWR_IDLE: int = const( + 0x40 +) #: triggers low power mode (sleep) after completing measurements + class PinnacleTouch: - """The abstract base class for driving the Pinnacle ASIC.""" - def __init__(self, dr_pin=None): + """The abstract base class for driving the Pinnacle ASIC. + + :param ~digitalio.DigitalInOut dr_pin: |dr_pin_parameter| + + .. important:: |dr_pin_note| + + .. |dr_pin_parameter| replace:: The input pin connected to the Pinnacle ASIC's "Data + Ready" pin. If this parameter is not specified, then the SW_DR (software data ready) flag + of the STATUS register is used to determine if the data being reported is new. + + .. |dr_pin_note| replace:: This parameter must be specified if your application is + going to use the Pinnacle ASIC's + :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` mode (a rather + experimental measuring of raw ADC values). + """ + + def __init__(self, dr_pin: Optional[digitalio.DigitalInOut] = None): self.dr_pin = dr_pin - if dr_pin is not None: + if self.dr_pin is not None: self.dr_pin.switch_to_input() firmware_id, firmware_ver = self._rap_read_bytes(0, 2) if firmware_id != 7 or firmware_ver != 0x3A: - raise OSError("Cirque Pinnacle ASIC not responding") + raise RuntimeError("Cirque Pinnacle ASIC not responding") # init internal attributes w/ factory defaults after power-on-reset self._mode = 0 # 0 means relative mode which is factory default self.detect_finger_stylus() @@ -53,29 +76,54 @@ def __init__(self, dr_pin=None): self.calibrate(True) # enables all compensations @property - def feed_enable(self): + def feed_enable(self) -> bool: """This `bool` attribute controls if the touch/button event data is - reported (`True`) or not (`False`).""" + reported (`True`) or not (`False`). + + This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` + or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode. Otherwise if + `data_mode` is set to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, + then this attribute will have no effect. + """ return bool(self._rap_read(4) & 1) @feed_enable.setter - def feed_enable(self, is_on): + def feed_enable(self, is_on: bool): is_enabled = self._rap_read(4) - if is_enabled & 1 != is_on: + if bool(is_enabled & 1) != is_on: # save ourselves the unnecessary transaction is_enabled = (is_enabled & 0xFE) | is_on self._rap_write(4, is_enabled) @property - def data_mode(self): - """This attribute controls which mode the data report is configured - for.""" + def data_mode(self) -> int: + """This attribute controls which mode the data reports are configured + for. + + Valid input values are :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` for + relative/mouse mode, :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` for + absolute positioning mode, or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` + (referred to as "AnyMeas" in specification sheets) mode for reading ADC values. + + :Returns: + + - ``0`` for Relative mode (AKA mouse mode) + - ``1`` for AnyMeas mode (raw ADC measurements) + - ``2`` for Absolute mode (X & Y axis positions) + + .. important:: + When switching from :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` to + :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` or + :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` all + configurations are reset, and must be re-configured by using + `absolute_mode_config()` or `relative_mode_config()`. + """ return self._mode @data_mode.setter - def data_mode(self, mode): + def data_mode(self, mode: int): if mode not in (ANYMEAS, RELATIVE, ABSOLUTE): - raise ValueError("Unrecognised input value for data_mode.") + raise ValueError("Unrecognized input value for data_mode.") sys_config = self._rap_read(3) & 0xE7 # clear AnyMeas mode flags if mode in (RELATIVE, ABSOLUTE): if self.data_mode == ANYMEAS: # if leaving AnyMeas mode @@ -98,89 +146,251 @@ def data_mode(self, mode): self._mode = mode @property - def hard_configured(self): - """This `bool` attribute can be used to inform applications about - factory customized hardware configuration.""" - return bool(self._rap_read(0x1f)) - - def relative_mode_config(self, rotate90=False, taps=True, - secondary_tap=True, glide_extend=True, - intellimouse=False): + def hard_configured(self) -> bool: + """This read-only `bool` attribute can be used to inform applications about + factory customized hardware configuration. See note about product labeling in + `Model Labeling Scheme `_. + + :Returns: + `True` if a 470K ohm resistor is populated at the junction labeled "R4" + """ + return bool(self._rap_read(0x1F)) + + def relative_mode_config( + self, + rotate90: bool = False, + taps: bool = True, + secondary_tap: bool = True, + glide_extend: bool = True, + intellimouse: bool = False, + ): """Configure settings specific to Relative mode (AKA Mouse mode) data - reporting.""" + reporting. + + This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` + mode, otherwise if `data_mode` is set to + :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` or + :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE`, then this function does nothing. + + :param rotate90: Specifies if the axis data is altered for 90 degree rotation before + reporting it (essentially swaps the axis data). Default is `False`. + :param taps: Specifies if all taps should be reported (`True`) or not + (`False`). Default is `True`. This affects ``secondary_tap`` option as well. + :param secondary_tap: Specifies if tapping in the top-left corner (depending on + orientation) triggers the secondary button data. Defaults to `True`. This feature is + always disabled if `hard_configured` is `True`. + :param glide_extend: A patented 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 always disabled if `hard_configured` is `True`. + :param 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 always + disabled if `hard_configured` is `True`. + """ if self.data_mode == RELATIVE: config2 = (rotate90 << 7) | ((not glide_extend) << 4) config2 |= ((not secondary_tap) << 2) | ((not taps) << 1) self._rap_write(5, config2 | bool(intellimouse)) - def absolute_mode_config(self, z_idle_count=30, - invert_x=False, invert_y=False): + def absolute_mode_config( + self, z_idle_count: int = 30, invert_x: bool = False, invert_y: bool = False + ): """Configure settings specific to Absolute mode (reports axis - positions).""" + positions). + + This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` + mode, otherwise if `data_mode` is set to + :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` or + :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE`, then this function does nothing. + + :param z_idle_count: Specifies the number of empty packets (x-axis, y-axis, and z-axis + are ``0``) reported (every 10 milliseconds) when there is no touch detected. Defaults + to 30. This number is clamped to range [0, 255]. + :param invert_x: Specifies if the x-axis data is to be inverted before reporting it. + Default is `False`. + :param invert_y: Specifies if the y-axis data is to be inverted before reporting it. + Default is `False`. + """ if self.data_mode == ABSOLUTE: self._rap_write(0x0A, max(0, min(z_idle_count, 255))) config1 = self._rap_read(4) & 0x3F | (invert_y << 7) self._rap_write(4, config1 | (invert_x << 6)) - def report(self, only_new=True): + def available(self) -> bool: + """Determine if there is fresh data to report. + + If the ``dr_pin`` parameter is specified upon instantiation, then the specified + input pin is used to detect if the data is new. Otherwise the SW_DR flag in the + STATUS register is used to determine if the data is new. + + :Return: If there is fresh data to report (`True`) or not (`False`). + + .. versionadded:: 0.0.5 + """ + if self.dr_pin is None: + return bool(self._rap_read(2) & 4) + return self.dr_pin.value + + def read(self) -> Optional[Union[List[int], bytearray]]: """This function will return touch event data from the Pinnacle ASIC - (including empty packets on ending of a touch event).""" + (including empty packets on ending of a touch event). + + This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` + or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode. Otherwise if + `data_mode` is set to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this + function returns `None` and does nothing. + + :Returns: A `list` or `bytearray` of parameters that describe the (touch or + button) event. The structure is as follows: + + .. list-table:: + :header-rows: 1 + :widths: 1, 5, 5 + + * - Index + - Relative (Mouse) mode + + as a `bytearray` + - Absolute Mode + + as a `list` + * - 0 + - Button Data [1]_ + + one unsigned byte + - Button Data [1]_ + + one unsigned byte + * - 1 + - change in x-axis [2]_ + + -128 |LessEq| X |LessEq| 127 + - x-axis Position + + 0 |LessEq| X |LessEq| 2047 [4]_ + * - 2 + - change in y-axis [2]_ + + -128 |LessEq| Y |LessEq| 127 + - y-axis Position + + 0 |LessEq| Y |LessEq| 1535 [5]_ + * - 3 + - change in scroll wheel [3]_ + + -128 |LessEq| SCROLL |LessEq| 127 + - z-axis Magnitude + + .. [1] The returned button data is a byte in which each bit represents a button. + The bit to button order is as follows: + + 0. [LSB] Button 1 (thought of as Left button in Relative/Mouse mode). If ``taps`` + parameter is passed as `True` when calling `relative_mode_config()`, a single + tap will be reflected here. + 1. Button 2 (thought of as Right button in Relative/Mouse mode). If ``taps`` and + ``secondary_tap`` parameters are passed as `True` when calling + `relative_mode_config()`, a single tap in the perspective top-left-most corner will + be reflected here (secondary taps are constantly disabled if `hard_configured` + returns `True`). Note that the top-left-most corner can be perspectively moved if + ``rotate90`` parameter is passed as `True` when calling `relative_mode_config()`. + 2. Button 3 (thought of as Middle or scroll wheel button in Relative/Mouse mode) + .. [2] The axis data reported in Relative/Mouse mode is in two's + compliment form. Use Python's :py:func:`struct.unpack()` to convert the + data into integer form (see `Simple Test example `_ + for how to use this function). + + The axis data reported in Absolute mode is always positive as the + xy-plane's origin is located to the top-left, unless ``invert_x`` or ``invert_y`` + parameters to `absolute_mode_config()` are manipulated to change the perspective + location of the origin. + .. [3] In Relative/Mouse mode the scroll wheel data is only reported if the + ``intellimouse`` parameter is passed as `True` to `relative_mode_config()`. + Otherwise this is an empty byte as the + returned `bytearray` follows the buffer structure of a mouse HID report (see + `USB Mouse example `_). + .. [4] The datasheet recommends the x-axis value (in Absolute mode) should be + clamped to range 128 |LessEq| ``x`` |LessEq| 1920 for reliability. + .. [5] The datasheet recommends the y-axis value (in Absolute mode) should be + clamped to range 64 |LessEq| ``y`` |LessEq| 1472 for reliability. + .. |LessEq| unicode:: U+2264 + + .. versionchanged:: 0.0.5 + removed ``only_new`` parameter in favor of using `available()`. + """ if self._mode == ANYMEAS: return None - return_vals = None - data_ready = False - if only_new: - if self.dr_pin is None: - data_ready = self._rap_read(2) & 4 - else: - data_ready = self.dr_pin.value - if (only_new and data_ready) or not only_new: - if self.data_mode == ABSOLUTE: # if absolute mode - return_vals = list(self._rap_read_bytes(0x12, 6)) - return_vals[0] &= 0x3F # buttons - return_vals[2] |= (return_vals[4] & 0x0F) << 8 # x - return_vals[3] |= (return_vals[4] & 0xF0) << 4 # y - return_vals[5] &= 0x3F # z - del return_vals[4], return_vals[1] # no longer need these - elif self.data_mode == RELATIVE: # if in relative mode - return_vals = self._rap_read_bytes(0x12, 4) - return_vals[0] &= 7 - self.clear_flags() + return_vals: Optional[Union[List[int], bytearray]] = None + if self.data_mode == ABSOLUTE: # if absolute mode + return_vals = list(self._rap_read_bytes(0x12, 6)) + return_vals[0] &= 0x3F # buttons + return_vals[2] |= (return_vals[4] & 0x0F) << 8 # x + return_vals[3] |= (return_vals[4] & 0xF0) << 4 # y + return_vals[5] &= 0x3F # z + del return_vals[4], return_vals[1] # no longer need these + elif self.data_mode == RELATIVE: # if in relative mode + return_vals = self._rap_read_bytes(0x12, 4) + return_vals[0] &= 7 + self.clear_status_flags() return return_vals - def clear_flags(self): + def clear_status_flags(self): """This function clears the "Data Ready" flag which is reflected with the ``dr_pin``.""" self._rap_write(2, 0) time.sleep(0.00005) # per official example from Cirque @property - def allow_sleep(self): + def allow_sleep(self) -> bool: """This attribute specifies if the Pinnacle ASIC is allowed to sleep - after about 5 seconds of idle (no input event).""" + after about 5 seconds of idle (no input event). + + Set this attribute to `True` if you want the Pinnacle ASIC to enter sleep (low power) + mode after about 5 seconds of inactivity (does not apply to AnyMeas mode). 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._rap_read(3) & 4) @allow_sleep.setter - def allow_sleep(self, is_enabled): + def allow_sleep(self, is_enabled: bool): self._rap_write(3, (self._rap_read(3) & 0xFB) | (is_enabled << 2)) @property - def shutdown(self): - """This attribute controls power of the Pinnacle ASIC.""" + def shutdown(self) -> bool: + """This attribute controls power of the Pinnacle ASIC. `True` means powered down + (AKA standby mode), and `False` means not powered down (Active, Idle, or Sleep mode). + + .. note:: + The ASIC 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._rap_read(3) & 2) @shutdown.setter - def shutdown(self, is_off): + def shutdown(self, is_off: bool): self._rap_write(3, (self._rap_read(3) & 0xFD) | (is_off << 1)) @property - def sample_rate(self): - """This attribute controls how many samples (of data) per second are - reported.""" + def sample_rate(self) -> int: + """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). Optionally, ``200`` and + ``300`` sps can be specified, but using these values automatically disables palm (referred + to as "NERD" in the specification sheet) and noise compensations. These higher values are + meant for using a stylus with a 2mm diameter tip, while the values less than 200 are meant + for a finger or stylus with a 5.25mm diameter tip. + + This attribute only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` + or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode. Otherwise if + `data_mode` is set to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then + this attribute will have no effect. + """ return self._rap_read(9) @sample_rate.setter - def sample_rate(self, val): + def sample_rate(self, val: int): if self.data_mode != ANYMEAS: if val in (200, 300): # disable palm & noise compensations @@ -195,19 +405,64 @@ def sample_rate(self, val): val = val if val in (100, 80, 60, 40, 20, 10) else 100 self._rap_write(9, val) - def detect_finger_stylus(self, enable_finger=True, - enable_stylus=True, sample_rate=100): + def detect_finger_stylus( + self, + enable_finger: bool = True, + enable_stylus: bool = True, + sample_rate: int = 100, + ): """This function will configure the Pinnacle ASIC to detect either - finger, stylus, or both.""" + finger, stylus, or both. + + :param enable_finger: `True` enables the Pinnacle ASIC's measurements to + detect if the touch event was caused by a finger or 5.25mm stylus. `False` disables + this feature. Default is `True`. + :param enable_stylus: `True` enables the Pinnacle ASIC's measurements to + detect if the touch event was caused by a 2mm stylus. `False` disables this + feature. Default is `True`. + :param sample_rate: See the `sample_rate` attribute as this parameter manipulates that + attribute. + + .. tip:: + Consider adjusting the ADC matrix's gain to enhance performance/results using + `set_adc_gain()` + """ finger_stylus = self._era_read(0x00EB) finger_stylus |= (enable_stylus << 2) | enable_finger self._era_write(0x00EB, finger_stylus) self.sample_rate = sample_rate - def calibrate(self, run, tap=True, track_error=True, nerd=True, - background=True): + def calibrate( + self, + run: bool, + tap: bool = True, + track_error: bool = True, + nerd: bool = True, + background: bool = True, + ): """Set calibration parameters when the Pinnacle ASIC calibrates - itself.""" + itself. + + This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` + or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode. Otherwise if + `data_mode` is set to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this + function will have no effect. + + :param 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 tap: Enable dynamic tap compensation? Default is `True`. + :param track_error: Enable dynamic track error compensation? Default is `True`. + :param nerd: Enable dynamic NERD compensation? Default is `True`. This parameter has + something to do with palm detection/compensation. + :param 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. + """ if self.data_mode != ANYMEAS: cal_config = (tap << 4) | (track_error << 3) | (nerd << 2) cal_config |= background << 1 @@ -215,70 +470,223 @@ def calibrate(self, run, tap=True, track_error=True, nerd=True, if run: while self._rap_read(7) & 1: pass # calibration is running - self.clear_flags() # now that calibration is done + self.clear_status_flags() # now that calibration is done @property - def calibration_matrix(self): + def calibration_matrix(self) -> List[int]: """This attribute returns a `list` of the 46 signed 16-bit (short) values stored in the Pinnacle ASIC's memory that is used for taking - measurements.""" + measurements. + + This matrix is not applicable in AnyMeas mode. Use this attribute to compare a prior + compensation matrix with a new matrix that was either loaded manually by setting this + attribute to a `list` of 46 signed 16-bit (short) integers or created internally by calling + `calibrate()` with the ``run`` parameter as `True`. + + .. note:: + A paraphrased note from Cirque's Application Note on Comparing compensation matrices: + + If any 16-bit values are above 20K (absolute), it generally indicates a problem with + the sensor. If no values exceed 20K, proceed with the data comparison. Compare each + 16-bit value in one matrix to the corresponding 16-bit value in the other matrix. If + the difference between the two values is greater than 500 (absolute), it indicates a + change in the environment. Either an object was on the sensor during calibration, or + the surrounding conditions (temperature, humidity, or noise level) have changed. One + strategy is to force another calibration and compare again, if the values continue to + differ by 500, determine whether to use the new data or a previous set of stored data. + Another strategy is to average any two values that differ by more than 500 and write + this new matrix, with the average values, back into Pinnacle ASIC. + """ # combine every 2 bytes from resulting buffer into list of signed # 16-bits integers - return list(unpack('46h', self._era_read_bytes(0x01DF, 92))) + return list(struct.unpack("46h", self._era_read_bytes(0x01DF, 92))) @calibration_matrix.setter - def calibration_matrix(self, matrix): - matrix += [0] * (46 - len(matrix)) # padd short matrices w/ 0s + def calibration_matrix(self, matrix: List[int]): + matrix += [0] * (46 - len(matrix)) # pad short matrices w/ 0s for index in range(46): - buf = pack('h', matrix[index]) + buf = struct.pack("h", matrix[index]) self._era_write(0x01DF + index * 2, buf[0]) self._era_write(0x01DF + index * 2 + 1, buf[1]) - def set_adc_gain(self, sensitivity): + def set_adc_gain(self, sensitivity: int): """Sets the ADC gain in range [0,3] to enhance performance based on - the overlay type""" + the overlay type (does not apply to AnyMeas mode). + + :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 not 0 <= sensitivity < 4: raise ValueError("sensitivity is out of bounds [0,3]") val = self._era_read(0x0187) & 0x3F | (sensitivity << 6) self._era_write(0x0187, val) - def tune_edge_sensitivity(self, x_axis_wide_z_min=0x04, - y_axis_wide_z_min=0x03): - """Changes thresholds to improve detection of fingers.""" + def tune_edge_sensitivity( + self, x_axis_wide_z_min: int = 0x04, y_axis_wide_z_min: int = 0x03 + ): + """Changes thresholds to improve detection of fingers. + + .. warning:: + 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! + """ self._era_write(0x0149, x_axis_wide_z_min) self._era_write(0x0168, y_axis_wide_z_min) - def anymeas_mode_config(self, gain=GAIN_200, frequency=FREQ_0, - sample_length=512, mux_ctrl=MUX_PNP, - apperture_width=500, ctrl_pwr_cnt=1): + def anymeas_mode_config( + self, + gain: int = GAIN_200, + frequency: int = FREQ_0, + sample_length: int = 512, + mux_ctrl: int = MUX_PNP, + apperture_width: int = 500, + ctrl_pwr_cnt: int = 1, + ): """This function configures the Pinnacle ASIC to output raw ADC - measurements.""" + measurements. + + Be sure to set the `data_mode` attribute to + :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` before calling this function + otherwise it will do nothing. + + :param gain: Sets the sensitivity of the ADC matrix. Valid values are the constants + defined in `AnyMeas mode Gain`_. Defaults to + :attr:`~circuitpython_cirque_pinnacle.glidepoint.GAIN_200`. + :param frequency: Sets the frequency of measurements made by the ADC matrix. Valid + values are the constants defined in `AnyMeas mode Frequencies`_. + Defaults :attr:`~circuitpython_cirque_pinnacle.glidepoint.FREQ_0`. + :param sample_length: Sets the maximum bit length of the measurements made by the ADC + matrix. Valid values are ``128``, ``256``, or ``512``. Defaults to ``512``. + :param mux_ctrl: The Pinnacle ASIC can employ different bipolar junctions + and/or reference capacitors. Valid values are the constants defined in + `AnyMeas mode Muxing`_. Additional combination of + these constants is also allowed. Defaults to + :attr:`~circuitpython_cirque_pinnacle.glidepoint.MUX_PNP`. + :param apperture_width: Sets the window of time (in nanoseconds) to allow for the ADC + to take a measurement. Valid values are multiples of 125 in range [``250``, ``1875``]. + Erroneous values are clamped/truncated to this range. + + .. note:: The ``apperture_width`` parameter has a inverse relationship/affect on the + ``frequency`` parameter. The approximated frequencies described in this + documentation are based on an aperture width of 500 nanoseconds, and they will + shrink as the apperture width grows or grow as the aperture width shrinks. + + :param ctrl_pwr_cnt: Configure the Pinnacle to perform a number of measurements for + each call to `measure_adc()`. Defaults to 1. Constants defined in + `AnyMeas mode Control`_ can be used to specify if is sleep + is allowed (:attr:`~circuitpython_cirque_pinnacle.glidepoint.CRTL_PWR_IDLE` -- this + is not default) or if repetitive measurements is allowed + (:attr:`~circuitpython_cirque_pinnacle.glidepoint.CRTL_REPEAT`) if number of + measurements is more than 1. + + .. warning:: + There is no bounds checking on the number of measurements specified + here. Specifying more than 63 will trigger sleep mode after performing + measurements. + + .. tip:: + Be aware that allowing the Pinnacle to enter sleep mode after taking + measurements will slow consecutive calls to `measure_adc()` as the Pinnacle + requires about 300 milliseconds to wake up. + """ if self.data_mode == ANYMEAS: anymeas_config = [2, 3, 4, 0, 4, 0, 19, 0, 0, 1] anymeas_config[0] = gain | frequency - anymeas_config[1] = (max(1, min(int(sample_length / 128), 3))) + anymeas_config[1] = max(1, min(int(sample_length / 128), 3)) anymeas_config[2] = mux_ctrl anymeas_config[4] = max(2, min(int(apperture_width / 125), 15)) anymeas_config[9] = ctrl_pwr_cnt self._rap_write_bytes(5, anymeas_config) self._rap_write_bytes(0x13, [0] * 8) - self.clear_flags() + self.clear_status_flags() - def measure_adc(self, bits_to_toggle, toggle_polarity): + def measure_adc(self, bits_to_toggle: int, toggle_polarity: int) -> bytearray: """This blocking function instigates and returns the measurements (a - signed short) from the Pinnacle ASIC's ADC (Analog to Digital - Converter) matrix.""" + signed short) from the Pinnacle ASIC's ADC (Analog to Digital Converter) matrix. + + Internally this function calls `start_measure_adc()` and `get_measure_adc()` in sequence. + Be sure to set the `data_mode` attribute to + :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` before calling this function + otherwise it will do nothing. + + :Parameters' Context: + Each of the parameters are a 4-byte integer (see + :ref:`format table below `) in which each bit corresponds to a + capacitance sensing electrode in the sensor's matrix (12 electrodes for Y-axis, 16 + electrodes for X-axis). They are used to compensate for varying capacitances in + the electrodes during measurements. **It is highly recommended that the trackpad be + installed in a finished/prototyped housing when determining what electrodes to + manipulate.** See `AnyMeas mode example `_ to + understand how to use these 4-byte integers. + + :param bits_to_toggle: A bit of ``1`` flags that electrode's output for toggling, and a bit + of ``0`` signifies that the electrode's output should remain unaffected. + :param toggle_polarity: This specifies which polarity the output of the electrode(s) + (specified with corresponding bits in ``bits_to_toggle`` parameter) should be toggled + (forced). A bit of ``1`` toggles that bit positive, and a bit of ``0`` toggles that + bit negative. + + :Returns: + A 2-byte `bytearray` that represents a signed short integer. If `data_mode` is not set + to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this function returns + `None` and does nothing. + + .. _polynomial-fmt: + + :4-byte Integer Format: + Bits 31 & 30 are not used and should remain ``0``. Bits 29 and 28 represent the optional + implementation of reference capacitors built into the Pinnacle ASIC. To use these + capacitors, the corresponding constants + (:attr:`~circuitpython_cirque_pinnacle.glidepoint.MUX_REF0` and/or + :attr:`~circuitpython_cirque_pinnacle.glidepoint.MUX_REF1`) must be passed to + `anymeas_mode_config()` in the ``mux_ctrl`` parameter, and their representative + bits must be flagged in both ``bits_to_toggle`` & ``toggle_polarity`` parameters. + + .. csv-table:: byte 3 (MSByte) + :stub-columns: 1 + :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 + + "bit position",31,30,29,28,27,26,25,24 + "representation",N/A,N/A,Ref1,Ref0,Y11,Y10,Y9,Y8 + .. csv-table:: byte 2 + :stub-columns: 1 + :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 + + "bit position",23,22,21,20,19,18,17,16 + "representation",Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0 + .. csv-table:: byte 1 + :stub-columns: 1 + :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 + + "bit position",15,14,13,12,11,10,9,8 + "representation",X15,X14,X13,X12,X11,X10,X9,X8 + .. csv-table:: byte 0 (LSByte) + :stub-columns: 1 + :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 + + "bit position",7,6,5,4,3,2,1,0 + "representation",X7,X6,X5,X4,X3,X2,X1,X0 + """ self.start_measure_adc(bits_to_toggle, toggle_polarity) result = self.get_measure_adc() while result is None: # wait till measurements are complete result = self.get_measure_adc() # Pinnacle is still computing return result - def start_measure_adc(self, bits_to_toggle, toggle_polarity): + def start_measure_adc(self, bits_to_toggle: int, toggle_polarity: int): """A non-blocking function that starts measuring ADC values in - AnyMeas mode.""" + AnyMeas mode. + + See the parameters and table in `measure_adc()` as this is its helper function, and all + parameters there are used the same way here. + """ if self._mode == ANYMEAS: - tog_pol = [] # assemble list of register buffers + tog_pol: List[int] = [] # assemble list of register buffers for i in range(3, -1, -1): tog_pol.append((bits_to_toggle >> (i * 8)) & 0xFF) for i in range(3, -1, -1): @@ -288,91 +696,118 @@ def start_measure_adc(self, bits_to_toggle, toggle_polarity): # initiate measurements self._rap_write(3, self._rap_read(3) | 0x18) - def get_measure_adc(self): + def get_measure_adc(self) -> Optional[bytearray]: """A non-blocking function that returns ADC measurement on - completion.""" + completion. + + This function is only meant ot be used in conjunction with `start_measure_adc()` for + non-blocking application. + + :returns: + * `None` if `data_mode` is not set to `ANYMEAS` or if the "data ready" pin's signal is + not active (while `data_mode` is set to `ANYMEAS`) meaning the Pinnacle ASIC is still + computing the ADC measurements based on the 4-byte polynomials passed to + `start_measure_adc()`. + * a `bytearray` that represents a signed 16-bit integer upon completed ADC measurements + based on the 4-byte polynomials passed to `start_measure_adc()`. + """ if self._mode != ANYMEAS: return None - if not self.dr_pin.value: + if self.dr_pin is not None and not self.dr_pin.value: return None result = self._rap_read_bytes(0x11, 2) - self.clear_flags() + self.clear_status_flags() return result - def _rap_read(self, reg): + def _rap_read(self, reg: int) -> int: raise NotImplementedError() - def _rap_read_bytes(self, reg, numb_bytes): + def _rap_read_bytes(self, reg: int, numb_bytes: int) -> bytearray: raise NotImplementedError() - def _rap_write(self, reg, value): + def _rap_write(self, reg: int, value: int): raise NotImplementedError() - def _rap_write_bytes(self, reg, values): + def _rap_write_bytes(self, reg: int, values: List[int]): raise NotImplementedError() - def _era_read(self, reg): + def _era_read(self, reg: int) -> int: prev_feed_state = self.feed_enable self.feed_enable = False # accessing raw memory, so do this - self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xff]) + self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xFF]) self._rap_write(0x1E, 1) # indicate reading only 1 byte while self._rap_read(0x1E): # read until reg == 0 pass # also sets Command Complete flag in Status register buf = self._rap_read(0x1B) # get value - self.clear_flags() + self.clear_status_flags() self.feed_enable = prev_feed_state # resume previous feed state return buf - def _era_read_bytes(self, reg, numb_bytes): + def _era_read_bytes(self, reg: int, numb_bytes: int) -> bytes: buf = b"" prev_feed_state = self.feed_enable self.feed_enable = False # accessing raw memory, so do this - self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xff]) + self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xFF]) for _ in range(numb_bytes): self._rap_write(0x1E, 5) # indicate reading sequential bytes while self._rap_read(0x1E): # read until reg == 0 pass # also sets Command Complete flag in Status register buf += bytes([self._rap_read(0x1B)]) # get value - self.clear_flags() + self.clear_status_flags() self.feed_enable = prev_feed_state # resume previous feed state return buf - def _era_write(self, reg, value): + def _era_write(self, reg: int, value: int): prev_feed_state = self.feed_enable self.feed_enable = False # accessing raw memory, so do this self._rap_write(0x1B, value) # write value - self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xff]) + self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xFF]) self._rap_write(0x1E, 2) # indicate writing only 1 byte while self._rap_read(0x1E): # read until reg == 0 pass # also sets Command Complete flag in Status register - self.clear_flags() + self.clear_status_flags() self.feed_enable = prev_feed_state # resume previous feed state - def _era_write_bytes(self, reg, value, numb_bytes): + def _era_write_bytes(self, reg: int, value: int, numb_bytes: int): # rarely used as it only writes 1 value to multiple registers prev_feed_state = self.feed_enable self.feed_enable = False # accessing raw memory, so do this self._rap_write(0x1B, value) # write value - self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xff]) + self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xFF]) self._rap_write(0x1E, 0x0A) # indicate writing sequential bytes for _ in range(numb_bytes): while self._rap_read(0x1E): # read until reg == 0 pass # also sets Command Complete flag in Status register - self.clear_flags() + self.clear_status_flags() self.feed_enable = prev_feed_state # resume previous feed state + # pylint: disable=no-member class PinnacleTouchI2C(PinnacleTouch): """Parent class for interfacing with the Pinnacle ASIC via the I2C - protocol.""" - def __init__(self, i2c, address=0x2A, dr_pin=None): + 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 Pinnacle ASIC. Defaults to ``0x2A``. + :param ~digitalio.DigitalInOut dr_pin: |dr_pin_parameter| + + .. important:: |dr_pin_note| + """ + + def __init__( + self, + i2c: busio.I2C, + address: int = 0x2A, + dr_pin: Optional[digitalio.DigitalInOut] = None, + ): self._i2c = I2CDevice(i2c, address) - super(PinnacleTouchI2C, self).__init__(dr_pin=dr_pin) + super().__init__(dr_pin=dr_pin) - def _rap_read(self, reg): - return self._rap_read_bytes(reg, 1) + def _rap_read(self, reg: int) -> int: + return self._rap_read_bytes(reg, 1)[0] - def _rap_read_bytes(self, reg, numb_bytes): + def _rap_read_bytes(self, reg: int, numb_bytes: int) -> bytearray: buf = bytes([reg | 0xA0]) # per datasheet with self._i2c as i2c: i2c.write(buf) # includes a STOP condition @@ -381,10 +816,10 @@ def _rap_read_bytes(self, reg, numb_bytes): i2c.readinto(buf) return buf - def _rap_write(self, reg, value): + def _rap_write(self, reg: int, value: int): self._rap_write_bytes(reg, [value]) - def _rap_write_bytes(self, reg, values): + def _rap_write_bytes(self, reg: int, values: List[int]): buf = b"" for index, byte in enumerate(values): # Pinnacle doesn't auto-increment register @@ -394,22 +829,38 @@ def _rap_write_bytes(self, reg, values): with self._i2c as i2c: i2c.write(buf) + class PinnacleTouchSPI(PinnacleTouch): """Parent class for interfacing with the Pinnacle ASIC via the SPI - protocol.""" - def __init__(self, spi, ss_pin, spi_frequency=12000000, dr_pin=None): - self._spi = SPIDevice(spi, chip_select=ss_pin, phase=1, - baudrate=spi_frequency) - super(PinnacleTouchSPI, self).__init__(dr_pin=dr_pin) - - def _rap_read(self, reg): + 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 ~digitalio.DigitalInOut ss_pin: The "slave select" pin output to the Pinnacle ASIC. + :param int spi_frequency: The SPI bus speed in Hz. Default is 12 MHz. + :param ~digitalio.DigitalInOut dr_pin: |dr_pin_parameter| + + .. important:: |dr_pin_note| + """ + + def __init__( + self, + spi: busio.SPI, + ss_pin: digitalio.DigitalInOut, + spi_frequency: int = 12000000, + dr_pin: Optional[digitalio.DigitalInOut] = None, + ): + self._spi = SPIDevice(spi, chip_select=ss_pin, phase=1, baudrate=spi_frequency) + super().__init__(dr_pin=dr_pin) + + def _rap_read(self, reg: int) -> int: buf_out = bytes([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): + def _rap_read_bytes(self, reg: int, numb_bytes: int) -> bytearray: # using auto-increment method buf_out = bytes([reg | 0xA0]) + b"\xFC" * (1 + numb_bytes) + b"\xFB" buf_in = bytearray(len(buf_out)) @@ -417,11 +868,11 @@ def _rap_read_bytes(self, reg, numb_bytes): spi.write_readinto(buf_out, buf_in) return buf_in[3:] - def _rap_write(self, reg, value): + def _rap_write(self, reg: int, value: int): buf = bytes([(reg | 0x80), value]) with self._spi as spi: spi.write(buf) - def _rap_write_bytes(self, reg, values): + def _rap_write_bytes(self, reg: int, values: List[int]): for i, val in enumerate(values): self._rap_write(reg + i, val) diff --git a/circuitpython_cirque_pinnacle/glidepoint_lite.py b/circuitpython_cirque_pinnacle/glidepoint_lite.py index 4c04dbc..ca72243 100644 --- a/circuitpython_cirque_pinnacle/glidepoint_lite.py +++ b/circuitpython_cirque_pinnacle/glidepoint_lite.py @@ -3,14 +3,17 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle.git" import time +from micropython import const + try: from ubus_device import SPIDevice, I2CDevice except ImportError: from adafruit_bus_device.spi_device import SPIDevice from adafruit_bus_device.i2c_device import I2CDevice -RELATIVE = 0x00 -ABSOLUTE = 0x02 +RELATIVE = const(0x00) +ABSOLUTE = const(0x02) + class PinnacleTouch: def __init__(self, dr_pin=None): @@ -19,12 +22,12 @@ def __init__(self, dr_pin=None): self.dr_pin.switch_to_input() firmware_id, firmware_ver = self._rap_read_bytes(0, 2) if firmware_id != 7 or firmware_ver != 0x3A: - raise OSError("Cirque Pinnacle ASIC not responding") + raise RuntimeError("Cirque Pinnacle ASIC not responding") self._mode = 0 self.sample_rate = 100 self._rap_write(0x0A, 30) self._rap_write_bytes(3, [0, 1, 2]) - self.clear_flags() + self.clear_status_flags() @property def feed_enable(self): @@ -43,51 +46,51 @@ def data_mode(self): @data_mode.setter def data_mode(self, mode): - if mode not in (RELATIVE, ABSOLUTE): - raise ValueError("Unrecognised input value for data_mode.") - self._rap_write(4, 1 | mode) - self._mode = mode + self._mode = bool(mode) * 2 + self._rap_write(4, 1 | self._mode) @property def hard_configured(self): - return bool(self._rap_read(0x1f)) - - def relative_mode_config(self, rotate90=False, taps=True, - secondary_tap=True, glide_extend=True, - intellimouse=False): + return bool(self._rap_read(0x1F)) + + def relative_mode_config( + self, + rotate90=False, + taps=True, + secondary_tap=True, + glide_extend=True, + intellimouse=False, + ): config2 = (rotate90 << 7) | ((not glide_extend) << 4) config2 |= ((not secondary_tap) << 2) | ((not taps) << 1) self._rap_write(5, config2 | bool(intellimouse)) - def absolute_mode_config(self, z_idle_count=30, - invert_x=False, invert_y=False): + def absolute_mode_config(self, z_idle_count=30, invert_x=False, invert_y=False): self._rap_write(0x0A, max(0, min(z_idle_count, 255))) config1 = self._rap_read(4) & 0x3F | (invert_y << 7) self._rap_write(4, config1 | (invert_x << 6)) - def report(self, only_new=True): + def available(self): + if self.dr_pin is None: + return bool(self._rap_read(2) & 4) + return self.dr_pin.value + + def read(self): return_vals = None - data_ready = False - if only_new: - if self.dr_pin is None: - data_ready = self._rap_read(2) & 4 - else: - data_ready = self.dr_pin.value - if (only_new and data_ready) or not only_new: - if self.data_mode == ABSOLUTE: - return_vals = list(self._rap_read_bytes(0x12, 6)) - return_vals[0] &= 0x3F - return_vals[2] |= (return_vals[4] & 0x0F) << 8 - return_vals[3] |= (return_vals[4] & 0xF0) << 4 - return_vals[5] &= 0x3F - del return_vals[4], return_vals[1] - elif self.data_mode == RELATIVE: - return_vals = self._rap_read_bytes(0x12, 4) - return_vals[0] &= 7 - self.clear_flags() + if self.data_mode == ABSOLUTE: + return_vals = list(self._rap_read_bytes(0x12, 6)) + return_vals[0] &= 0x3F + return_vals[2] |= (return_vals[4] & 0x0F) << 8 + return_vals[3] |= (return_vals[4] & 0xF0) << 4 + return_vals[5] &= 0x3F + del return_vals[4], return_vals[1] + elif self.data_mode == RELATIVE: + return_vals = self._rap_read_bytes(0x12, 4) + return_vals[0] &= 7 + self.clear_status_flags() return return_vals - def clear_flags(self): + def clear_status_flags(self): self._rap_write(2, 0) time.sleep(0.00005) @@ -97,7 +100,7 @@ def allow_sleep(self): @allow_sleep.setter def allow_sleep(self, is_enabled): - self._rap_write(3, (self._rap_read(3) & 0xFB) | (is_enabled << 2)) + self._rap_write(3, (self._rap_read(3) & 0xFB) | (bool(is_enabled) << 2)) @property def shutdown(self): @@ -105,7 +108,7 @@ def shutdown(self): @shutdown.setter def shutdown(self, is_off): - self._rap_write(3, (self._rap_read(3) & 0xFD) | (is_off << 1)) + self._rap_write(3, (self._rap_read(3) & 0xFD) | (bool(is_off) << 1)) @property def sample_rate(self): @@ -141,26 +144,26 @@ def _rap_write_bytes(self, reg, values): def _era_read(self, reg): prev_feed_state = self.feed_enable self.feed_enable = False - self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xff]) + self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xFF]) self._rap_write(0x1E, 1) while self._rap_read(0x1E): pass buf = self._rap_read(0x1B) - self.clear_flags() + self.clear_status_flags() self.feed_enable = prev_feed_state return buf def _era_read_bytes(self, reg, numb_bytes): - buf = b'' + buf = b"" prev_feed_state = self.feed_enable self.feed_enable = False - self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xff]) + self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xFF]) for _ in range(numb_bytes): self._rap_write(0x1E, 5) while self._rap_read(0x1E): pass buf += bytes([self._rap_read(0x1B)]) - self.clear_flags() + self.clear_status_flags() self.feed_enable = prev_feed_state return buf @@ -168,18 +171,19 @@ def _era_write(self, reg, value): prev_feed_state = self.feed_enable self.feed_enable = False self._rap_write(0x1B, value) - self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xff]) + self._rap_write_bytes(0x1C, [reg >> 8, reg & 0xFF]) self._rap_write(0x1E, 2) while self._rap_read(0x1E): pass - self.clear_flags() + self.clear_status_flags() self.feed_enable = prev_feed_state + # pylint: disable=no-member class PinnacleTouchI2C(PinnacleTouch): def __init__(self, i2c, address=0x2A, dr_pin=None): self._i2c = I2CDevice(i2c, address) - super(PinnacleTouchI2C, self).__init__(dr_pin=dr_pin) + super().__init__(dr_pin=dr_pin) def _rap_read(self, reg): return self._rap_read_bytes(reg, 1) @@ -202,11 +206,11 @@ def _rap_write_bytes(self, reg, values): with self._i2c as i2c: i2c.write(buf) + class PinnacleTouchSPI(PinnacleTouch): def __init__(self, spi, ss_pin, spi_frequency=12000000, dr_pin=None): - self._spi = SPIDevice(spi, chip_select=ss_pin, phase=1, - baudrate=spi_frequency) - super(PinnacleTouchSPI, self).__init__(dr_pin=dr_pin) + self._spi = SPIDevice(spi, chip_select=ss_pin, phase=1, baudrate=spi_frequency) + super().__init__(dr_pin=dr_pin) def _rap_read(self, reg): buf_out = bytes([reg | 0xA0]) + b"\xFB" * 3 diff --git a/docs/_static/Logo.png b/docs/_static/Logo.png new file mode 100644 index 0000000..79a714f Binary files /dev/null and b/docs/_static/Logo.png differ diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css deleted file mode 100644 index 5ad759e..0000000 --- a/docs/_static/darkness.css +++ /dev/null @@ -1,334 +0,0 @@ -/* -----------------------------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; -} - -.highlight .go { - color: #1afd00; - } - -/* ----------------------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: #3e3e3e; - 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; -} - -.wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand { - color: #f2f2f2; -} - -.wy-menu-vertical li span.toctree-expand { - color: #f2f2f2; -} - -.wy-menu-vertical li.current { - background: #034e69; -} - -.wy-menu-vertical li.toctree-l2 a, -.wy-menu-vertical li.toctree-l3 a, -.wy-menu-vertical li.toctree-l4 a { - color:#f2f2f2; -} - -.wy-menu-vertical li.current a:hover { - background:#3e3e3ecf; -} - -.wy-menu-vertical li.toctree-l2.current>a { - background:#242424; -} - -.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { - background:#033969; -} - -.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a { - background:#092d21; -} - -/* -----------------------------------------API sections-------------------------------------- */ -.wy-table caption, -.rst-content table.docutils caption, -.rst-content table.field-list caption { - color:#fff; - text-align: left; -} - -.rst-content table.docutils.citation tt, -.rst-content table.docutils.citation code, -.rst-content table.docutils.footnote tt, -.rst-content table.docutils.footnote code { - color:#16ff00; -} - -.wy-table-bordered-all td, .rst-content table.docutils td { - text-align: left; -} - -.rst-content table.docutils.citation, .rst-content table.docutils.footnote { - color: white; -} - -.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/extra_css.css b/docs/_static/extra_css.css new file mode 100644 index 0000000..2b55bc1 --- /dev/null +++ b/docs/_static/extra_css.css @@ -0,0 +1,10 @@ + +th { + background-color: var(--md-default-fg-color--lightest); +} + +.md-nav.md-nav--primary > label { + white-space: normal; + line-height: inherit; + padding-top: 3.5rem; +} diff --git a/docs/about_lite.rst b/docs/about_lite.rst new file mode 100644 index 0000000..b2781c1 --- /dev/null +++ b/docs/about_lite.rst @@ -0,0 +1,18 @@ + +About the lite version +====================== + +This library includes a "lite" version of the module ``glidepoint.py`` titled ``glidepoint_lite.py``. +The lite version is limited to only Relative and Absolute data modes. It has been developed to +save space on microcontrollers with limited amount of RAM and/or storage (like boards using the +ATSAMD21 M0). The following functionality has been removed from the lite version: + +* `anymeas_mode_config()` +* `measure_adc()` +* `detect_finger_stylus()` +* `calibrate()` +* `calibration_matrix` +* `set_adc_gain()` +* `tune_edge_sensitivity()` +* ``_era_write_bytes()`` (private member for accessing the Pinnacle ASIC's memory) +* all comments and docstrings (meaning ``help()`` will provide no specific information) diff --git a/docs/anymeas.rst b/docs/anymeas.rst new file mode 100644 index 0000000..796ad88 --- /dev/null +++ b/docs/anymeas.rst @@ -0,0 +1,95 @@ + +AnyMeas mode API +================ + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.anymeas_mode_config + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.measure_adc + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.start_measure_adc + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.get_measure_adc + +AnyMeas mode Gain +----------------- + +Allowed ADC gain configurations of AnyMeas mode. The percentages defined here are approximate +values. + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.GAIN_100 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.GAIN_133 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.GAIN_166 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.GAIN_200 + :no-value: + +AnyMeas mode Frequencies +------------------------ + +Allowed frequency configurations of AnyMeas mode. The frequencies defined here are +approximated based on an aperture width of 500 nanoseconds. If the ``aperture_width`` +parameter to `anymeas_mode_config()` specified is less than 500 nanoseconds, then the +frequency will be larger than what is described here (& vice versa). + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_0 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_1 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_2 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_3 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_4 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_5 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_6 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.FREQ_7 + :no-value: + +AnyMeas mode Muxing +------------------- + +Allowed muxing gate polarity and reference capacitor configurations of AnyMeas mode. +Combining these values (with ``+`` operator) is allowed. + +.. note:: + The sign of the measurements taken in AnyMeas mode is inverted depending on which + muxing gate is specified (when specifying an individual gate polarity). + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.MUX_REF1 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.MUX_REF0 + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.MUX_PNP + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.MUX_NPN + :no-value: + +AnyMeas mode Control +-------------------- + +These constants control the number of measurements performed in `measure_adc()`. +The number of measurements can range [0, 63]. + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.CRTL_REPEAT + :no-value: + +.. autodata:: circuitpython_cirque_pinnacle.glidepoint.CRTL_PWR_IDLE + :no-value: diff --git a/docs/api.rst b/docs/api.rst index 9dd6aef..8804a5b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,644 +1,43 @@ -.. currentmodule:: circuitpython_cirque_pinnacle - -About the lite version -====================== - -This library includes a "lite" version of the module ``glidepoint.py`` titled ``glidepoint_lite.py``. -The lite version is limited to only Relative and Absolute data modes. It has been developed to -save space on microcontrollers with limited amount of RAM and/or storage (like boards using the -ATSAMD21 M0). The following functionality has been removed from the lite version: - - * `anymeas_mode_config()` - * `measure_adc()` - * `detect_finger_stylus()` - * `calibrate()` - * `calibration_matrix` - * `set_adc_gain()` - * `tune_edge_sensitivity()` - * ``_era_write_bytes()`` (private member for accessing the Pinnacle ASIC's memory) - * all comments and docstrings (meaning ``help()`` will provide no specific information) - PinnacleTouch API ================== -Accepted Constants ------------------- - Data Modes -*********** - Allowed symbols for configuring the Pinanacle ASIC's data reporting/measurements. - - .. data:: circuitpython_cirque_pinnacle.glidepoint.RELATIVE - :annotation: =0 - - Alias symbol for specifying Relative mode (AKA Mouse mode). - - .. data:: circuitpython_cirque_pinnacle.glidepoint.ANYMEAS - :annotation: =1 - - Alias symbol for specifying "AnyMeas" mode (raw ADC measurement) - - .. data:: circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE - :annotation: =2 - - Alias symbol for specifying Absolute mode (axis positions) - -AnyMeas mode Gain -****************** - - Allowed ADC gain configurations of AnyMeas mode. The percentages defined here are approximate - values. - - .. data:: circuitpython_cirque_pinnacle.glidepoint.GAIN_100 - - around 100% gain - - .. data:: circuitpython_cirque_pinnacle.glidepoint.GAIN_133 - - around 133% gain - - .. data:: circuitpython_cirque_pinnacle.glidepoint.GAIN_166 - - around 166% gain - - .. data:: circuitpython_cirque_pinnacle.glidepoint.GAIN_200 - - around 200% gain - - -AnyMeas mode Frequencies -************************ - - Allowed frequency configurations of AnyMeas mode. The frequencies defined here are - approximated based on an aperture width of 500 nanoseconds. If the ``aperture_width`` - parameter to `anymeas_mode_config()` specified is less than 500 nanoseconds, then the - frequency will be larger than what is described here (& vice versa). - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_0 - - frequency around 500,000Hz - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_1 - - frequency around 444,444Hz - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_2 - - frequency around 400,000Hz - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_3 - - frequency around 363,636Hz - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_4 - - frequency around 333,333Hz - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_5 - - frequency around 307,692Hz - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_6 - - frequency around 267,000Hz - - .. data:: circuitpython_cirque_pinnacle.glidepoint.FREQ_7 - - frequency around 235,000Hz - - -AnyMeas mode Muxing -******************* +---------- - Allowed muxing gate polarity and reference capacitor configurations of AnyMeas mode. - Combining these values (with ``+`` operator) is allowed. +Allowed symbols for configuring the Pinnacle ASIC's data reporting/measurements. - .. note:: The sign of the measurements taken in AnyMeas mode is inverted depending on which - muxing gate is specified (when specifying an individual gate polarity). +.. data:: circuitpython_cirque_pinnacle.glidepoint.RELATIVE + :annotation: = 0 - .. data:: circuitpython_cirque_pinnacle.glidepoint.MUX_REF1 + Alias symbol for specifying Relative mode (AKA Mouse mode). - enables a builtin capacitor (~0.5pF). See note in `measure_adc()` +.. data:: circuitpython_cirque_pinnacle.glidepoint.ANYMEAS + :annotation: = 1 - .. data:: circuitpython_cirque_pinnacle.glidepoint.MUX_REF0 + Alias symbol for specifying "AnyMeas" mode (raw ADC measurement) - enables a builtin capacitor (~0.25pF). See note in `measure_adc()` +.. data:: circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE + :annotation: = 2 - .. data:: circuitpython_cirque_pinnacle.glidepoint.MUX_PNP + Alias symbol for specifying Absolute mode (axis positions) - enable PNP sense line - .. data:: circuitpython_cirque_pinnacle.glidepoint.MUX_NPN +PinnacleTouch class +------------------- - enable NPN sense line - - -AnyMeas mode Control -******************** - - These constants control the number of measurements performed in `measure_adc()`. - The number of measurements can range [0, 63]. - - .. data:: circuitpython_cirque_pinnacle.glidepoint.CRTL_REPEAT - - required for more than 1 measurement - - .. data:: circuitpython_cirque_pinnacle.glidepoint.CRTL_PWR_IDLE - - triggers low power mode (sleep) after completing measurements - - -PinnacleTouch -------------- - -.. |dr_pin_parameter| replace:: The input pin connected to the Pinnacle ASIC's "Data - Ready" pin. If this parameter is not specified, then the SW_DR (software data ready) flag - of the STATUS register is used to detirmine if the data being reported is new. - -.. |dr_pin_note| replace:: This parameter must be specified if your application is going to use the - Pinnacle ASIC's :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` - mode (a rather experimental measuring of raw ADC values). - - -Constructor -************************* - - .. autoclass:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch - :no-members: - - :param ~microcontroller.Pin dr_pin: |dr_pin_parameter| - - .. important:: |dr_pin_note| - -data_mode -************************* +.. autoclass:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch + :no-members: .. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.data_mode - Valid input values are :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` for - relative/mouse mode, :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` for - absolute positioning mode, or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` - (referred to as "AnyMeas" in specification sheets) mode for reading ADC values. - - :Returns: - - - ``0`` for Relative mode (AKA mouse mode) - - ``1`` for AnyMeas mode (raw ADC measurements) - - ``2`` for Absolute mode (X & Y axis positions) - - .. important:: When switching from :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` or - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` all configurations are reset, and - must be re-configured by using `absolute_mode_config()` or `relative_mode_config()`. - -Relative or Absolute mode -************************* - -feed_enable -^^^^^^^^^^^^^^^^^^^^^^^ - - .. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.feed_enable - - This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` - or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode, otherwise if `data_mode` is set to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this function will do nothing. - -hard_configured -^^^^^^^^^^^^^^^^^^^^^^^ - - .. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.hard_configured - - See note about product labeling in `Model Labeling Scheme `_. (read only) - - :Returns: - `True` if a 470K ohm resistor is populated at the junction labeled "R4" - -relative_mode_config() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.relative_mode_config - - (write only) - - This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` - mode, otherwise if `data_mode` is set to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` or - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE`, then this function does nothing. - - :param bool rotate90: Specifies if the axis data is altered for 90 degree rotation before - reporting it (essentially swaps the axis data). Default is `False`. - :param bool taps: Specifies if all taps should be reported (`True`) or not - (`False`). Default is `True`. This affects ``secondary_tap`` option as well. - :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 - always disabled if `hard_configured` is `True`. - :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 always disabled if `hard_configured` is `True`. - :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 always - disabled if `hard_configured` is `True`. - -absolute_mode_config() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.absolute_mode_config - - (write only) - - This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` - mode, otherwise if `data_mode` is set to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` or - :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE`, then this function does nothing. - - :param int z_idle_count: Specifies the number of empty packets (x-axis, y-axis, and z-axis - are ``0``) reported (every 10 milliseconds) when there is no touch detected. Defaults - to 30. This number is clamped to range [0, 255]. - :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`. - -report() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.report - - This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` - or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode, otherwise if `data_mode` is set to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this function returns `None` and does nothing. - - :param bool only_new: This parameter can be used to ensure the data reported is only new - data. Otherwise the data returned can be either old data or new data. If the ``dr_pin`` - parameter is specified upon instantiation, then the specified input pin is used to - detect if the data is new. Otherwise the SW_DR flag in the STATUS register is used to - detirmine if the data is new. - - :Returns: `None` if the ``only_new`` parameter is set `True` and there is no new data to - report. Otherwise, a `list` or `bytearray` of parameters that describe the (touch or - button) event. The structure is as follows: - - .. list-table:: - :header-rows: 1 - :widths: 1, 5, 5 - - * - Index - - Relative (Mouse) mode - - as a `bytearray` - - Absolute Mode - - as a `list` - * - 0 - - Button Data [1]_ - - one unsigned byte - - Button Data [1]_ - - one unsigned byte - * - 1 - - change in x-axis [2]_ - - -128 |LessEq| X |LessEq| 127 - - x-axis Position - - 0 |LessEq| X |LessEq| 2047 - * - 2 - - change in y-axis [2]_ - - -128 |LessEq| Y |LessEq| 127 - - y-axis Position - - 0 |LessEq| Y |LessEq| 1535 - * - 3 - - change in scroll wheel - - -128 |LessEq| SCROLL |LessEq| 127 [3]_ - - z-axis Magnitude - - .. [1] The returned button data is a byte in which each bit represents a button. - The bit to button order is as follows: - - 0. [LSB] Button 1 (thought of as Left button in Relative/Mouse mode). If ``taps`` - parameter is passed as `True` when calling `relative_mode_config()`, a single - tap will be reflected here. - 1. Button 2 (thought of as Right button in Relative/Mouse mode). If ``taps`` and - ``secondary_tap`` parameters are passed as `True` when calling `relative_mode_config()`, - a single tap in the perspective top-left-most corner will be reflected here (secondary - taps are constantly disabled if `hard_configured` returns `True`). Note that the - top-left-most corner can be perspectively moved if ``rotate90`` parameter is passed as - `True` when calling `relative_mode_config()`. - 2. Button 3 (thought of as Middle or scroll wheel button in Relative/Mouse mode) - .. [2] The axis data reported in Relative/Mouse mode is in two's - comliment form. Use Python's :py:func:`struct.unpack()` to convert the - data into integer form (see `Simple Test example `_ - for how to use this function). - - The axis data reported in Absolute mode is always positive as the - xy-plane's origin is located to the top-left, unless ``invert_x`` or ``invert_y`` - parameters to `absolute_mode_config()` are manipulated to change the perspective - location of the origin. - .. [3] In Relative/Mouse mode the scroll wheel data is only reported if the - ``intellimouse`` parameter is passed as `True` to `relative_mode_config()`. - Otherwise this is an empty byte as the - returned `bytearray` follows the buffer structure of a mouse HID report (see - `USB Mouse example `_). - .. |LessEq| unicode:: U+2264 - -clear_flags() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.clear_flags - -allow_sleep -^^^^^^^^^^^^^^^^^^^^^^^ - - .. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.allow_sleep - - Set this attribute to `True` if you want the Pinnacle ASIC to enter sleep (low power) - mode after about 5 seconds of inactivity (does not apply to AnyMeas mode). 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). - -shutdown -^^^^^^^^^^^^^^^^^^^^^^^ - - .. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.shutdown - - `True` means powered down (AKA standby mode), and `False` means not powered down - (Active, Idle, or Sleep mode). - - .. note:: The ASIC 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. - -sample_rate -^^^^^^^^^^^^^^^^^^^^^^^ - - .. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.sample_rate - - Valid values are ``100``, ``80``, ``60``, ``40``, ``20``, ``10``. Any other input values - automatically set the sample rate to 100 sps (samples per second). Optionally, ``200`` and - ``300`` sps can be specified, but using these values automatically disables palm (referred - to as "NERD" in the specification sheet) and noise compensations. These higher values are - meant for using a stylus with a 2mm diameter tip, while the values less than 200 are meant - for a finger or stylus with a 5.25mm diameter tip. - - This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` - or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode, otherwise if `data_mode` is set to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this function will do nothing. - -detect_finger_stylus() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.detect_finger_stylus - - :param bool enable_finger: `True` enables the Pinnacle ASIC's measurements to - detect if the touch event was caused by a finger or 5.25mm stylus. `False` disables - this feature. Default is `True`. - :param bool enable_stylus: `True` enables the Pinnacle ASIC's measurements to - detect if the touch event was caused by a 2mm stylus. `False` disables this - feature. Default is `True`. - :param int sample_rate: See the `sample_rate` attribute as this parameter manipulates that - attribute. - - .. tip:: Consider adjusting the ADC matrix's gain to enhance performance/results using - `set_adc_gain()` - -calibrate() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.calibrate - - This function only applies to :attr:`~circuitpython_cirque_pinnacle.glidepoint.RELATIVE` - or :attr:`~circuitpython_cirque_pinnacle.glidepoint.ABSOLUTE` mode, otherwise if `data_mode` is set to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this function will do nothing. - - :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. - -calibration_matrix -^^^^^^^^^^^^^^^^^^^^^^^ - - .. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.calibration_matrix - - This matrix is not applicable in AnyMeas mode. Use this attribute to compare a prior - compensation matrix with a new matrix that was either loaded manually by setting this - attribute to a `list` of 46 signed 16-bit (short) integers or created internally by calling - `calibrate()` with the ``run`` parameter as `True`. - - .. note:: A paraphrased note from Cirque's Application Note on Comparing compensation - matrices: - - If any 16-bit values are above 20K (absolute), it generally indicates a problem with - the sensor. If no values exceed 20K, proceed with the data comparison. Compare each - 16-bit value in one matrix to the corresponding 16-bit value in the other matrix. If - the difference between the two values is greater than 500 (absolute), it indicates a - change in the environment. Either an object was on the sensor during calibration, or - the surrounding conditions (temperature, humidity, or noise level) have changed. One - strategy is to force another calibration and compare again, if the values continue to - differ by 500, determine whether to use the new data or a previous set of stored data. - Another strategy is to average any two values that differ by more than 500 and write - this new matrix, with the average values, back into Pinnacle ASIC. - -set_adc_gain() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.set_adc_gain - - (does not apply to AnyMeas mode). (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``. - -tune_edge_sensitivity() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.tune_edge_sensitivity - - 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! - -AnyMeas mode -************* - -anymeas_mode_config() -^^^^^^^^^^^^^^^^^^^^^^^ - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.anymeas_mode_config - - Be sure to set the `data_mode` attribute to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` before calling this function - otherwise it will do nothing. - - :param int gain: Sets the sensitivity of the ADC matrix. Valid values are the constants - defined in `AnyMeas mode Gain`_. Defaults to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.GAIN_200`. - :param int frequency: Sets the frequency of measurements made by the ADC matrix. Valid - values are the constants defined in - `AnyMeas mode Frequencies`_. - Defaults :attr:`~circuitpython_cirque_pinnacle.glidepoint.FREQ_0`. - :param int sample_length: Sets the maximum bit length of the measurements made by the ADC - matrix. Valid values are ``128``, ``256``, or ``512``. Defaults to ``512``. - :param int mux_ctrl: The Pinnacle ASIC can employ different bipolar junctions - and/or reference capacitors. Valid values are the constants defined in - `AnyMeas mode Muxing`_. Additional combination of - these constants is also allowed. Defaults to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.MUX_PNP`. - :param int apperture_width: Sets the window of time (in nanoseconds) to allow for the ADC - to take a measurement. Valid values are multiples of 125 in range [``250``, ``1875``]. - Erroneous values are clamped/truncated to this range. - - .. note:: The ``apperture_width`` parameter has a inverse relationship/affect on the - ``frequency`` parameter. The approximated frequencies described in this - documentation are based on an aperture width of 500 nanoseconds, and they will - shrink as the apperture width grows or grow as the aperture width shrinks. - - :param int ctrl_pwr_cnt: Configure the Pinnacle to perform a number of measurements for - each call to `measure_adc()`. Defaults to 1. Constants defined in - `AnyMeas mode Control`_ can be used to specify if is sleep - is allowed (:attr:`~circuitpython_cirque_pinnacle.glidepoint.CRTL_PWR_IDLE` -- this - is not default) or if repetive measurements is allowed ( - :attr:`~circuitpython_cirque_pinnacle.glidepoint.CRTL_REPEAT`) if number of - measurements is more than 1. - - .. warning:: There is no bounds checking on the number of measurements specified - here. Specifying more than 63 will trigger sleep mode after performing - measuements. - - .. tip:: Be aware that allowing the Pinnacle to enter sleep mode after taking - measurements will slow consecutive calls to `measure_adc()` as the Pinnacle - requires about 300 milliseconds to wake up. - -measure_adc() -^^^^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.measure_adc - - Internally this function calls `start_measure_adc()` and `get_measure_adc()` in sequence. - Be sure to set the `data_mode` attribute to - :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS` before calling this function - otherwise it will do nothing. - - :parameters: - Each of the parameters are a 4-byte integer (see format table below) in which each bit - corresponds to a capacitance sensing eletrode in the sensor's matrix (12 electrodes for - Y-axis, 16 electrodes for X-axis). They are used to compensate for varying capacitances in - the electrodes during measurements. **It is highly recommended that the trackpad be - installed in a finished/prototyped housing when determining what electrodes to - manipulate.** See `AnyMeas mode example `_ to - understand how to use these 4-byte integers. - - * bits_to_toggle (`int `_) - A bit - of ``1`` flags that electrode's ouput for toggling, and a bit of ``0`` signifies that - the electrode's output should remain unaffected. - * toggle_polarity (`int `_) - This - specifies which polarity the output of the electrode(s) (specified with corresponding - bits in ``bits_to_toggle`` parameter) should be toggled (forced). A bit of ``1`` toggles - that bit positve, and a bit of ``0`` toggles that bit negative. - - :Returns: - A 2-byte `bytearray` that represents a signed short integer. If `data_mode` is not set - to :attr:`~circuitpython_cirque_pinnacle.glidepoint.ANYMEAS`, then this function returns - `None` and does nothing. - - :4-byte Integer Format: - Bits 31 & 30 are not used and should remain ``0``. Bits 29 and 28 represent the optional - implementation of reference capacitors built into the Pinnacle ASIC. To use these - capacitors, the corresponding constants - (:attr:`~circuitpython_cirque_pinnacle.glidepoint.AnyMeasMux.MUX_REF0` and/or - :attr:`~circuitpython_cirque_pinnacle.glidepoint.AnyMeasMux.MUX_REF1`) must be passed to - `anymeas_mode_config()` in the ``mux_ctrl`` parameter, and their representative - bits must be flagged in both ``bits_to_toggle`` & ``toggle_polarity`` parameters. - - .. csv-table:: byte 3 (MSByte) - :stub-columns: 1 - :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 - - "bit position",31,30,29,28,27,26,25,24 - "representation",N/A,N/A,Ref1,Ref0,Y11,Y10,Y9,Y8 - .. csv-table:: byte 2 - :stub-columns: 1 - :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 - - "bit position",23,22,21,20,19,18,17,16 - "representation",Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0 - .. csv-table:: byte 1 - :stub-columns: 1 - :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 - - "bit position",15,14,13,12,11,10,9,8 - "representation",X15,X14,X13,X12,X11,X10,X9,X8 - .. csv-table:: byte 0 (LSByte) - :stub-columns: 1 - :widths: 10, 5, 5, 5, 5, 5, 5, 5, 5 - - "bit position",7,6,5,4,3,2,1,0 - "representation",X7,X6,X5,X4,X3,X2,X1,X0 - -start_measure_adc() -^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.start_measure_adc - - See the parameters and table in `measure_adc()` as this is its helper function, and all - parameters there are used the same way here. - -get_measure_adc() -^^^^^^^^^^^^^^^^^^^^ - - .. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.get_measure_adc - - This function is only meant ot be used in conjunction with `start_measure_adc()` for - non-blocking application. - - :returns: - * `None` if `data_mode` is not set to `ANYMEAS` or if the "data ready" pin's signal is not - active (while `data_mode` is set to `ANYMEAS`) meaing the Pinnacle ASIC is still computing - the ADC measurements based on the 4-byte polynomials passed to `start_measure_adc()`. - * a `bytearray` that represents a signed 16-bit integer upon completed ADC measurements based - on the 4-byte polynomials passed to `start_measure_adc()`. - - SPI & I2C Interfaces -******************** - - .. autoclass:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouchSPI - :members: - :show-inheritance: - - :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 Pinnacle ASIC. - :param int spi_frequency: The SPI bus speed in Hz. Default is 12 MHz. - :param ~microcontroller.Pin dr_pin: |dr_pin_parameter| - - .. important:: |dr_pin_note| - - .. autoclass:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouchI2C - :members: - :show-inheritance: - +-------------------- - :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 Pinnacle ASIC. Defaults to ``0x2A``. - :param ~microcontroller.Pin dr_pin: |dr_pin_parameter| +.. autoclass:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouchSPI + :members: + :show-inheritance: - .. important:: |dr_pin_note| +.. autoclass:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouchI2C + :members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index db5d9d6..82f4daa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,171 +1,298 @@ -# -*- 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.7', 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 = 'CircuitpythonCirquePinnacleLibrarydoc' - -# -- 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, 'CircuitPythonCirquePinnacleLibrary.tex', u'CircuitPython Cirque Pinnacle 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, 'CircuitPythonCirquePinnacleLibrary', u'CircuitPython Cirque Pinnacle 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, 'CircuitpythonCirquePinnacleLibrary', u'Circuitpython Cirque Pinnacle Library Documentation', - author, 'CircuitpythonCirquePinnacleLibrary', 'CircuitPython Library for Cirque Pinnacle touch Controller.', - 'Miscellaneous'), -] +# pylint: disable=invalid-name,too-few-public-methods +"""This file is for `sphinx-build` configuration""" +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", + "sphinx_immaterial", + # "rst2pdf.pdfbuilder", # for local pdf builder support +] + +# 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", "usb_hid", "microcontroller"] +autodoc_member_order = "bysource" + +intersphinx_mapping = { + "python": ("https://docs.python.org/3.7", 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 = "Circuitpython Cirque Pinnacle Library" +copyright = "2020 Brendan Doherty" # pylint: disable=redefined-builtin +author = "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 = "dev" +# The full version, including alpha/beta/rc tags. +release = "1.0" + +html_baseurl = "https://circuitpython-cirque-pinnacle.readthedocs.io/" + +# 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 = "en" + +# 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", + "requirements.txt", +] + +# 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 + +# 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. +html_theme = "sphinx_immaterial" + +html_theme_options = { + "features": [ + "search.share", + ], + # Set the color and the accent color + "palette": [ + { + "media": "(prefers-color-scheme: light)", + "scheme": "default", + "primary": "green", + "accent": "light-blue", + "toggle": { + "icon": "material/lightbulb-outline", + "name": "Switch to dark mode", + }, + }, + { + "media": "(prefers-color-scheme: dark)", + "scheme": "slate", + "primary": "green", + "accent": "light-blue", + "toggle": { + "icon": "material/lightbulb", + "name": "Switch to light mode", + }, + }, + ], + # Set the repo location to get a badge with stats + "repo_url": "https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle/", + "repo_name": "CircuitPython_Cirque_Pinnacle", + "social": [ + { + "icon": "fontawesome/brands/github", + "link": "https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle", + }, + { + "icon": "fontawesome/brands/python", + "link": "https://pypi.org/project/circuitpython-cirque-pinnacle/", + }, + { + "icon": "fontawesome/brands/discord", + "link": "https://adafru.it/discord", + }, + { + "icon": "simple/adafruit", + "link": "https://www.adafruit.com/", + }, + { + "icon": "simple/sparkfun", + "link": "https://www.sparkfun.com/", + }, + { + "name": "CircuitPython Downloads", + "icon": "octicons/download-24", + "link": "https://circuitpython.org", + }, + ], +} + +sphinx_immaterial_custom_admonitions = [ + { + "name": "warning", + "color": (255, 66, 66), + "icon": "octicons/alert-24", + "override": True, + }, + { + "name": "note", + "icon": "octicons/pencil-24", + "override": True, + }, + { + "name": "seealso", + "color": (255, 66, 252), + "icon": "octicons/eye-24", + "title": "See Also", + "override": True, + }, + { + "name": "hint", + "icon": "material/school", + "override": True, + }, + { + "name": "tip", + "icon": "material/school", + "override": True, + }, + { + "name": "important", + "icon": "material/school", + "override": True, + }, +] + +python_type_aliases = { + "DigitalInOut": "digitalio.DigitalInOut", +} + +object_description_options = [ + ("py:.*", dict(generate_synopses="first_sentence")), +] + +# Set link name generated in the top bar. +html_title = "CircuitPython Cirque Pinnacle" + +# 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 = ["extra_css.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" + +html_logo = "_static/Logo.png" +# Output file base name for HTML help builder. +htmlhelp_basename = "CircuitpythonCirquePinnacleLibrarydoc" + +# -- 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, + "CircuitPythonCirquePinnacleLibrary.tex", + "CircuitPython Cirque Pinnacle 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, + "CircuitPythonCirquePinnacleLibrary", + "CircuitPython Cirque Pinnacle 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, + "CircuitpythonCirquePinnacleLibrary", + "Circuitpython Cirque Pinnacle Library Documentation", + author, + "CircuitpythonCirquePinnacleLibrary", + "CircuitPython Library for Cirque Pinnacle touch Controller.", + "Miscellaneous", + ), +] + +# ---Options for PDF output +# ----------------------------------------- +# requires `rst2pdf` module which is not builtin to Python 3.4 nor +# readthedocs.org's docker) + +pdf_documents = [ + ( + "index", + "CircuitPython-Cirque-Pinnacle", + "CircuitPython-Cirque-Pinnacle library documentation", + "Brendan Doherty", + ), +] diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..e582053 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst diff --git a/docs/examples.rst b/docs/examples.rst index e19a118..12c48a2 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,26 +1,30 @@ -Simple Test ------------ - -Ensure your device works with this simple test. - -.. literalinclude:: ../examples/cirque_pinnacle_simpletest.py - :caption: examples/cirque_pinnacle_simpletest.py - :linenos: - -USB Mouse example ------------------ - -This example uses CircuitPython's built-in `usb_hid` API to emulate a mouse with the Cirque circle trackpad. - -.. literalinclude:: ../examples/cirque_pinnacle_usb_mouse.py - :caption: examples/cirque_pinnacle_usb_mouse.py - :linenos: - -AnyMeas mode example --------------------- - -This example uses the Pinnacle touch controller's AnyMeas mode to fetch raw ADC values. - -.. literalinclude:: ../examples/cirque_pinnacle_anymeas_test.py - :caption: examples/cirque_pinnacle_anymeas_test.py - :linenos: + +Examples +======== + +Simple Test +----------- + +Ensure your device works with this simple test. + +.. literalinclude:: ../examples/cirque_pinnacle_simpletest.py + :caption: examples/cirque_pinnacle_simpletest.py + :linenos: + +USB Mouse example +----------------- + +This example uses CircuitPython's built-in `usb_hid` API to emulate a mouse with the Cirque circle trackpad. + +.. literalinclude:: ../examples/cirque_pinnacle_usb_mouse.py + :caption: examples/cirque_pinnacle_usb_mouse.py + :linenos: + +AnyMeas mode example +-------------------- + +This example uses the Pinnacle touch controller's AnyMeas mode to fetch raw ADC values. + +.. literalinclude:: ../examples/cirque_pinnacle_anymeas_test.py + :caption: examples/cirque_pinnacle_anymeas_test.py + :linenos: diff --git a/docs/index.rst b/docs/index.rst index 0a01633..8137dbc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,25 +1,27 @@ -.. include:: ../README.rst - -Table of Contents -================= .. toctree:: - :maxdepth: 4 :hidden: self .. toctree:: - :caption: Examples - :maxdepth: 3 + :hidden: examples .. toctree:: :caption: API Reference - :maxdepth: 4 + :hidden: + about_lite api + rel_abs + anymeas + +.. toctree:: + :hidden: + + contributing .. toctree:: :caption: Related Products @@ -27,19 +29,160 @@ Table of Contents Cirque Glidepoint circle trackpads 12-pin FPC cable (0.5mm pitch) -.. toctree:: - :caption: Other Links +Introduction +============ + +A CircuitPython driver library that implements the Adafruit_BusDevice library +for interfacing with the Cirque Pinnacle (1CA027) touch controller used in Cirque Circle Trackpads. + +Supported Features +------------------ + +* Use SPI or I2C bus protocols to interface with the Pinnacle touch controller ASIC (Application + Specific Integrated Circuit). +* Relative mode data reporting (AKA Mouse mode) with optional tap detection. +* Absolute mode data reporting (x, y, & z axis positions). +* AnyMeas mode data reporting. This mode exposes the ADC (Analog to Digital Converter) values and is + not well documented in the numerous datasheets provided by the Cirque corporation about the + Pinnacle (1CA027), thus this is a rather experimental mode. +* Hardware input buttons' states included in data reports. There are 3 button input lines on + the Cirque circle trackpads -- see `Pinout`_ section. +* Configure measurements for finger or stylus (or automatically detirmine either) touch + events. The Cirque circle trackpads are natively capable of measuring only 1 touch + point per event. +* Download/upload the underlying compensation matrix for ADC measurements. +* Adjust the ADC matrix gain (sensitivity). + +.. 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 allow + changing the I2C slave device address (via software); this means only 1 Cirque circle trackpad + can be accessed over the I2C bus in the lifecycle of an application. That said, you could change + the I2C address from ``0x2A`` to ``0x2C`` by soldering a 470K ohm resistor at the junction + labeled "ADR" (see picture in `Pinout`_ section), although this is untested. + +Unsupported Features +-------------------- + +* The legacy PS\\2 interface is pretty limited and not accessible by some CircuitPython MCUs. + Therefore, it has been neglected in this library. +* Cirque's circle trackpads ship with the newer non-AG (Advanced Gestures) variant of the + Pinnacle touch controller ASIC. Thus, this library focuses on the the non-AG variant's + functionality via testing, and it does not provide access to the older AG variant's features + (register addresses slightly differ which breaks compatibility). + +Pinout +------ + +.. image:: https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle/raw/master/docs/_static/Cirque_GlidePoint-Circle-Trackpad.png + :target: https://www.mouser.com/new/cirque/glidepoint-circle-trackpads/ + +The above picture is an 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 FFC/FPC cable `_ connector (the white block near the +bottom). The following table shows how the pins are connected in the `examples `_ (tested on an `ItsyBitys M4 `_) + +.. csv-table:: pinout (ordered the same as the FFC/FPC cable connector) + :header: "cable pin number", Label, "MCU pin", Description + :widths: 4, 5, 5, 13 + + 1, SCK, SCK, "SPI clock line" + 2, SO, MISO, "Master Input Slave Output" + 3, SS, D7, "Slave Select (AKA Chip Select)" + 4, DR, D2, "Data Ready interrupt" + 5, SI, MOSI, "SPI Master Output Slave Input" + 6, B2, N/A, "Hardware input button #2" + 7, B3, N/A, "Hardware input button #3" + 8, B1, N/A, "Hardware input button #1" + 9, SCL, SCL, "I2C clock line" + 10, SDA, SDA, "I2C data line" + 11, GND, GND, Ground + 12, VDD, 3V, "3V power supply" + +.. tip:: Of course, you can capture button data manually (if your application utilizes more + than 3 buttons), but if you connect the pins B1, B2, B3 to momentary push buttons that + (when pressed) provide a path to ground, the Pinnacle touch controller will report all 3 + buttons' states for each touch (or even button only) events. + +Model Labeling Scheme +********************* + +TM\ ``yyyxxx``\ -202\ ``i``\ -\ ``cc``\ ``o`` + +- ``yyyxxx`` stands for the respective vertical & horizontal width of the trackpad in millimeters. +- ``i`` stands for the hardwired interface protocol (3 = I2C, 4 = SPI). Notice, if there is a + resistor populated at the R1 (470K ohm) junction (located just above the Pinnacle ASIC), it + is configured for SPI, otherwise it is configured for I2C. +- ``cc`` stands for Custom Configuration which describes if a 470K ohm resistor is populated at + junction R4. "30" (resistor at R4 exists) means that the hardware is configured to disable + certain features despite what this library does. "00" (no resistor at R4) means that the + hardware is configured to allow certain features to be manipulated by this library. These + features include "secondary tap" (thought of as "right mouse button" in relative data mode), + Intellimouse scrolling (Microsoft patented scroll wheel behavior -- a throw back to when + scroll wheels were first introduced), and 180 degree orientation (your application can invert + the axis data anyway). +- ``o`` stands for the overlay type (0 = none, 1 = adhesive, 2 = flat, 3 = curved) + +Getting Started +--------------- + +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 +************** + +Using ``pip`` +~~~~~~~~~~~~~ + +This library is deployed to pypi.org, so you can easily install this library +using + +.. code-block:: shell + + pip3 install circuitpython-cirque-pinnacle + +Using git source +~~~~~~~~~~~~~~~~ + +This library can also be installed from the git source repository. + +.. code-block:: shell + + git clone https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle.git + cd CircuitPython_Cirque_Pinnacle + python3 -m pip install . + +Usage Example +************* + +Ensure you've connected the TMyyyxxx correctly by running the `examples/` located in the `examples +folder of this library `_. + +Contributing +------------ + +Contributions are welcome! Please read our `Code of Conduct +`_ +before contributing to help this project stay welcoming. + +Please review our :doc:`contributing` for details on the development workflow and linting tools. - Download - CircuitPython Reference Documentation - CircuitPython Support Forum - Adafruit Learning System - Adafruit Blog - Adafruit Store +To initiate a discussion of idea(s), you need only open an issue on the +`source's git repository `_ +(it doesn't have to be a bug report). -Indices and tables -================== +Sphinx documentation +******************** -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +Please read our :doc:`contributing` for instructions on how to build the documentation. diff --git a/docs/rel_abs.rst b/docs/rel_abs.rst new file mode 100644 index 0000000..fc56eda --- /dev/null +++ b/docs/rel_abs.rst @@ -0,0 +1,32 @@ +Relative or Absolute mode API +============================= + +.. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.feed_enable + +.. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.hard_configured + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.relative_mode_config + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.absolute_mode_config + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.available + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.read + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.clear_status_flags + +.. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.allow_sleep + +.. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.shutdown + +.. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.sample_rate + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.detect_finger_stylus + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.calibrate + +.. autoattribute:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.calibration_matrix + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.set_adc_gain + +.. automethod:: circuitpython_cirque_pinnacle.glidepoint.PinnacleTouch.tune_edge_sensitivity diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..434057c --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx-immaterial diff --git a/examples/cirque_pinnacle_anymeas_test.py b/examples/cirque_pinnacle_anymeas_test.py index 6508977..26d70e1 100644 --- a/examples/cirque_pinnacle_anymeas_test.py +++ b/examples/cirque_pinnacle_anymeas_test.py @@ -1,11 +1,19 @@ -""" a test example using SPI to read ADC measurements from the Pinnacle touch -controller in "AnyMeas" mode. This example does NOT work with -glidepoint_lite.py""" +""" +A test example using SPI to read ADC measurements from the Pinnacle touch +controller in "AnyMeas" mode +""" import time import struct import board from digitalio import DigitalInOut -import circuitpython_cirque_pinnacle.glidepoint as Pinnacle + +# This example does NOT work with glidepoint_lite.py +from circuitpython_cirque_pinnacle import glidepoint + +try: + from typing import List +except ImportError: + pass dr_pin = DigitalInOut(board.D2) # NOTE The dr_pin is a required keyword argument to the @@ -14,25 +22,25 @@ # if using a trackpad configured for SPI spi = board.SPI() ss_pin = DigitalInOut(board.D7) -tpad = Pinnacle.PinnacleTouchSPI(spi, ss_pin, dr_pin=dr_pin) +t_pad = glidepoint.PinnacleTouchSPI(spi, ss_pin, dr_pin=dr_pin) # if using a trackpad configured for I2C # i2c = board.I2C() -# tpad = Pinnacle.PinnacleTouchI2C(i2c, dr_pin=dr_pin) +# t_pad = glidepoint.PinnacleTouchI2C(i2c, dr_pin=dr_pin) # if dr_pin was not specified upon instantiation. # this command will raise an AttributeError exception -tpad.data_mode = Pinnacle.ANYMEAS +t_pad.data_mode = glidepoint.ANYMEAS # setup toggle and polarity bits for measuring with PNP gate muxing -class MeasVector: +class MeasVector: # pylint: disable=too-few-public-methods """A blueprint matrix used to manipulate the measurements' vector""" - def __init__(self, toggle, polarity): + def __init__(self, toggle: int, polarity: int): self.toggle = toggle self.polarity = polarity -vectors = [] +vectors: List[MeasVector] = [] # This toggles Y0 only and toggles it positively vectors.append(MeasVector(0x00010000, 0x00010000)) # This toggles Y0 only and toggles it negatively @@ -48,13 +56,12 @@ def __init__(self, toggle, polarity): def compensate(count=5): - """take ``count`` measurements, then average them together """ + """take ``count`` measurements, then average them together""" for i, vector in enumerate(vectors): idle_vectors[i] = 0 for _ in range(count): result = struct.unpack( - "h", - tpad.measure_adc(vector.toggle, vector.polarity) + "h", t_pad.measure_adc(vector.toggle, vector.polarity) )[0] idle_vectors[i] += result idle_vectors[i] /= count @@ -68,8 +75,7 @@ def take_measurements(timeout=10): while time.monotonic() - start < timeout: for i, vector in enumerate(vectors): result = struct.unpack( - "h", - tpad.measure_adc(vector.toggle, vector.polarity) + "h", t_pad.measure_adc(vector.toggle, vector.polarity) )[0] print("vector{}: {}".format(i, result - idle_vectors[i]), end="\t") print() diff --git a/examples/cirque_pinnacle_simpletest.py b/examples/cirque_pinnacle_simpletest.py index 1acfe41..55de49c 100644 --- a/examples/cirque_pinnacle_simpletest.py +++ b/examples/cirque_pinnacle_simpletest.py @@ -1,9 +1,14 @@ -"""A simple test example. This example also works with glidepoint_lite.py""" +""" +A simple test example. This example also works with glidepoint_lite.py +""" import time import struct import board from digitalio import DigitalInOut -import circuitpython_cirque_pinnacle.glidepoint as Pinnacle + +# if running this on a ATSAMD21 M0 based board +# from circuitpython_cirque_pinnacle import glidepoint_lite as glidepoint +from circuitpython_cirque_pinnacle import glidepoint dr_pin = DigitalInOut(board.D2) # NOTE The dr_pin is an optional keyword argument to the @@ -12,37 +17,35 @@ # if using a trackpad configured for SPI spi = board.SPI() ss_pin = DigitalInOut(board.D7) -tpad = Pinnacle.PinnacleTouchSPI(spi, ss_pin) +t_pad = glidepoint.PinnacleTouchSPI(spi, ss_pin) # if using a trackpad configured for I2C # i2c = board.I2C() -# tpad = Pinnacle.PinnacleTouchI2C(i2c) +# t_pad = glidepoint.PinnacleTouchI2C(i2c) + +t_pad.data_mode = glidepoint.ABSOLUTE # ensure Absolute mode is enabled +t_pad.absolute_mode_config(z_idle_count=1) # limit idle packet count to 1 -tpad.data_mode = Pinnacle.ABSOLUTE # ensure Absolute mode is enabled -tpad.absolute_mode_config(z_idle_count=1) # limit idle packet count to 1 def print_data(timeout=6): """Print available data reports from the Pinnacle touch controller until there's no input for a period of ``timeout`` seconds.""" - if tpad.data_mode == Pinnacle.RELATIVE: + if t_pad.data_mode == glidepoint.RELATIVE: print("using Relative mode") - elif tpad.data_mode == Pinnacle.ABSOLUTE: + elif t_pad.data_mode == glidepoint.ABSOLUTE: print("using Absolute mode") start = time.monotonic() while time.monotonic() - start < timeout: - if dr_pin.value: # is there new data? - data = tpad.report(only_new=False) - # Because we did not specify the dr_pin when instantiating the tpad variable, - # only_new=False skips the extra SPI or I2C transaction to check the - # SW_DR flag in the STATUS register which is reflected on the dr_pin + if dr_pin.value: # is there new data? + data = t_pad.read() - if tpad.data_mode == Pinnacle.ABSOLUTE and data[3]: + if t_pad.data_mode == glidepoint.ABSOLUTE and data[3]: # NOTE ``and data[3]`` means only when Z-axis is > 0 # specification sheet recommends clamping absolute position data of # X & Y axis for reliability data[1] = max(128, min(1920, data[1])) # X-axis data[2] = max(64, min(1472, data[2])) # Y-axis - elif tpad.data_mode == Pinnacle.RELATIVE: + elif t_pad.data_mode == glidepoint.RELATIVE: # convert 2's compliment form into natural numbers - data = struct.unpack('Bbbb', data) + data = struct.unpack("Bbbb", data) print(data) start = time.monotonic() diff --git a/examples/cirque_pinnacle_usb_mouse.py b/examples/cirque_pinnacle_usb_mouse.py index a5137da..3366639 100644 --- a/examples/cirque_pinnacle_usb_mouse.py +++ b/examples/cirque_pinnacle_usb_mouse.py @@ -1,25 +1,29 @@ -""" This example uses CircuitPython's built-in `usb_hid` API -to emulate a mouse with the Cirque circle trackpad. This example -also works with glidepoint_lite.py""" +""" +This example uses CircuitPython's built-in `usb_hid` API +to emulate a mouse with the Cirque circle trackpad +""" import time import board from digitalio import DigitalInOut import usb_hid -import circuitpython_cirque_pinnacle.glidepoint as Pinnacle + +# if running this on a ATSAMD21 M0 based board +# from circuitpython_cirque_pinnacle import glidepoint_lite as glidepoint +from circuitpython_cirque_pinnacle import glidepoint dr_pin = DigitalInOut(board.D2) # NOTE Specifying the optional keyword argument ``dr_pin`` to the -# constructor expedites ``report()`` when using Absolute or Relative modes +# constructor expedites ``read()`` when using Absolute or Relative modes # if using a trackpad configured for SPI spi = board.SPI() ss_pin = DigitalInOut(board.D7) -tpad = Pinnacle.PinnacleTouchSPI(spi, ss_pin, dr_pin=dr_pin) +t_pad = glidepoint.PinnacleTouchSPI(spi, ss_pin, dr_pin=dr_pin) # if using a trackpad configured for I2C # i2c = board.I2C() -# tpad = Pinnacle.PinnacleTouchI2C(i2c, dr_pin=dr_pin) +# t_pad = glidepoint.PinnacleTouchI2C(i2c, dr_pin=dr_pin) -tpad.data_mode = Pinnacle.RELATIVE # ensure mouse mode is enabled +t_pad.data_mode = glidepoint.RELATIVE # ensure mouse mode is enabled mouse = None for dev in usb_hid.devices: @@ -32,16 +36,17 @@ # byte1 = delta x-axis # byte2 = delta y-axis # byte3 = delta scroll wheel +if mouse is None: + raise OSError("mouse HID device not available.") + def move(timeout=10): """Send mouse X & Y reported data from the Pinnacle touch controller until there's no input for a period of ``timeout`` seconds.""" - if mouse is None: - raise OSError("mouse HID device not available.") start = time.monotonic() while time.monotonic() - start < timeout: - data = tpad.report() # only returns fresh data (if any) + data = t_pad.read() # only returns fresh data (if any) if data: # is there fresh data? mouse.send_report(data) # no scrolling or backward/forward start = time.monotonic() - mouse.send_report(b'\x00' * 4) # release buttons (just in case) + mouse.send_report(b"\x00" * 4) # release buttons (just in case) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3a05b4a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,616 @@ + +[build-system] +requires = [ + "setuptools>=61", + "wheel", + "setuptools-scm", +] + +[project] +name = "circuitpython-cirque-pinnacle" +requires-python = ">=3.7" +description = "A CircuitPython driver for Cirque Pinnacle (1CA027) touch controller used in Cirque Trackpads" +readme = "README.rst" +authors = [ + {name = "Brendan Doherty", email = "2bndy5@gmail.com"} +] +keywords = [ + "blinka", + "circuitpython", + "raspberrypi", + "driver", + "Cirque", + "Pinnacle", + "touch", + "sensor", + "trackpad", +] +license = {text = "MIT"} +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.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", +] +dynamic = ["version", "dependencies"] + +[project.urls] +Documentation = "https://circuitpython-cirque-pinnacle.readthedocs.io" +Source = "https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle" +Tracker = "https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle/issues" + +[tool.setuptools] +packages = ["circuitpython_cirque_pinnacle"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + +[tool.setuptools_scm] +# It would be nice to include the commit hash in the version, but that +# can't be done in a PEP 440-compatible way. +version_scheme= "no-guess-dev" +# Test PyPI does not support local versions. +local_scheme = "no-local-version" +fallback_version = "0.0.0" + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-vv" +testpaths = ["tests"] +log_level = "DEBUG" +log_format = "%(levelname)s\t%(name)s: %(message)s" + +[tool.mypy] +show_error_codes = true +show_column_numbers = true + +[tool.coverage] +[tool.coverage.run] +dynamic_context = "test_function" + +[tool.coverage.json] +pretty_print = true + +[tool.coverage.html] +show_contexts = true + +[tool.coverage.report] +# Regexes for lines to exclude from consideration +exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + "except ImportError", +] + +[tool.pylint.main] +# 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 = + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint in +# a server-like mode. +# clear-cache-post-run = + +# Always return a 0 (non-error) status code, even if lint errors are found. This +# is primarily useful in continuous integration scripts. +# exit-zero = + +# 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-allow-list = + +# 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. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +# extension-pkg-whitelist = + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +# fail-on = + +# Specify a score threshold under which the program will exit with error. +fail-under = 10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +# from-stdin = + +# Files or directories to be skipped. They should be base names, not paths. +ignore = ["CVS"] + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, it +# can't be used as an escape character. +# ignore-paths = + +# Files or directories matching the regular expression patterns are skipped. The +# regex matches against base names, not paths. The default value ignores Emacs +# file locks +# ignore-patterns = + +# 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"] + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +# init-hook = + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs = 2 + +# Control the amount of potential inferred values when inferring a single object. +# This can help the performance when dealing with large functions or complex, +# nested conditions. +limit-inference-results = 100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +# load-plugins = + +# Pickle collected data for later comparisons. +persistent = true + +# Minimum Python version to use for version dependent checks. Will default to the +# version used to run pylint. +py-version = "3.11" + +# Discover python modules and packages in the file system subtree. +# recursive = + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode = true + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +# unsafe-load-any-extension = + +[tool.pylint.basic] +# Naming style matching correct argument names. +argument-naming-style = "snake_case" + +# Regular expression matching correct argument names. Overrides argument-naming- +# style. If left empty, argument names will be checked with the set naming style. +argument-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +# Naming style matching correct attribute names. +attr-naming-style = "snake_case" + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +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"] + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +# bad-names-rgxs = + +# Naming style matching correct class attribute names. +class-attribute-naming-style = "any" + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +class-attribute-rgx = "([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$" + +# Naming style matching correct class constant names. +class-const-naming-style = "UPPER_CASE" + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +# class-const-rgx = + +# Naming style matching correct class names. +class-naming-style = "PascalCase" + +# Regular expression matching correct class names. Overrides class-naming-style. +# If left empty, class names will be checked with the set naming style. +class-rgx = "[A-Z_][a-zA-Z0-9_]+$" + +# Naming style matching correct constant names. +const-naming-style = "UPPER_CASE" + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming style. +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 style matching correct function names. +function-naming-style = "snake_case" + +# Regular expression matching correct function names. Overrides function-naming- +# style. If left empty, function names will be checked with the set naming style. +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 = ["r", "g", "b", "w", "i", "j", "k", "n", "x", "y", "z", "ex", "ok", "Run", "_"] + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +# good-names-rgxs = + +# Include a hint for the correct naming format with invalid-name. +# include-naming-hint = + +# Naming style matching correct inline iteration names. +inlinevar-naming-style = "any" + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +inlinevar-rgx = "[A-Za-z_][A-Za-z0-9_]*$" + +# Naming style matching correct method names. +method-naming-style = "snake_case" + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +method-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +# Naming style matching correct module names. +module-naming-style = "snake_case" + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +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. These +# decorators are taken in consideration only for invalid-name. +property-classes = ["abc.abstractproperty"] + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +# typevar-rgx = + +# Naming style matching correct variable names. +variable-naming-style = "snake_case" + +# Regular expression matching correct variable names. Overrides variable-naming- +# style. If left empty, variable names will be checked with the set naming style. +variable-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +[tool.pylint.classes] +# Warn about protected attribute access inside special methods +# check-protected-access-in-special-methods = + +# 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"] + +[tool.pylint.design] +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +# exclude-too-few-public-methods = + +# List of qualified class names to ignore when counting class parents (see R0901) +# ignored-parents = + +# Maximum number of arguments for function / method. +max-args = 5 + +# Maximum number of attributes for a class (see R0902). +max-attributes = 11 + +# Maximum number of boolean expressions in an if statement (see R0916). +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 + +[tool.pylint.exceptions] +# Exceptions that will emit a warning when caught. +overgeneral-exceptions = ["Exception"] + +[tool.pylint.format] +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +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 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +# single-line-class-stmt = + +# Allow the body of an if to be on the same line as the test if there is no else. +# single-line-if-stmt = + +[tool.pylint.imports] +# List of modules that can be imported at any level, not just the top level one. +# allow-any-import-level = + +# Allow explicit reexports by alias from a package __init__. +# allow-reexport-from-package = + +# Allow wildcard imports from modules that define __all__. +# allow-wildcard-with-all = + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules = ["optparse", "tkinter.tix"] + +# Output a graph (.gv or any supported image format) of external dependencies to +# the given file (report RP0402 must not be disabled). +# ext-import-graph = + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be disabled). +# import-graph = + +# Output a graph (.gv or any supported image format) of internal dependencies to +# 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"] + +# Couples of modules and preferred modules, separated by a comma. +# preferred-modules = + +[tool.pylint.logging] +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style = "old" + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules = ["logging"] + +[tool.pylint."messages control"] +# Only show warnings with the listed confidence levels. Leave empty to show all. +# Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] + +# 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 re-enable 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 = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "import-error", "duplicate-code", "consider-using-f-string", "too-many-arguments"] + +# 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 = ["c-extension-no-member"] + +[tool.pylint.method_args] +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods = ["requests.api.delete", "requests.api.get", "requests.api.head", "requests.api.options", "requests.api.patch", "requests.api.post", "requests.api.put", "requests.api.request"] + +[tool.pylint.miscellaneous] +# List of note tags to take in consideration, separated by a comma. +notes = ["FIXME", "XXX"] + +# Regular expression of note tags to take in consideration. +# notes-rgx = + +[tool.pylint.refactoring] +# Maximum number of nested blocks for function / method body +max-nested-blocks = 5 + +# Complete name of functions that never returns. When checking for inconsistent- +# return-statements if a never returning function is called then it will be +# considered as an explicit return statement and no message will be printed. +never-returning-functions = ["sys.exit", "argparse.parse_error"] + +[tool.pylint.reports] +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each category, +# as well as 'statement' which is the total number of statements analyzed. This +# score 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, e.g. +# mypackage.mymodule.MyReporterClass. +# output-format = + +# Tells whether to display a full report or only the messages. +# reports = + +# Activate the evaluation score. +score = true + +[tool.pylint.similarities] +# Comments are removed from the similarity computation +ignore-comments = true + +# Docstrings are removed from the similarity computation +ignore-docstrings = true + +# Imports are removed from the similarity computation +# ignore-imports = + +# Signatures are removed from the similarity computation +ignore-signatures = true + +# Minimum lines number of a similarity. +min-similarity-lines = 4 + +[tool.pylint.spelling] +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions = 4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +# spelling-dict = + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:" + +# List of comma separated words that should not be checked. +# spelling-ignore-words = + +# A path to a file that contains the private dictionary; one word per line. +# spelling-private-dict-file = + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +# spelling-store-unknown-words = + +[tool.pylint.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 +# class is considered mixin if its name matches the mixin-class-rgx option. +# Tells whether to warn about missing members when the owner of the attribute is +# inferred to be None. +ignore-none = true + +# 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 = true + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins = ["no-member", "not-async-context-manager", "not-context-manager", "attribute-defined-outside-init"] + +# 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"] + +# 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 = true + +# 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 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx = ".*[Mm]ixin" + +# List of decorators that change the signature of a decorated function. +# signature-mutators = + +[tool.pylint.variables] +# List of additional names supposed to be defined in builtins. Remember that you +# should avoid defining new builtins when possible. +# additional-builtins = + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables = true + +# List of names allowed to shadow builtins +# allowed-redefined-builtins = + +# 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. expected to not +# be used). +dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_" + +# Argument names that match this expression will be ignored. +ignored-argument-names = "_.*|^ignored_|^unused_" + +# Tells whether we should check for unused import in __init__ files. +# init-import = + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules = ["six.moves", "future.builtins"] diff --git a/setup.py b/setup.py index 676f9d3..87b326e 100644 --- a/setup.py +++ b/setup.py @@ -1,60 +1,4 @@ -"""A setuptools based setup module. - -See: -https://packaging.python.org/en/latest/distributing.html -https://github.com/pypa/sampleproject -""" - -from os import path +"""All setup/install info is now in pyproject.toml""" from setuptools import setup -# To use a consistent encoding -from codecs import open - -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 Cirque 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=['...']` - packages=["circuitpython_cirque_pinnacle"], - # Extra links for the sidebar on pypi - project_urls={ - "Documentation": "https://circuitpython-cirque-pinnacle.readthedocs.io", - }, - download_url="https://github.com/2bndy5/CircuitPython_Cirque_Pinnacle/releases", -) +setup()