pylint
This commit is contained in:
parent
ac3aafbb73
commit
fcbc78e4bd
8 changed files with 696 additions and 120 deletions
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
|
|
@ -15,9 +15,6 @@ jobs:
|
|||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.5
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
|
@ -25,7 +22,15 @@ jobs:
|
|||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install python3-venv build-essential gettext
|
||||
make venv
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: print versions
|
||||
run: python3 --version
|
||||
- name: setup venv
|
||||
run: make venv
|
||||
- name: lint
|
||||
run: make lint
|
||||
- name: build
|
||||
run: make cp
|
||||
- uses: actions/upload-artifact@v1.0.0
|
||||
|
|
|
|||
433
.pylintrc
Normal file
433
.pylintrc
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
[MASTER]
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code
|
||||
extension-pkg-whitelist=
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Add files or directories matching the regex patterns to the blacklist. The
|
||||
# regex matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
# jobs=1
|
||||
jobs=2
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call
|
||||
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,blacklisted-name
|
||||
|
||||
# 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*(# )?<?https?://\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=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
# max-attributes=7
|
||||
max-attributes=24
|
||||
|
||||
# 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
|
||||
6
Makefile
6
Makefile
|
|
@ -24,10 +24,14 @@ clean:
|
|||
cp:
|
||||
$(PYTHON) install.py
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(PYTHON) -mpylint src/*.py
|
||||
|
||||
.PHONY: venv
|
||||
venv: .venv/bin/python
|
||||
|
||||
.venv/bin/python:
|
||||
/usr/bin/python3 -mvenv --clear .venv
|
||||
python3 -mvenv --clear .venv
|
||||
$(PYTHON) -mpip install wheel
|
||||
$(PYTHON) -mpip install -r requirements.txt
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
circuitpython-build-tools
|
||||
pylint
|
||||
|
|
|
|||
|
|
@ -1,7 +1,33 @@
|
|||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2020 Jeff Epler for Adafruit Industries LLC
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
"""
|
||||
Convert an analog joystick to digital
|
||||
"""
|
||||
|
||||
import analogio
|
||||
import board
|
||||
|
||||
class AnalogJoystick:
|
||||
"""Convert an analog joystick to digital"""
|
||||
def __init__(self, pin_x=None, pin_y=None, x_invert=False, y_invert=True, deadzone=8000):
|
||||
self._x = analogio.AnalogIn(pin_x or board.JOYSTICK_X)
|
||||
self._y = analogio.AnalogIn(pin_y or board.JOYSTICK_Y)
|
||||
|
|
@ -12,26 +38,34 @@ class AnalogJoystick:
|
|||
self.poll()
|
||||
|
||||
def poll(self):
|
||||
"""Read the analog values and update the digital outputs"""
|
||||
self.x = (self._x.value - self.x_center) * (-1 if self.x_invert else 1)
|
||||
self.y = (self._y.value - self.y_center) * (-1 if self.y_invert else 1)
|
||||
return [self.up, self.down, self.left, self.right]
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@property
|
||||
def up(self):
|
||||
"""Return true when the stick was pressed up at the last poll"""
|
||||
return self.y > self.deadzone
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
@property
|
||||
def down(self):
|
||||
"""Return true when the stick was pressed down at the last poll"""
|
||||
return self.y < -self.deadzone
|
||||
|
||||
@property
|
||||
def left(self):
|
||||
"""Return true when the stick was pressed left at the last poll"""
|
||||
return self.x < -self.deadzone
|
||||
|
||||
@property
|
||||
def right(self):
|
||||
"""Return true when the stick was pressed right at the last poll"""
|
||||
return self.x > self.deadzone
|
||||
|
||||
def recenter(self):
|
||||
"""Use the current position of the analog joystick as the center"""
|
||||
self.x_center = self._x.value
|
||||
self.y_center = self._y.value
|
||||
|
|
|
|||
36
src/bar.py
36
src/bar.py
|
|
@ -1,6 +1,32 @@
|
|||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2020 Jeff Epler for Adafruit Industries LLC
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
"""
|
||||
Display a Progress Bar
|
||||
"""
|
||||
|
||||
import displayio
|
||||
|
||||
class Bar(displayio.TileGrid):
|
||||
"""Progress Bar"""
|
||||
def __init__(self, x, y, width, height, *, value=0, colors=[0xffffff, None]):
|
||||
self._palette = displayio.Palette(2)
|
||||
for i in range(2):
|
||||
|
|
@ -12,14 +38,17 @@ class Bar(displayio.TileGrid):
|
|||
self._width = width
|
||||
self._height = height
|
||||
self._bitmap = displayio.Bitmap(2, height, 2)
|
||||
for i in range(0, height*2, 2): self._bitmap[i] = 1
|
||||
for i in range(0, height*2, 2):
|
||||
self._bitmap[i] = 1
|
||||
|
||||
super().__init__(self._bitmap, pixel_shader=self._palette, x=x, y=y, width=width, tile_width=1)
|
||||
super().__init__(self._bitmap, pixel_shader=self._palette, x=x, y=y,
|
||||
width=width, tile_width=1)
|
||||
|
||||
self.value = value
|
||||
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""The current value of the progress bar, from 0 to 1"""
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
|
|
@ -28,4 +57,3 @@ class Bar(displayio.TileGrid):
|
|||
j = newvalue * self._width - .5
|
||||
for i in range(self._width):
|
||||
self[i] = i <= j
|
||||
|
||||
|
|
|
|||
264
src/main.py
264
src/main.py
|
|
@ -1,10 +1,42 @@
|
|||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2020 Jeff Epler for Adafruit Industries LLC
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
"""
|
||||
jeplayer - main file
|
||||
|
||||
This is an MP3 player for the PyGamer with CircuitPython.
|
||||
|
||||
See README.md for more information.
|
||||
"""
|
||||
|
||||
import gc
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
|
||||
import adafruit_bitmap_font.bitmap_font
|
||||
import adafruit_display_text.label
|
||||
import bar
|
||||
import adafruit_sdcard
|
||||
import analogjoy
|
||||
import analogio
|
||||
import audiocore
|
||||
import audioio
|
||||
import audiomp3
|
||||
import board
|
||||
|
|
@ -12,33 +44,33 @@ import busio
|
|||
import digitalio
|
||||
import displayio
|
||||
import gamepadshift
|
||||
import gc
|
||||
import microcontroller
|
||||
import neopixel
|
||||
import os
|
||||
import random
|
||||
import repeat
|
||||
import storage
|
||||
import terminalio
|
||||
import time
|
||||
from micropython import const
|
||||
|
||||
def clear_display():
|
||||
"""Display nothing"""
|
||||
board.DISPLAY.show(displayio.Group(max_size=1))
|
||||
|
||||
board.DISPLAY.rotation = 0
|
||||
clear_display()
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def px(x, y):
|
||||
"""Convert a raw value (x/y) to a pixel value, clamping negative values"""
|
||||
return 0 if x <= 0 else round(x / y)
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
class PlaybackDisplay:
|
||||
"""Manage display during playback"""
|
||||
def __init__(self):
|
||||
self.group = displayio.Group(max_size=4)
|
||||
self.glyph_width, self.glyph_height = font.get_bounding_box()[:2]
|
||||
self.pbar = bar.Bar(0, 0, board.DISPLAY.width,
|
||||
self.glyph_height, colors=(0x0000ff, None))
|
||||
self.glyph_height, colors=(0x0000ff, None))
|
||||
self.label = adafruit_display_text.label.Label(font, line_spacing=1.0,
|
||||
max_glyphs=256)
|
||||
max_glyphs=256)
|
||||
self.label.y = 6
|
||||
self._bitmap_filename = None
|
||||
self._fallback_bitmap = ["/rsrc/background.bmp"]
|
||||
|
|
@ -52,16 +84,19 @@ class PlaybackDisplay:
|
|||
|
||||
@property
|
||||
def text(self):
|
||||
"""The text shown at the top of the display. Usually 2 lines."""
|
||||
return self._text
|
||||
|
||||
@text.setter
|
||||
def text(self, text):
|
||||
if len(text) > 256: text = text[:256]
|
||||
if len(text) > 256:
|
||||
text = text[:256]
|
||||
self._text = text
|
||||
self.label.text = text
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
"""The fraction of progress through the current track"""
|
||||
return self.pbar.value
|
||||
|
||||
@progress.setter
|
||||
|
|
@ -69,18 +104,18 @@ class PlaybackDisplay:
|
|||
self.pbar.value = frac
|
||||
|
||||
def set_bitmap(self, candidates):
|
||||
for c in candidates + self._fallback_bitmap:
|
||||
if c == self._bitmap_filename:
|
||||
"""Find and use a background from among candidates, or else the fallback bitmap"""
|
||||
for i in candidates + self._fallback_bitmap:
|
||||
if i == self._bitmap_filename:
|
||||
return # Already loaded
|
||||
try:
|
||||
f = _bitmap_file = open(c, 'rb')
|
||||
except OSError as e:
|
||||
bitmap_file = open(i, 'rb')
|
||||
except OSError:
|
||||
continue
|
||||
bitmap = displayio.OnDiskBitmap(f)
|
||||
self._bitmap_filename = c
|
||||
bitmap = displayio.OnDiskBitmap(bitmap_file)
|
||||
self._bitmap_filename = i
|
||||
# Create a TileGrid to hold the bitmap
|
||||
self.tile_grid = displayio.TileGrid(bitmap,
|
||||
pixel_shader=displayio.ColorConverter())
|
||||
self.tile_grid = displayio.TileGrid(bitmap, pixel_shader=displayio.ColorConverter())
|
||||
|
||||
# Add the TileGrid to the Group
|
||||
if len(self.group) == 0:
|
||||
|
|
@ -93,6 +128,7 @@ class PlaybackDisplay:
|
|||
|
||||
@property
|
||||
def rms(self):
|
||||
"""The RMS audio level, used to control the neopixel vu meter"""
|
||||
return self._rms
|
||||
|
||||
@rms.setter
|
||||
|
|
@ -105,6 +141,7 @@ class PlaybackDisplay:
|
|||
self.pixels[4] = (20, 0, 0) if value > 320 else (px(value - 160, 8), 0, 0)
|
||||
self.pixels.show()
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
|
||||
enable.direction = digitalio.Direction.OUTPUT
|
||||
enable.value = True
|
||||
|
|
@ -117,15 +154,6 @@ playback_display = PlaybackDisplay()
|
|||
board.DISPLAY.show(playback_display.group)
|
||||
font.load_glyphs(range(32, 128))
|
||||
|
||||
def change_stream(filename):
|
||||
old_stream = mp3stream.file
|
||||
mp3stream.file = open(filename, "rb")
|
||||
old_stream.close()
|
||||
return mp3stream.file
|
||||
|
||||
adc_vbat = analogio.AnalogIn(board.A6)
|
||||
scale_vbat = 2 * adc_vbat.reference_voltage / 65535
|
||||
|
||||
BUTTON_SEL = const(8)
|
||||
BUTTON_START = const(4)
|
||||
BUTTON_A = const(2)
|
||||
|
|
@ -148,56 +176,46 @@ else:
|
|||
buttons = gamepadshift.GamePadShift(digitalio.DigitalInOut(board.BUTTON_CLOCK),
|
||||
digitalio.DigitalInOut(board.BUTTON_OUT),
|
||||
digitalio.DigitalInOut(board.BUTTON_LATCH))
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
def mount_sd():
|
||||
"""Mount the SD card"""
|
||||
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
|
||||
cs = digitalio.DigitalInOut(board.SD_CS)
|
||||
sdcard = adafruit_sdcard.SDCard(spi, cs)
|
||||
sd_cs = digitalio.DigitalInOut(board.SD_CS)
|
||||
sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
|
||||
vfs = storage.VfsFat(sdcard)
|
||||
storage.mount(vfs, "/sd")
|
||||
|
||||
def join(base, *args):
|
||||
for a in args: base = base + '/' + a
|
||||
return base
|
||||
def join(*args):
|
||||
"""Like posixpath.join"""
|
||||
return "/".join(args)
|
||||
|
||||
def play_all(dir='/sd'):
|
||||
with digitalio.DigitalInOut(board.SPEAKER_ENABLE) as enable:
|
||||
enable.direction = digitalio.Direction.OUTPUT
|
||||
enable.value = True
|
||||
# In 5.0a1, stereo playback on samd dac doesn't work due to a bug
|
||||
#with audioio.AudioOut(board.SPEAKER, right_channel=board.A1) as speaker:
|
||||
with audioio.AudioOut(board.SPEAKER) as speaker:
|
||||
for f in os.listdir(dir):
|
||||
if f.lower().endswith('.mp3'):
|
||||
play_one_file(speaker, join(dir, f))
|
||||
|
||||
def blank_screen():
|
||||
displayio.release_displays()
|
||||
|
||||
# https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
|
||||
def shuffle(seq):
|
||||
"""Shuffle a sequence using the Fisher-Yates shuffle algorithm (like random.shuffle)"""
|
||||
for i in range(len(seq)-2):
|
||||
j = random.randint(i, len(seq)-1)
|
||||
seq[i], seq[j] = seq[j], seq[i]
|
||||
|
||||
def menu_choice(seq, button_ok, button_cancel, *, sel_idx=0, font=font):
|
||||
# pylint: disable=too-many-locals
|
||||
def menu_choice(seq, button_ok, button_cancel, *, sel_idx=0, text_font=font):
|
||||
"""Display a menu and allow a choice from it"""
|
||||
board.DISPLAY.auto_refresh = True
|
||||
scroll_idx = sel_idx
|
||||
glyph_width, glyph_height = font.get_bounding_box()[:2]
|
||||
glyph_width, glyph_height = text_font.get_bounding_box()[:2]
|
||||
num_rows = min(len(seq), board.DISPLAY.height // glyph_height)
|
||||
max_glyphs = board.DISPLAY.width // glyph_width
|
||||
labels = [adafruit_display_text.label.Label(font, max_glyphs=max_glyphs)
|
||||
for i in range(num_rows)]
|
||||
cursor = adafruit_display_text.label.Label(font, max_glyphs=1, color=0xddddff)
|
||||
y0 = (glyph_height+1)//2
|
||||
labels = [adafruit_display_text.label.Label(text_font, max_glyphs=max_glyphs)
|
||||
for i in range(num_rows)]
|
||||
cursor = adafruit_display_text.label.Label(text_font, max_glyphs=1, color=0xddddff)
|
||||
base_y = (glyph_height+1)//2
|
||||
scene = displayio.Group(max_size=len(labels) + 1)
|
||||
for i, li in enumerate(labels):
|
||||
li.x = round(glyph_width * 1.5)
|
||||
li.y = y0 + glyph_height * i
|
||||
li.text = seq[i][:max_glyphs]
|
||||
scene.append(li)
|
||||
cursor.x = 0
|
||||
cursor.y = y0
|
||||
for i, label in enumerate(labels):
|
||||
label.x = round(glyph_width * 1.5)
|
||||
label.y = base_y + glyph_height * i
|
||||
label.text = seq[i][:max_glyphs]
|
||||
scene.append(label)
|
||||
cursor.x = 0
|
||||
cursor.y = base_y
|
||||
cursor.text = ">"
|
||||
scene.append(cursor)
|
||||
|
||||
|
|
@ -209,12 +227,16 @@ def menu_choice(seq, button_ok, button_cancel, *, sel_idx=0, font=font):
|
|||
while True:
|
||||
enable.value = speaker.playing
|
||||
pressed = buttons.get_pressed()
|
||||
if button_cancel and (pressed & button_cancel): return -1
|
||||
if pressed & button_ok: return sel_idx
|
||||
if button_cancel and (pressed & button_cancel):
|
||||
return -1
|
||||
if pressed & button_ok:
|
||||
return sel_idx
|
||||
|
||||
joystick.poll()
|
||||
if up_key.value: sel_idx -= 1
|
||||
if down_key.value: sel_idx += 1
|
||||
if up_key.value:
|
||||
sel_idx -= 1
|
||||
if down_key.value:
|
||||
sel_idx += 1
|
||||
|
||||
sel_idx = min(len(seq)-1, max(0, sel_idx))
|
||||
|
||||
|
|
@ -230,36 +252,49 @@ def menu_choice(seq, button_ok, button_cancel, *, sel_idx=0, font=font):
|
|||
if new_text != labels[j].text:
|
||||
labels[j].text = new_text
|
||||
|
||||
cursor.y = y0 + glyph_height * (sel_idx - scroll_idx)
|
||||
cursor.y = base_y + glyph_height * (sel_idx - scroll_idx)
|
||||
|
||||
time.sleep(1/20)
|
||||
# pylint: enable=too-many-locals
|
||||
|
||||
S_IFDIR = const(16384)
|
||||
def isdir(x): return os.stat(x)[0] & S_IFDIR
|
||||
def isdir(x):
|
||||
"""Return True if 'x' is a directory"""
|
||||
return os.stat(x)[0] & S_IFDIR
|
||||
|
||||
def choose_folder(base='/sd'):
|
||||
"""Let the user choose a folder within a base directory"""
|
||||
all_folders = sorted(m for m in os.listdir(base) if isdir(join(base, m)))
|
||||
choices = ['Surprise Me'] + all_folders
|
||||
|
||||
idx = menu_choice(choices,
|
||||
BUTTON_START | BUTTON_A | BUTTON_B | BUTTON_SEL, 0)
|
||||
BUTTON_START | BUTTON_A | BUTTON_B | BUTTON_SEL, 0)
|
||||
clear_display()
|
||||
if idx >= 1: result = all_folders[idx-1]
|
||||
else: result = random.choice(all_folders)
|
||||
if idx >= 1:
|
||||
result = all_folders[idx-1]
|
||||
else:
|
||||
result = random.choice(all_folders)
|
||||
return join(base, result)
|
||||
|
||||
def wait_no_button_pressed():
|
||||
while buttons.get_pressed(): time.sleep(1/20)
|
||||
"""Wait until no button is pressed"""
|
||||
while buttons.get_pressed():
|
||||
time.sleep(1/20)
|
||||
|
||||
def average_temperature(n=20):
|
||||
return sum(microcontroller.cpu.temperature for i in range(n)) / n
|
||||
def change_stream(filename):
|
||||
"""Change the global MP3Decoder object to play a new file"""
|
||||
old_stream = mp3stream.file
|
||||
mp3stream.file = open(filename, "rb")
|
||||
old_stream.close()
|
||||
return mp3stream.file
|
||||
|
||||
_bitmap_file = None
|
||||
def play_one_file(speaker, idx, filename, folder, title, next_title):
|
||||
def play_one_file(idx, filename, folder, title):
|
||||
"""Play one file, reacting to user input"""
|
||||
board.DISPLAY.auto_refresh = False
|
||||
|
||||
playback_display.set_bitmap([
|
||||
filename.rsplit('.', 1)[0] + ".bmp",
|
||||
filename.rsplit('/', 1)[0] + ".bmp"])
|
||||
filename.rsplit('.', 1)[0] + ".bmp",
|
||||
filename.rsplit('/', 1)[0] + ".bmp"])
|
||||
|
||||
playback_display.text = "%s\n%s" % (folder, title)
|
||||
|
||||
|
|
@ -268,8 +303,8 @@ def play_one_file(speaker, idx, filename, folder, title, next_title):
|
|||
result = idx + 1
|
||||
wait_no_button_pressed()
|
||||
paused = False
|
||||
sz = os.stat(filename)[6]
|
||||
f = change_stream(filename)
|
||||
file_size = os.stat(filename)[6]
|
||||
mp3file = change_stream(filename)
|
||||
speaker.play(mp3stream)
|
||||
board.DISPLAY.auto_refresh = True
|
||||
while speaker.playing:
|
||||
|
|
@ -277,7 +312,7 @@ def play_one_file(speaker, idx, filename, folder, title, next_title):
|
|||
gc.collect()
|
||||
|
||||
playback_display.rms = mp3stream.rms_level
|
||||
playback_display.progress = f.tell() / sz
|
||||
playback_display.progress = mp3file.tell() / file_size
|
||||
|
||||
pressed = buttons.get_pressed()
|
||||
# SEL: cancel playlist
|
||||
|
|
@ -303,34 +338,38 @@ def play_one_file(speaker, idx, filename, folder, title, next_title):
|
|||
if pressed & BUTTON_A:
|
||||
result = idx + 1
|
||||
break
|
||||
|
||||
|
||||
speaker.stop()
|
||||
playback_display.rms = 0
|
||||
|
||||
|
||||
gc.collect()
|
||||
|
||||
return result
|
||||
|
||||
def play_all(playlist, *, folder='', trim=0, dir='/sd'):
|
||||
def play_all(playlist, *, folder='', trim=0, location='/sd'):
|
||||
"""Play everything in 'playlist', which is relative to 'location'.
|
||||
|
||||
'folder' is a display name for the user."""
|
||||
i = 0
|
||||
board.DISPLAY.show(playback_display.group)
|
||||
while i >= 0 and i < len(playlist):
|
||||
f = playlist[i]
|
||||
next_up = (playlist[i+1][trim:-4]
|
||||
if i+1 < len(playlist) else "(the end)")
|
||||
i = play_one_file(speaker, i, join(dir, f), folder, f[trim:-4], next_up)
|
||||
while 0 <= i < len(playlist):
|
||||
filename = playlist[i]
|
||||
i = play_one_file(i, join(location, filename), folder, filename[trim:-4])
|
||||
speaker.stop()
|
||||
clear_display()
|
||||
|
||||
|
||||
def longest_common_prefix(seq):
|
||||
"""Find the longest common prefix between all items in sequence"""
|
||||
seq0 = seq[0]
|
||||
for i in range(0, len(seq0)):
|
||||
for i, seq0i in enumerate(seq0):
|
||||
for j in seq:
|
||||
if len(j) < i or j[i] != seq0[i]: return i
|
||||
if len(j) < i or j[i] != seq0i:
|
||||
return i
|
||||
return len(seq0)
|
||||
|
||||
def play_folder(dir):
|
||||
playlist = [d for d in os.listdir(dir) if d.lower().endswith('.mp3')]
|
||||
def play_folder(location):
|
||||
"""Play everything within a given folder"""
|
||||
playlist = [d for d in os.listdir(location) if d.lower().endswith('.mp3')]
|
||||
if not playlist:
|
||||
# hmm, no mp3s in a folder? Well, don't crash okay?
|
||||
del playlist
|
||||
|
|
@ -339,24 +378,27 @@ def play_folder(dir):
|
|||
playlist.sort()
|
||||
trim = longest_common_prefix(playlist)
|
||||
enable.value = True
|
||||
play_all(playlist, folder=dir.split('/')[-1], trim=trim, dir=dir)
|
||||
play_all(playlist, folder=location.split('/')[-1], trim=trim, location=location)
|
||||
enable.value = False
|
||||
|
||||
try:
|
||||
mount_sd()
|
||||
except OSError as detail:
|
||||
t = adafruit_display_text.label.Label(font,
|
||||
text="%s\n\nInsert or re-seat\nSD card\nthen press reset"
|
||||
% detail.args[0])
|
||||
t.x = 8
|
||||
t.y = board.DISPLAY.height // 2
|
||||
g = displayio.Group()
|
||||
g.append(t)
|
||||
board.DISPLAY.show(g)
|
||||
|
||||
def main():
|
||||
"""The main function of the player"""
|
||||
try:
|
||||
mount_sd()
|
||||
except OSError as detail:
|
||||
text = "%s\n\nInsert or re-seat\nSD card\nthen press reset" % detail.args[0]
|
||||
error_text = adafruit_display_text.label.Label(font, text)
|
||||
error_text.x = 8
|
||||
error_text.y = board.DISPLAY.height // 2
|
||||
g = displayio.Group()
|
||||
g.append(error_text)
|
||||
board.DISPLAY.show(g)
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
while True:
|
||||
folder = choose_folder()
|
||||
play_folder(folder)
|
||||
|
||||
folder = choose_folder()
|
||||
play_folder(folder)
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,32 @@
|
|||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2020 Jeff Epler for Adafruit Industries LLC
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
"""
|
||||
Make a key (button) repeat when held down
|
||||
"""
|
||||
|
||||
import time
|
||||
class KeyRepeat:
|
||||
"""Track the state of a button and, while it is held, output a press every
|
||||
'rate' seconds"""
|
||||
def __init__(self, getter, rate=0.5):
|
||||
self.getter = getter
|
||||
self.rate_ns = round(rate * 1e9)
|
||||
|
|
@ -7,6 +34,8 @@ class KeyRepeat:
|
|||
|
||||
@property
|
||||
def value(self):
|
||||
"""True when a button is first pressed, or once every 'rate' seconds
|
||||
thereafter"""
|
||||
state = self.getter()
|
||||
if not state:
|
||||
self.next = -1
|
||||
|
|
|
|||
Loading…
Reference in a new issue