From 430a05b6cb6394aa728d03df1f74a8e2741de30c Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Mon, 8 Oct 2018 12:04:26 -0400 Subject: [PATCH 01/14] Initial commit. --- .gitignore | 6 + .pylintrc | 433 +++++++++++++++++++++++++++++++ .readthedocs.yml | 3 + .travis.yml | 34 +++ CODE_OF_CONDUCT.md | 127 +++++++++ LICENSE | 21 ++ README.md | 2 - README.rst | 97 +++++++ adafruit_slideshow.py | 209 +++++++++++++++ docs/_static/favicon.ico | Bin 0 -> 4414 bytes docs/api.rst | 8 + docs/conf.py | 160 ++++++++++++ docs/examples.rst | 8 + docs/index.rst | 48 ++++ examples/slideshow_simpletest.py | 13 + requirements.txt | 3 + 16 files changed, 1170 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .pylintrc create mode 100644 .readthedocs.yml create mode 100644 .travis.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE delete mode 100644 README.md create mode 100644 README.rst create mode 100755 adafruit_slideshow.py create mode 100644 docs/_static/favicon.ico create mode 100644 docs/api.rst create mode 100644 docs/conf.py create mode 100644 docs/examples.rst create mode 100644 docs/index.rst create mode 100644 examples/slideshow_simpletest.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dd8629 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__ +_build +*.pyc +.env +build* +bundles diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..039eaec --- /dev/null +++ b/.pylintrc @@ -0,0 +1,433 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +# jobs=1 +jobs=2 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call +disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +# notes=FIXME,XXX,TODO +notes=FIXME,XXX + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=board + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +# expected-line-ending-format= +expected-line-ending-format=LF + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming hint for argument names +argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct argument names +argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for attribute names +attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct attribute names +attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class names +# class-name-hint=[A-Z_][a-zA-Z0-9]+$ +class-name-hint=[A-Z_][a-zA-Z0-9_]+$ + +# Regular expression matching correct class names +# class-rgx=[A-Z_][a-zA-Z0-9]+$ +class-rgx=[A-Z_][a-zA-Z0-9_]+$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming hint for function names +function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct function names +function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Good variable names which should always be accepted, separated by a comma +# good-names=i,j,k,ex,Run,_ +good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for method names +method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct method names +method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming hint for variable names +variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct variable names +variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +# max-attributes=7 +max-attributes=11 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=1 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..f4243ad --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,3 @@ +python: + version: 3 +requirements_file: requirements.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f0b9855 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +# This is a common .travis.yml for generating library release zip files for +# CircuitPython library releases using circuitpython-build-tools. +# See https://github.com/adafruit/circuitpython-build-tools for detailed setup +# instructions. + +dist: trusty +sudo: false +language: python +python: + - "3.6" + +cache: + pip: true + +deploy: + provider: releases + api_key: $GITHUB_TOKEN + file_glob: true + file: $TRAVIS_BUILD_DIR/bundles/* + skip_cleanup: true + overwrite: true + on: + tags: true + +install: + - pip install -r requirements.txt + - pip install circuitpython-build-tools Sphinx sphinx-rtd-theme + - pip install --force-reinstall pylint==1.9.2 + +script: + - pylint adafruit_slideshow.py + - ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace examples/*.py) + - circuitpython-build-bundles --filename_prefix adafruit-circuitpython-slideshow --library_location . + - cd docs && sphinx-build -E -W -b html . _build/html diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8ee6e44 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,127 @@ +# Adafruit Community Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and leaders pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level or type of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +We are committed to providing a friendly, safe and welcoming environment for +all. + +Examples of behavior that contributes to creating a positive environment +include: + +* Be kind and courteous to others +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Collaborating with other community members +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and sexual attention or advances +* The use of inappropriate images, including in a community member's avatar +* The use of inappropriate language, including in a community member's nickname +* Any spamming, flaming, baiting or other attention-stealing behavior +* Excessive or unwelcome helping; answering outside the scope of the question + asked +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate + +The goal of the standards and moderation guidelines outlined here is to build +and maintain a respectful community. We ask that you don’t just aim to be +"technically unimpeachable", but rather try to be your best self. + +We value many things beyond technical expertise, including collaboration and +supporting others within our community. Providing a positive experience for +other community members can have a much more significant impact than simply +providing the correct answer. + +## Our Responsibilities + +Project leaders are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project leaders have the right and responsibility to remove, edit, or +reject messages, comments, commits, code, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any community member for other behaviors that they deem +inappropriate, threatening, offensive, or harmful. + +## Moderation + +Instances of behaviors that violate the Adafruit Community Code of Conduct +may be reported by any member of the community. Community members are +encouraged to report these situations, including situations they witness +involving other community members. + +You may report in the following ways: + +In any situation, you may send an email to . + +On the Adafruit Discord, you may send an open message from any channel +to all Community Helpers by tagging @community helpers. You may also send an +open message from any channel, or a direct message to @kattni#1507, +@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or +@Andon#8175. + +Email and direct message reports will be kept confidential. + +In situations on Discord where the issue is particularly egregious, possibly +illegal, requires immediate action, or violates the Discord terms of service, +you should also report the message directly to Discord. + +These are the steps for upholding our community’s standards of conduct. + +1. Any member of the community may report any situation that violates the +Adafruit Community Code of Conduct. All reports will be reviewed and +investigated. +2. If the behavior is an egregious violation, the community member who +committed the violation may be banned immediately, without warning. +3. Otherwise, moderators will first respond to such behavior with a warning. +4. Moderators follow a soft "three strikes" policy - the community member may +be given another chance, if they are receptive to the warning and change their +behavior. +5. If the community member is unreceptive or unreasonable when warned by a +moderator, or the warning goes unheeded, they may be banned for a first or +second offense. Repeated offenses will result in the community member being +banned. + +## Scope + +This Code of Conduct and the enforcement policies listed above apply to all +Adafruit Community venues. This includes but is not limited to any community +spaces (both public and private), the entire Adafruit Discord server, and +Adafruit GitHub repositories. Examples of Adafruit Community spaces include +but are not limited to meet-ups, audio chats on the Adafruit Discord, or +interaction at a conference. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. As a community +member, you are representing our community, and are expected to behave +accordingly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +, +and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). + +For other projects adopting the Adafruit Community Code of +Conduct, please contact the maintainers of those projects for enforcement. +If you wish to use this code of conduct for your own project, consider +explicitly mentioning your moderation policy or making a copy with your +own moderation policy so as to avoid confusion. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..56c23eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Kattni Rembor for Adafruit Industries + +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. diff --git a/README.md b/README.md deleted file mode 100644 index 2ed91d0..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Adafruit_CircuitPython_Slideshow -CircuitPython helper library for displaying a slideshow of images on a board with a built-in display. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..57cf764 --- /dev/null +++ b/README.rst @@ -0,0 +1,97 @@ +Introduction +============ + +.. image:: https://readthedocs.org/projects/adafruit-circuitpython-slideshow/badge/?version=latest + :target: https://circuitpython.readthedocs.io/projects/slideshow/en/latest/ + :alt: Documentation Status + +.. image:: https://img.shields.io/discord/327254708534116352.svg + :target: https://discord.gg/nBQh6qu + :alt: Discord + +.. image:: https://travis-ci.org/adafruit/adafruit_CircuitPython_Slideshow.svg?branch=master + :target: https://travis-ci.org/adafruit/adafruit_CircuitPython_Slideshow + :alt: Build Status + +CircuitPython helper library for displaying a slideshow of images on a board with a built-in +display. + +Dependencies +============= +This driver depends on: + +* `Adafruit CircuitPython `_ + +Please ensure all dependencies are available on the CircuitPython filesystem. +This is easily achieved by downloading +`the Adafruit library and driver bundle `_. + +Usage Example +============= + +.. code-block::python + import adafruit_slideshow + + slideshow = adafruit_slideshow.SlideShow() + slideshow.loop = False + slideshow.order = slideshow.ALPHA + + while slideshow.update(): + pass + +Contributing +============ + +Contributions are welcome! Please read our `Code of Conduct +`_ +before contributing to help this project stay welcoming. + +Building locally +================ + +Zip release files +----------------- + +To build this library locally you'll need to install the +`circuitpython-build-tools `_ package. + +.. code-block:: shell + + python3 -m venv .env + source .env/bin/activate + pip install circuitpython-build-tools + +Once installed, make sure you are in the virtual environment: + +.. code-block:: shell + + source .env/bin/activate + +Then run the build: + +.. code-block:: shell + + circuitpython-build-bundles --filename_prefix adafruit-circuitpython-slideshow --library_location . + +Sphinx documentation +----------------------- + +Sphinx is used to build the documentation based on rST files and comments in the code. First, +install dependencies (feel free to reuse the virtual environment from above): + +.. code-block:: shell + + python3 -m venv .env + source .env/bin/activate + pip install Sphinx sphinx-rtd-theme + +Now, once you have the virtual environment activated: + +.. code-block:: shell + + cd docs + sphinx-build -E -W -b html . _build/html + +This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to +view them. It will also (due to -W) error out on any warning like Travis will. This is a good way to +locally verify it will pass. diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py new file mode 100755 index 0000000..d596dee --- /dev/null +++ b/adafruit_slideshow.py @@ -0,0 +1,209 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Kattni Rembor for Adafruit Industries +# +# 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. + +""" +`adafruit_slideshow` +==================================================== +CircuitPython helper library for displaying a slideshow of images on a board with a built-in +display. + +* Author(s): Kattni Rembor, Carter Nelson + +Implementation Notes +-------------------- + +**Hardware:** + + * `Adafruit Hollowing M0 Express `_" + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" +import time +import os +import random +import board +import displayio +import pulseio + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Slideshow.git" + + +class SlideShow(): + """Class for displaying a slideshow of .bmp images on boards with built-in + displays. + + :param str folder: Specify the folder containing the image files, in quotes. Default is + the root directory, ``"/"``. + + :param order: The order in which the images display. You can choose random (``RANDOM``) or + alphabetical (``ALPHA``). Default is ``RANDOM``. + + :param loop: Specify whether to loop the images or play through the list once. `True` + if slideshow will continue to loop, ``False`` if it will play only once. + Default is ``True``. + + :param int dwell: The number of seconds each image displays, in seconds. Default is 3. + + Example code for Hollowing Express. With this example, the slideshow will play through once + in alphabetical order: + + .. code-block:: python + + import adafruit_slideshow + + slideshow = adafruit_slideshow.SlideShow() + slideshow.loop = False + slideshow.order = slideshow.ALPHA + + while slideshow.update(): + pass + """ + MAX_BRIGHTNESS = 2 ** 15 + + ALPHA = 0 + RANDOM = 1 + + FADE_IN = 0 + SHOW_IMG = 1 + FADE_OUT = 2 + LOAD_IMG = 3 + + def __init__(self, folder="/", order=RANDOM, loop=True, dwell=3): + self._group = displayio.Group() + board.DISPLAY.show(self._group) + self._backlight = pulseio.PWMOut(board.TFT_BACKLIGHT) + self._folder = folder + self._order = order + self._loop = loop + self._dwell = dwell + self._current_state = self.LOAD_IMG + self._img_start = None + self._file_list = None + self._images = None + self._load_images() + + @property + def folder(self): + """Specifies the folder containing the image files. Default is the root directory, ``"/"``. + """ + return self._folder + + @folder.setter + def folder(self, folder): + self._folder = folder + + @property + def order(self): + """Specifies the order in which the images are displayed. Options are random (``RANDOM``) or + alphabetical (``ALPHA``). Default is ``RANDOM``.""" + return self._order + + @order.setter + def order(self, order): + self._order = order + + @property + def loop(self): + """Specifies whether to loop through the images continuously or play through the list once. + ``True`` will continue to loop, ``False`` will play only once. Default is `True`.""" + return self._loop + + @loop.setter + def loop(self, loop): + self._loop = loop + + @property + def dwell(self): + """The number of seconds each image displays, in seconds. Default is 3.""" + return self._dwell + + @dwell.setter + def dwell(self, dwell): + self._dwell = dwell + + def update(self): + """Updates the slideshow to the next image.""" + now = time.monotonic() + if self._current_state == self.FADE_IN: + steps = 100 + for b in range(steps): + self._backlight.duty_cycle = b * SlideShow.MAX_BRIGHTNESS // steps + time.sleep(0.01) + self._current_state = self.SHOW_IMG + self._img_start = time.monotonic() + + if self._current_state == self.SHOW_IMG: + if now - self._img_start > self._dwell: + self._current_state = self.FADE_OUT + + if self._current_state == self.FADE_OUT: + steps = 100 + for b in range(steps, -1, -1): + self._backlight.duty_cycle = b * SlideShow.MAX_BRIGHTNESS // steps + time.sleep(0.01) + self._group.pop() + self._current_state = self.LOAD_IMG + + if self._current_state == self.LOAD_IMG: + try: + imagename = next(self._images) + except StopIteration: + return False + try: + self._show_bmp(imagename) + self._current_state = self.FADE_IN + except ValueError as error: + print("Incompatible image:", imagename, str(error)) + + return True + + def _show_bmp(self, imagename): + """Opens and loads the image onto the display.""" + with open(imagename, "rb") as image: + odb = displayio.OnDiskBitmap(image) + face = displayio.Sprite(odb, pixel_shader=displayio.ColorConverter(), position=(0, 0)) + self._group.append(face) + board.DISPLAY.wait_for_frame() + + def _get_next_image(self): + """Cycles through the list of images.""" + while True: + for image in self._file_list: + yield image + if not self._loop: + return + + def _load_images(self): + """Loads the list of images to be displayed in alphabetical or random order.""" + self._file_list = self._get_filenames() + if self._order == SlideShow.RANDOM: + self._file_list = sorted(self._file_list, key=lambda x: random.random()) + self._images = self._get_next_image() + + def _get_filenames(self, extension="bmp"): + """Creates a list of available image files ending with .bmp in the specified folder.""" + return list(filter(lambda x: x.endswith(extension), os.listdir(self._folder))) diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5aca98376a1f7e593ebd9cf41a808512c2135635 GIT binary patch literal 4414 zcmd^BX;4#F6n=SG-XmlONeGrD5E6J{RVh+e928U#MG!$jWvO+UsvWh`x&VqGNx*en zx=qox7Dqv{kPwo%fZC$dDwVpRtz{HzTkSs8QhG0)%Y=-3@Kt!4ag|JcIo?$-F|?bXVS9UDUyev>MVZQ(H8K4#;BQW-t2CPorj8^KJrMX}QK zp+e<;4ldpXz~=)2GxNy811&)gt-}Q*yVQpsxr@VMoA##{)$1~=bZ1MmjeFw?uT(`8 z^g=09<=zW%r%buwN%iHtuKSg|+r7HkT0PYN*_u9k1;^Ss-Z!RBfJ?Un4w(awqp2b3 z%+myoFis_lTlCrGx2z$0BQdh+7?!JK#9K9@Z!VrG zNj6gK5r(b4?YDOLw|DPRoN7bdP{(>GEG41YcN~4r_SUHU2hgVtUwZG@s%edC;k7Sn zC)RvEnlq~raE2mY2ko64^m1KQL}3riixh?#J{o)IT+K-RdHae2eRX91-+g!y`8^># z-zI0ir>P%Xon)!@xp-BK2bDYUB9k613NRrY6%lVjbFcQc*pRqiK~8xtkNPLxt}e?&QsTB}^!39t_%Qb)~Ukn0O%iC;zt z<&A-y;3h++)>c1br`5VFM~5(83!HKx$L+my8sW_c#@x*|*vB1yU)_dt3vH;2hqPWx zAl^6@?ipx&U7pf`a*>Yq6C85nb+B=Fnn+(id$W#WB^uHAcZVG`qg;rWB}ubvi(Y>D z$ei>REw$#xp0SHAd^|1hq&9HJ=jKK8^zTH~nk)G?yUcmTh9vUM6Y0LMw4(gYVY$D$ zGl&WY&H<)BbJ&3sYbKjx1j^=3-0Q#f^}(aP1?8^`&FUWMp|rmtpK)bLQ1Zo?^s4jqK=Lfg*9&geMGVQ z#^-*!V`fG@;H&{M9S8%+;|h&Qrxym0Ar>WT4BCVLR8cGXF=JmEYN(sNT(9vl+S|%g z8r7nXQ(95i^`=+XHo|){$vf2$?=`F$^&wFlYXyXg$B{a>$-Fp+V}+D;9k=~Xl~?C4 zAB-;RKXdUzBJE{V&d&%R>aEfFe;vxqI$0@hwVM}gFeQR@j}a>DDxR+n+-*6|_)k%% z*mSpDV|=5I9!&VC&9tD%fcVygWZV!iIo2qFtm#!*(s|@ZT33*Ad;+<|3^+yrp*;oH zBSYLV(H1zTU?2WjrCQoQW)Z>J2a=dTriuvezBmu16`tM2fm7Q@d4^iqII-xFpwHGI zn9CL}QE*1vdj2PX{PIuqOe5dracsciH6OlAZATvE8rj6ykqdIjal2 z0S0S~PwHb-5?OQ-tU-^KTG@XNrEVSvo|HIP?H;7ZhYeZkhSqh-{reE!5di;1zk$#Y zCe7rOnlzFYJ6Z#Hm$GoidKB=2HBCwm`BbZVeZY4ukmG%1uz7p2URs6c9j-Gjj^oQV zsdDb3@k2e`C$1I5ML5U0Qs0C1GAp^?!*`=|Nm(vWz3j*j*8ucum2;r0^-6Aca=Gv) zc%}&;!+_*S2tlnnJnz0EKeRmw-Y!@9ob!XQBwiv}^u9MkaXHvM=!<3YX;+2#5Cj5pp?FEK750S3BgeSDtaE^ zXUM@xoV6yBFKfzvY20V&Lr0yC + +.. toctree:: + :caption: Other Links + + Download + CircuitPython Reference Documentation + CircuitPython Support Forum + Discord Chat + Adafruit Learning System + Adafruit Blog + Adafruit Store + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/examples/slideshow_simpletest.py b/examples/slideshow_simpletest.py new file mode 100644 index 0000000..1ff3d7f --- /dev/null +++ b/examples/slideshow_simpletest.py @@ -0,0 +1,13 @@ +import adafruit_slideshow + +# Create the slideshow object +slideshow = adafruit_slideshow.SlideShow() + +# Set it to play through only once. +slideshow.loop = False + +# Set the order to alphabetical. +slideshow.order = slideshow.ALPHA + +while slideshow.update(): + pass diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d5d88a1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Adafruit-Blinka +adafruit-circuitpython-busdevice +adafruit-circuitpython-register From dc5009187b858bb6775e7b32e003549f632a5b19 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Mon, 8 Oct 2018 12:12:36 -0400 Subject: [PATCH 02/14] Removed unnecessary requirements --- requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index d5d88a1..6b4e64a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1 @@ -Adafruit-Blinka -adafruit-circuitpython-busdevice -adafruit-circuitpython-register +Adafruit-Blinka \ No newline at end of file From a23a82ea12c7b47889ee92ef2a74387eba4d1d34 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Mon, 8 Oct 2018 12:16:31 -0400 Subject: [PATCH 03/14] Added space in code-block --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 57cf764..19f70ed 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ This is easily achieved by downloading Usage Example ============= -.. code-block::python +.. code-block:: python import adafruit_slideshow slideshow = adafruit_slideshow.SlideShow() From cce8983cc183dba81d8a71651239fabea8b54458 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Mon, 8 Oct 2018 12:23:57 -0400 Subject: [PATCH 04/14] first go at making Sphinx happy --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 19f70ed..162c136 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,7 @@ Usage Example ============= .. code-block:: python + import adafruit_slideshow slideshow = adafruit_slideshow.SlideShow() From bfb827a5af41834c38a9441bf2aa4e475978a0b3 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Tue, 9 Oct 2018 16:29:27 -0400 Subject: [PATCH 05/14] Added auto_advance and backlight_level control Added ability to choose whether slideshow will automatically cycle through the images or whether you would like to specify an input to manually cycle through the slideshow. Added backlight brightness level control. Added ability to cycle forwards or backwards through image list. Added ability to turn off the fade in/out effects during image transition - this means the redraw effect of the image changing is visible, but it also means you can advance more quickly through the list if you choose to. Documentation updated. Various linting fixes. --- adafruit_slideshow.py | 255 ++++++++++++++++++++++++++++++------------ 1 file changed, 185 insertions(+), 70 deletions(-) diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index d596dee..1e14e92 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -26,14 +26,14 @@ CircuitPython helper library for displaying a slideshow of images on a board with a built-in display. -* Author(s): Kattni Rembor, Carter Nelson +* Author(s): Kattni Rembor, Carter Nelson, Roy Hooper Implementation Notes -------------------- **Hardware:** - * `Adafruit Hollowing M0 Express `_" + * `Adafruit Hollowing M0 Express `_ **Software and Dependencies:** @@ -52,9 +52,33 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Slideshow.git" -class SlideShow(): - """Class for displaying a slideshow of .bmp images on boards with built-in - displays. +_FADE_IN = 0 +_SHOW_IMG = 1 +_FADE_OUT = 2 +_LOAD_IMG = 3 +_WAIT = 4 + + +class PlayBackMode: + """Helper class for handling playback mode.""" + # pylint: disable=too-few-public-methods + ALPHA = 0 + RANDOM = 1 + # pylint: enable=too-few-public-methods + + +class PlayBackDirection: + """Helper class for handling playback direction.""" + # pylint: disable=too-few-public-methods + BACKWARD = -1 + FORWARD = 1 + # pylint: enable=too-few-public-methods + + +class SlideShow: + # pylint: disable=too-many-instance-attributes + """ + Class for displaying a slideshow of .bmp images on boards with built-in displays. :param str folder: Specify the folder containing the image files, in quotes. Default is the root directory, ``"/"``. @@ -68,6 +92,15 @@ class SlideShow(): :param int dwell: The number of seconds each image displays, in seconds. Default is 3. + :param fade_effect: Specify whether to include the fade effect between images. ``True`` + tells the code to fade the backlight up and down between image display + transitions. ``False`` maintains max brightness on the backlight between + image transitions. Default is ``True``. + + :param auto_advance: Specify whether to automatically advance after dwell seconds. ``True`` + if slideshow should auto play, ``False`` if you want to control advancement + manually. Default is ``True``. + Example code for Hollowing Express. With this example, the slideshow will play through once in alphabetical order: @@ -77,44 +110,76 @@ class SlideShow(): slideshow = adafruit_slideshow.SlideShow() slideshow.loop = False - slideshow.order = slideshow.ALPHA + slideshow.order = PlayBackMode.ALPHA while slideshow.update(): pass - """ - MAX_BRIGHTNESS = 2 ** 15 - ALPHA = 0 - RANDOM = 1 + Example code for Hollowing Express. Sets ``dwell`` to 0 seconds, turns ``auto_advance`` off, + and uses capacitive touch to advance backwards and forwards through the images and to control + the brightness level of the backlight: + + .. code-block:: python + + from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection + import touchio + import board + + forward_button = touchio.TouchIn(board.TOUCH4) + back_button = touchio.TouchIn(board.TOUCH1) + + brightness_up = touchio.TouchIn(board.TOUCH3) + brightness_down = touchio.TouchIn(board.TOUCH2) - FADE_IN = 0 - SHOW_IMG = 1 - FADE_OUT = 2 - LOAD_IMG = 3 + slideshow = SlideShow() + slideshow.auto_advance = False + slideshow.dwell = 0 - def __init__(self, folder="/", order=RANDOM, loop=True, dwell=3): + while True: + if forward_button.value: + slideshow.advance() + if back_button.value: + slideshow.advance(direction=PlayBackDirection.BACKWARD) + + if brightness_up.value: + slideshow.backlight_level_up() + elif brightness_down.value: + slideshow.backlight_level_down() + slideshow.update() + """ + + _max_brightness = 2 ** 15 + + # pylint: disable=too-many-arguments + def __init__(self, folder="/", order=PlayBackMode.RANDOM, loop=True, dwell=3, fade_effect=True, + auto_advance=True): self._group = displayio.Group() board.DISPLAY.show(self._group) self._backlight = pulseio.PWMOut(board.TFT_BACKLIGHT) - self._folder = folder - self._order = order - self._loop = loop - self._dwell = dwell - self._current_state = self.LOAD_IMG + self.folder = folder + """Specifies the folder containing the image files. Default is the root directory, ``"/"``. + """ + self.loop = loop + """Specifies whether to loop through the images continuously or play through the list once. + ``True`` will continue to loop, ``False`` will play only once. Default is `True`.""" + self.dwell = dwell + """The number of seconds each image displays, in seconds. Default is 3.""" + self.fade_effect = fade_effect + self._current_state = _LOAD_IMG self._img_start = None self._file_list = None self._images = None self._load_images() - - @property - def folder(self): - """Specifies the folder containing the image files. Default is the root directory, ``"/"``. - """ - return self._folder - - @folder.setter - def folder(self, folder): - self._folder = folder + self._order = None + self.order = order + self.direction = PlayBackDirection.FORWARD + """Specify the playback direction. Default is ``PlayBackDirection.FORWARD``. Can also be + ``PlayBackDirection.BACKWARD``.""" + self.auto_advance = auto_advance + """Enable auto-advance based on dwell time. Set to ``False`` to manually control.""" + self._update_order() + self._current_backlight_level = self._max_brightness + # pylint: enable=too-many-arguments @property def order(self): @@ -124,63 +189,107 @@ def order(self): @order.setter def order(self, order): + if order not in [PlayBackMode.ALPHA, PlayBackMode.RANDOM]: + raise ValueError("Order must be either 'RANDOM' or 'ALPHA'") + if order == self._order: + return self._order = order + self._update_order() - @property - def loop(self): - """Specifies whether to loop through the images continuously or play through the list once. - ``True`` will continue to loop, ``False`` will play only once. Default is `True`.""" - return self._loop + def _update_order(self): + if self.order == PlayBackMode.ALPHA: + self._file_list = sorted(self._file_list) + if self.order == PlayBackMode.RANDOM: + self._file_list = sorted(self._file_list, key=lambda x: random.random()) - @loop.setter - def loop(self, loop): - self._loop = loop + def backlight_level_up(self, step=16): + """Increases the backlight brightness level. - @property - def dwell(self): - """The number of seconds each image displays, in seconds. Default is 3.""" - return self._dwell + :param step: Specify the number of steps by which current backlight level will be increased. + Default is 16. + """ + self._max_brightness += step + if self._max_brightness >= 2 ** 16: + self._max_brightness = 2 ** 16 - 1 + self._current_backlight_level = self._max_brightness + return self._current_backlight_level + + def backlight_level_down(self, step=16): + """Decreases the backlight brightness level. - @dwell.setter - def dwell(self, dwell): - self._dwell = dwell + :param step: Specify the number of steps by which current backlight level will be decreased. + Default is 16. + """ + self._max_brightness -= step + if self._max_brightness < 0: + self._max_brightness = 0 + self._current_backlight_level = self._max_brightness + return self._current_backlight_level + + def _fade_up(self): + steps = 100 + for b in range(steps): + self._backlight.duty_cycle = b * self._current_backlight_level // steps + time.sleep(0.01) + + def _fade_down(self): + steps = 100 + for b in range(steps, -1, -1): + self._backlight.duty_cycle = b * self._current_backlight_level // steps + time.sleep(0.01) def update(self): """Updates the slideshow to the next image.""" now = time.monotonic() - if self._current_state == self.FADE_IN: - steps = 100 - for b in range(steps): - self._backlight.duty_cycle = b * SlideShow.MAX_BRIGHTNESS // steps - time.sleep(0.01) - self._current_state = self.SHOW_IMG + if self._current_state == _FADE_IN: + if self.fade_effect: + self._fade_up() + else: + self._backlight.duty_cycle = self._current_backlight_level + self._current_state = _SHOW_IMG self._img_start = time.monotonic() - if self._current_state == self.SHOW_IMG: - if now - self._img_start > self._dwell: - self._current_state = self.FADE_OUT + if self._current_state == _SHOW_IMG: + self._backlight.duty_cycle = self._current_backlight_level + if now - self._img_start > self.dwell: + self._current_state = _FADE_OUT if self.auto_advance else _WAIT - if self._current_state == self.FADE_OUT: - steps = 100 - for b in range(steps, -1, -1): - self._backlight.duty_cycle = b * SlideShow.MAX_BRIGHTNESS // steps - time.sleep(0.01) + if self._current_state == _WAIT: + self._backlight.duty_cycle = self._current_backlight_level + + if self._current_state == _FADE_OUT: + if self.fade_effect: + self._fade_down() + else: + self._backlight.duty_cycle = self._current_backlight_level self._group.pop() - self._current_state = self.LOAD_IMG + self._current_state = _LOAD_IMG - if self._current_state == self.LOAD_IMG: + if self._current_state == _LOAD_IMG: try: imagename = next(self._images) except StopIteration: return False try: self._show_bmp(imagename) - self._current_state = self.FADE_IN + self._current_state = _FADE_IN except ValueError as error: print("Incompatible image:", imagename, str(error)) return True + def advance(self, direction=None): + """Displays the next image when `auto_advance` is False. + + Does not advance the image until the current image change is over. + + :param int direction: Change the playback direction when advancing to the next image. + """ + if direction: + self.direction = direction + if self._current_state == _WAIT: + self._current_state = _FADE_OUT + def _show_bmp(self, imagename): """Opens and loads the image onto the display.""" with open(imagename, "rb") as image: @@ -191,19 +300,25 @@ def _show_bmp(self, imagename): def _get_next_image(self): """Cycles through the list of images.""" + index = -1 if self.direction == PlayBackDirection.FORWARD else len(self._file_list) while True: - for image in self._file_list: - yield image - if not self._loop: + wrapped = False + index += self.direction + if index < 0: + index = len(self._file_list) - 1 + wrapped = True + elif index >= len(self._file_list): + index = 0 + wrapped = True + yield self._file_list[index] + if wrapped and not self.loop: return def _load_images(self): - """Loads the list of images to be displayed in alphabetical or random order.""" + """Loads the list of images to be displayed.""" self._file_list = self._get_filenames() - if self._order == SlideShow.RANDOM: - self._file_list = sorted(self._file_list, key=lambda x: random.random()) - self._images = self._get_next_image() + self._images = self._get_next_image() def _get_filenames(self, extension="bmp"): """Creates a list of available image files ending with .bmp in the specified folder.""" - return list(filter(lambda x: x.endswith(extension), os.listdir(self._folder))) + return list(filter(lambda x: x.endswith(extension), os.listdir(self.folder))) From 346697b1aaf5c3b07ce02d57817fa3ad8af7193c Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Tue, 9 Oct 2018 16:35:12 -0400 Subject: [PATCH 06/14] Added touch input example --- examples/slideshow_touch.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/slideshow_touch.py diff --git a/examples/slideshow_touch.py b/examples/slideshow_touch.py new file mode 100644 index 0000000..b00dd89 --- /dev/null +++ b/examples/slideshow_touch.py @@ -0,0 +1,26 @@ +from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection +import touchio +import board + +forward_button = touchio.TouchIn(board.TOUCH4) +back_button = touchio.TouchIn(board.TOUCH1) + +brightness_up = touchio.TouchIn(board.TOUCH3) +brightness_down = touchio.TouchIn(board.TOUCH2) + +slideshow = SlideShow() +slideshow.order = PlayBackMode.ALPHA +slideshow.auto_advance = False +slideshow.dwell = 0 + +while True: + if forward_button.value: + slideshow.advance() + if back_button.value: + slideshow.advance(direction=PlayBackDirection.BACKWARD) + + if brightness_up.value: + slideshow.backlight_level_up() + elif brightness_down.value: + slideshow.backlight_level_down() + slideshow.update() From 229df341bc55cc4b539230d786ebcc88a6c4cfe7 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Tue, 9 Oct 2018 16:38:38 -0400 Subject: [PATCH 07/14] Linting and update to match API change --- examples/slideshow_simpletest.py | 6 +++--- examples/slideshow_touch.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/slideshow_simpletest.py b/examples/slideshow_simpletest.py index 1ff3d7f..8ee4139 100644 --- a/examples/slideshow_simpletest.py +++ b/examples/slideshow_simpletest.py @@ -1,13 +1,13 @@ -import adafruit_slideshow +from adafruit_slideshow import PlayBackMode, SlideShow # Create the slideshow object -slideshow = adafruit_slideshow.SlideShow() +slideshow = SlideShow() # Set it to play through only once. slideshow.loop = False # Set the order to alphabetical. -slideshow.order = slideshow.ALPHA +slideshow.order = PlayBackMode.ALPHA while slideshow.update(): pass diff --git a/examples/slideshow_touch.py b/examples/slideshow_touch.py index b00dd89..4934aff 100644 --- a/examples/slideshow_touch.py +++ b/examples/slideshow_touch.py @@ -1,6 +1,6 @@ +import board from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection import touchio -import board forward_button = touchio.TouchIn(board.TOUCH4) back_button = touchio.TouchIn(board.TOUCH1) From add0c484e4f940c177ab192f50df86fd545ef4ce Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Tue, 9 Oct 2018 17:23:22 -0400 Subject: [PATCH 08/14] Fixed typo and updated example. --- README.rst | 6 +++--- adafruit_slideshow.py | 6 +++--- docs/index.rst | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 162c136..3164d5b 100644 --- a/README.rst +++ b/README.rst @@ -31,11 +31,11 @@ Usage Example .. code-block:: python - import adafruit_slideshow + from adafruit_slideshow import PlayBackMode, SlideShow - slideshow = adafruit_slideshow.SlideShow() + slideshow = SlideShow() slideshow.loop = False - slideshow.order = slideshow.ALPHA + slideshow.order = PlayBackMode.ALPHA while slideshow.update(): pass diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 1e14e92..29d2b33 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -33,7 +33,7 @@ **Hardware:** - * `Adafruit Hollowing M0 Express `_ + * `Adafruit Hallowing M0 Express `_ **Software and Dependencies:** @@ -101,7 +101,7 @@ class SlideShow: if slideshow should auto play, ``False`` if you want to control advancement manually. Default is ``True``. - Example code for Hollowing Express. With this example, the slideshow will play through once + Example code for Hallowing Express. With this example, the slideshow will play through once in alphabetical order: .. code-block:: python @@ -115,7 +115,7 @@ class SlideShow: while slideshow.update(): pass - Example code for Hollowing Express. Sets ``dwell`` to 0 seconds, turns ``auto_advance`` off, + Example code for Hallowing Express. Sets ``dwell`` to 0 seconds, turns ``auto_advance`` off, and uses capacitive touch to advance backwards and forwards through the images and to control the brightness level of the backlight: diff --git a/docs/index.rst b/docs/index.rst index 2374169..5a0f8cb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,7 +27,7 @@ Table of Contents .. toctree:: :caption: Related Products - Adafruit Hollowing M0 Express + Adafruit Hallowing M0 Express .. toctree:: :caption: Other Links From 80faeaf897544354cc382c3e3605c24aa30906f2 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Tue, 9 Oct 2018 17:24:51 -0400 Subject: [PATCH 09/14] Updated the REST of the example... --- adafruit_slideshow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 29d2b33..0a83f1c 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -106,9 +106,9 @@ class SlideShow: .. code-block:: python - import adafruit_slideshow + from adafruit_slideshow import PlayBackMode, SlideShow - slideshow = adafruit_slideshow.SlideShow() + slideshow = SlideShow() slideshow.loop = False slideshow.order = PlayBackMode.ALPHA From 00ef90f51c7e05ba5794af8ded0ffd5ec2344ee0 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 9 Oct 2018 16:56:19 -0700 Subject: [PATCH 10/14] Simplify the internals and brightness. --- README.rst | 10 +- adafruit_slideshow.py | 310 +++++++++++++++---------------- examples/slideshow_simpletest.py | 15 +- examples/slideshow_touch.py | 20 +- 4 files changed, 167 insertions(+), 188 deletions(-) diff --git a/README.rst b/README.rst index 3164d5b..423ad88 100644 --- a/README.rst +++ b/README.rst @@ -31,11 +31,13 @@ Usage Example .. code-block:: python - from adafruit_slideshow import PlayBackMode, SlideShow + from adafruit_slideshow import PlayBackOrder, SlideShow + import board + import pulseio - slideshow = SlideShow() - slideshow.loop = False - slideshow.order = PlayBackMode.ALPHA + # Create the slideshow object that plays through once alphabetically. + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + loop=False, order=PlayBackOrder.ALPHABETICAL) while slideshow.update(): pass diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 0a83f1c..0edf60b 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -23,8 +23,7 @@ """ `adafruit_slideshow` ==================================================== -CircuitPython helper library for displaying a slideshow of images on a board with a built-in -display. +CircuitPython helper library for displaying a slideshow of images on a display. * Author(s): Kattni Rembor, Carter Nelson, Roy Hooper @@ -44,73 +43,72 @@ import time import os import random -import board import displayio -import pulseio __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Slideshow.git" - -_FADE_IN = 0 -_SHOW_IMG = 1 -_FADE_OUT = 2 -_LOAD_IMG = 3 -_WAIT = 4 - - -class PlayBackMode: - """Helper class for handling playback mode.""" +class PlayBackOrder: + """Defines possible slideshow playback orders.""" # pylint: disable=too-few-public-methods - ALPHA = 0 + ALPHABETICAL = 0 + """Orders by alphabetical sort of filenames""" + RANDOM = 1 + """Randomly shuffles the images""" # pylint: enable=too-few-public-methods class PlayBackDirection: - """Helper class for handling playback direction.""" + """Defines possible slideshow playback directions.""" # pylint: disable=too-few-public-methods BACKWARD = -1 + """The next image is before the current image. When alphabetically sorted, this is towards A.""" + FORWARD = 1 + """The next image is after the current image. When alphabetically sorted, this is towards Z.""" # pylint: enable=too-few-public-methods class SlideShow: # pylint: disable=too-many-instance-attributes """ - Class for displaying a slideshow of .bmp images on boards with built-in displays. + Class for displaying a slideshow of .bmp images on displays. :param str folder: Specify the folder containing the image files, in quotes. Default is the root directory, ``"/"``. - :param order: The order in which the images display. You can choose random (``RANDOM``) or - alphabetical (``ALPHA``). Default is ``RANDOM``. + :param PlayBackOrder order: The order in which the images display. You can choose random (``RANDOM``) or + alphabetical (``ALPHABETICAL``). Default is ``ALPHABETICAL``. - :param loop: Specify whether to loop the images or play through the list once. `True` + :param bool loop: Specify whether to loop the images or play through the list once. `True` if slideshow will continue to loop, ``False`` if it will play only once. Default is ``True``. :param int dwell: The number of seconds each image displays, in seconds. Default is 3. - :param fade_effect: Specify whether to include the fade effect between images. ``True`` + :param bool fade_effect: Specify whether to include the fade effect between images. ``True`` tells the code to fade the backlight up and down between image display transitions. ``False`` maintains max brightness on the backlight between image transitions. Default is ``True``. - :param auto_advance: Specify whether to automatically advance after dwell seconds. ``True`` + :param bool auto_advance: Specify whether to automatically advance after dwell seconds. ``True`` if slideshow should auto play, ``False`` if you want to control advancement manually. Default is ``True``. + :param PlayBackDirection direction: The playback direction. + Example code for Hallowing Express. With this example, the slideshow will play through once in alphabetical order: .. code-block:: python - from adafruit_slideshow import PlayBackMode, SlideShow + from adafruit_slideshow import PlayBackOrder, SlideShow + import board + import pulseio - slideshow = SlideShow() - slideshow.loop = False - slideshow.order = PlayBackMode.ALPHA + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + loop=False, order=PlayBackOrder.ALPHABETICAL) while slideshow.update(): pass @@ -121,9 +119,10 @@ class SlideShow: .. code-block:: python - from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection + from adafruit_slideshow import PlayBackOrder, SlideShow, PlayBackDirection import touchio import board + import pulseio forward_button = touchio.TouchIn(board.TOUCH4) back_button = touchio.TouchIn(board.TOUCH1) @@ -131,194 +130,175 @@ class SlideShow: brightness_up = touchio.TouchIn(board.TOUCH3) brightness_down = touchio.TouchIn(board.TOUCH2) - slideshow = SlideShow() - slideshow.auto_advance = False - slideshow.dwell = 0 + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + auto_advance=False, dwell=0) while True: if forward_button.value: + slideshow.direction = PlayBackDirection.FORWARD slideshow.advance() if back_button.value: - slideshow.advance(direction=PlayBackDirection.BACKWARD) + slideshow.direction = PlayBackDirection.BACKWARD + slideshow.advance() if brightness_up.value: - slideshow.backlight_level_up() + slideshow.brightness += 0.001 elif brightness_down.value: - slideshow.backlight_level_down() - slideshow.update() + slideshow.brightness -= 0.001 """ - _max_brightness = 2 ** 15 - - # pylint: disable=too-many-arguments - def __init__(self, folder="/", order=PlayBackMode.RANDOM, loop=True, dwell=3, fade_effect=True, - auto_advance=True): - self._group = displayio.Group() - board.DISPLAY.show(self._group) - self._backlight = pulseio.PWMOut(board.TFT_BACKLIGHT) - self.folder = folder - """Specifies the folder containing the image files. Default is the root directory, ``"/"``. - """ + def __init__(self, display, backlight_pwm, *, folder="/", order=PlayBackOrder.ALPHABETICAL, loop=True, + dwell=3, fade_effect=True, auto_advance=True, direction=PlayBackDirection.FORWARD): self.loop = loop """Specifies whether to loop through the images continuously or play through the list once. - ``True`` will continue to loop, ``False`` will play only once. Default is `True`.""" + ``True`` will continue to loop, ``False`` will play only once.""" + self.dwell = dwell - """The number of seconds each image displays, in seconds. Default is 3.""" - self.fade_effect = fade_effect - self._current_state = _LOAD_IMG - self._img_start = None - self._file_list = None - self._images = None - self._load_images() - self._order = None - self.order = order - self.direction = PlayBackDirection.FORWARD + """The number of seconds each image displays, in seconds.""" + + self.direction = direction """Specify the playback direction. Default is ``PlayBackDirection.FORWARD``. Can also be ``PlayBackDirection.BACKWARD``.""" + self.auto_advance = auto_advance """Enable auto-advance based on dwell time. Set to ``False`` to manually control.""" - self._update_order() - self._current_backlight_level = self._max_brightness - # pylint: enable=too-many-arguments + + self.fade_effect = fade_effect + """Whether to include the fade effect between images. ``True`` tells the code to fade the + backlight up and down between image display transitions. ``False`` maintains max + brightness on the backlight between image transitions.""" + + # Load the image names before setting order so they can be reordered. + self._img_start = None + self._file_list = list(filter(lambda x: x.endswith("bmp"), os.listdir(folder))) + + self.order = order + """The order in which the images display. You can choose random (``RANDOM``) or + alphabetical (``ALPHA``).""" + + self._current_image = -1 + self._image_file = None + self._brightness = 0.5 + + # Setup the display + self._group = displayio.Group() + self._display = display + display.show(self._group) + + self._backlight_pwm = backlight_pwm + + # Show the first image + self.advance() @property def order(self): """Specifies the order in which the images are displayed. Options are random (``RANDOM``) or - alphabetical (``ALPHA``). Default is ``RANDOM``.""" + alphabetical (``ALPHABETICAL``). Default is ``RANDOM``.""" return self._order @order.setter def order(self, order): - if order not in [PlayBackMode.ALPHA, PlayBackMode.RANDOM]: - raise ValueError("Order must be either 'RANDOM' or 'ALPHA'") - if order == self._order: - return + if order not in [PlayBackOrder.ALPHABETICAL, PlayBackOrder.RANDOM]: + raise ValueError("Order must be either 'RANDOM' or 'ALPHABETICAL'") + self._order = order - self._update_order() + self._reorder_images() - def _update_order(self): - if self.order == PlayBackMode.ALPHA: + def _reorder_images(self): + if self.order == PlayBackOrder.ALPHABETICAL: self._file_list = sorted(self._file_list) - if self.order == PlayBackMode.RANDOM: + elif self.order == PlayBackOrder.RANDOM: self._file_list = sorted(self._file_list, key=lambda x: random.random()) - def backlight_level_up(self, step=16): - """Increases the backlight brightness level. + def _set_backlight(self, brightness): + full_brightness = 2 ** 16 - 1 + self._backlight_pwm.duty_cycle = int(full_brightness * brightness) - :param step: Specify the number of steps by which current backlight level will be increased. - Default is 16. - """ - self._max_brightness += step - if self._max_brightness >= 2 ** 16: - self._max_brightness = 2 ** 16 - 1 - self._current_backlight_level = self._max_brightness - return self._current_backlight_level - - def backlight_level_down(self, step=16): - """Decreases the backlight brightness level. - - :param step: Specify the number of steps by which current backlight level will be decreased. - Default is 16. - """ - self._max_brightness -= step - if self._max_brightness < 0: - self._max_brightness = 0 - self._current_backlight_level = self._max_brightness - return self._current_backlight_level + @property + def brightness(self): + """Brightness of the backlight when an image is displaying. Clamps to 0 to 1.0""" + return self._brightness + + @brightness.setter + def brightness(self, brightness): + if brightness < 0: + brightness = 0 + elif brightness > 1.0: + brightness = 1.0 + self._brightness = brightness + self._set_backlight(brightness) def _fade_up(self): + if not self.fade_effect: + self._set_backlight(self.brightness) + return steps = 100 - for b in range(steps): - self._backlight.duty_cycle = b * self._current_backlight_level // steps + for i in range(steps): + self._set_backlight(self.brightness * i / steps) time.sleep(0.01) def _fade_down(self): + if not self.fade_effect: + self._set_backlight(self.brightness) + return steps = 100 - for b in range(steps, -1, -1): - self._backlight.duty_cycle = b * self._current_backlight_level // steps + for i in range(steps, -1, -1): + self._set_backlight(self.brightness * i / steps) time.sleep(0.01) def update(self): """Updates the slideshow to the next image.""" now = time.monotonic() - if self._current_state == _FADE_IN: - if self.fade_effect: - self._fade_up() - else: - self._backlight.duty_cycle = self._current_backlight_level - self._current_state = _SHOW_IMG - self._img_start = time.monotonic() - - if self._current_state == _SHOW_IMG: - self._backlight.duty_cycle = self._current_backlight_level - if now - self._img_start > self.dwell: - self._current_state = _FADE_OUT if self.auto_advance else _WAIT + if not self.auto_advance or now - self._img_start < self.dwell: + return True - if self._current_state == _WAIT: - self._backlight.duty_cycle = self._current_backlight_level + return self.advance() - if self._current_state == _FADE_OUT: - if self.fade_effect: - self._fade_down() - else: - self._backlight.duty_cycle = self._current_backlight_level + def advance(self): + """Displays the next image. Returns True when a new image was displayed, False otherwise. + """ + if self._image_file: + self._fade_down() self._group.pop() - self._current_state = _LOAD_IMG - - if self._current_state == _LOAD_IMG: - try: - imagename = next(self._images) - except StopIteration: + self._image_file.close() + self._image_file = None + + self._current_image += self.direction + + # Try and load an OnDiskBitmap until a valid file is found or we run out of options. This + # loop stops because we either set odb or reduce the length of _file_list. + odb = None + while not odb and self._file_list: + if 0 <= self._current_image < len(self._file_list): + pass + elif not self.loop: return False + else: + image_count = len(self._file_list) - 1 + if self._current_image < 0: + self._current_image += image_count + elif self._current_image > image_count: + self._current_image -= image_count + self._reorder_images() + + imagename = self._file_list[self._current_image] + self._image_file = open(imagename, "rb") try: - self._show_bmp(imagename) - self._current_state = _FADE_IN + odb = displayio.OnDiskBitmap(self._image_file) except ValueError as error: - print("Incompatible image:", imagename, str(error)) + self._image_file.close() + self._image_file = None + print("remove", imagename) + del self._file_list[self._current_image] - return True + if not odb: + raise RuntimeError("No valid images") - def advance(self, direction=None): - """Displays the next image when `auto_advance` is False. + sprite = displayio.Sprite(odb, pixel_shader=displayio.ColorConverter(), position=(0, 0)) + self._group.append(sprite) + self._display.wait_for_frame() - Does not advance the image until the current image change is over. + self._fade_up() + self._img_start = time.monotonic() - :param int direction: Change the playback direction when advancing to the next image. - """ - if direction: - self.direction = direction - if self._current_state == _WAIT: - self._current_state = _FADE_OUT - - def _show_bmp(self, imagename): - """Opens and loads the image onto the display.""" - with open(imagename, "rb") as image: - odb = displayio.OnDiskBitmap(image) - face = displayio.Sprite(odb, pixel_shader=displayio.ColorConverter(), position=(0, 0)) - self._group.append(face) - board.DISPLAY.wait_for_frame() - - def _get_next_image(self): - """Cycles through the list of images.""" - index = -1 if self.direction == PlayBackDirection.FORWARD else len(self._file_list) - while True: - wrapped = False - index += self.direction - if index < 0: - index = len(self._file_list) - 1 - wrapped = True - elif index >= len(self._file_list): - index = 0 - wrapped = True - yield self._file_list[index] - if wrapped and not self.loop: - return - - def _load_images(self): - """Loads the list of images to be displayed.""" - self._file_list = self._get_filenames() - self._images = self._get_next_image() - - def _get_filenames(self, extension="bmp"): - """Creates a list of available image files ending with .bmp in the specified folder.""" - return list(filter(lambda x: x.endswith(extension), os.listdir(self.folder))) + return True diff --git a/examples/slideshow_simpletest.py b/examples/slideshow_simpletest.py index 8ee4139..8b15c75 100644 --- a/examples/slideshow_simpletest.py +++ b/examples/slideshow_simpletest.py @@ -1,13 +1,10 @@ -from adafruit_slideshow import PlayBackMode, SlideShow +from adafruit_slideshow import PlayBackOrder, SlideShow +import board +import pulseio -# Create the slideshow object -slideshow = SlideShow() - -# Set it to play through only once. -slideshow.loop = False - -# Set the order to alphabetical. -slideshow.order = PlayBackMode.ALPHA +# Create the slideshow object that plays through once alphabetically. +slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + loop=False, order=PlayBackOrder.ALPHABETICAL) while slideshow.update(): pass diff --git a/examples/slideshow_touch.py b/examples/slideshow_touch.py index 4934aff..b0e0e92 100644 --- a/examples/slideshow_touch.py +++ b/examples/slideshow_touch.py @@ -1,6 +1,7 @@ -import board -from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection +from adafruit_slideshow import PlayBackOrder, SlideShow, PlayBackDirection import touchio +import board +import pulseio forward_button = touchio.TouchIn(board.TOUCH4) back_button = touchio.TouchIn(board.TOUCH1) @@ -8,19 +9,18 @@ brightness_up = touchio.TouchIn(board.TOUCH3) brightness_down = touchio.TouchIn(board.TOUCH2) -slideshow = SlideShow() -slideshow.order = PlayBackMode.ALPHA -slideshow.auto_advance = False -slideshow.dwell = 0 +slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + auto_advance=False, dwell=0) while True: if forward_button.value: + slideshow.direction = PlayBackDirection.FORWARD slideshow.advance() if back_button.value: - slideshow.advance(direction=PlayBackDirection.BACKWARD) + slideshow.direction = PlayBackDirection.BACKWARD + slideshow.advance() if brightness_up.value: - slideshow.backlight_level_up() + slideshow.brightness += 0.001 elif brightness_down.value: - slideshow.backlight_level_down() - slideshow.update() + slideshow.brightness -= 0.001 From 20ccb320df4a6351de883a8c8f9d9ff5c33277e1 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 9 Oct 2018 16:58:20 -0700 Subject: [PATCH 11/14] Remove builtin display requirement --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 423ad88..70582cf 100644 --- a/README.rst +++ b/README.rst @@ -13,8 +13,7 @@ Introduction :target: https://travis-ci.org/adafruit/adafruit_CircuitPython_Slideshow :alt: Build Status -CircuitPython helper library for displaying a slideshow of images on a board with a built-in -display. +CircuitPython helper library for displaying a slideshow of images on a display. Dependencies ============= From 084a9d574e97c4ac645f9268f69a0256300bb745 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 10 Oct 2018 14:16:32 -0700 Subject: [PATCH 12/14] Remove print --- adafruit_slideshow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 0edf60b..6d4c057 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -288,7 +288,6 @@ def advance(self): except ValueError as error: self._image_file.close() self._image_file = None - print("remove", imagename) del self._file_list[self._current_image] if not odb: From caee24a57bdb8aa18cb876da9f73848ee03abbba Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 10 Oct 2018 18:32:42 -0400 Subject: [PATCH 13/14] Linting. Added . --- adafruit_slideshow.py | 16 ++++++++++++---- examples/slideshow_simpletest.py | 2 +- examples/slideshow_touch.py | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 6d4c057..8511961 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -48,6 +48,7 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Slideshow.git" + class PlayBackOrder: """Defines possible slideshow playback orders.""" # pylint: disable=too-few-public-methods @@ -78,8 +79,9 @@ class SlideShow: :param str folder: Specify the folder containing the image files, in quotes. Default is the root directory, ``"/"``. - :param PlayBackOrder order: The order in which the images display. You can choose random (``RANDOM``) or - alphabetical (``ALPHABETICAL``). Default is ``ALPHABETICAL``. + :param PlayBackOrder order: The order in which the images display. You can choose random + (``RANDOM``) or alphabetical (``ALPHABETICAL``). Default is + ``ALPHABETICAL``. :param bool loop: Specify whether to loop the images or play through the list once. `True` if slideshow will continue to loop, ``False`` if it will play only once. @@ -147,8 +149,9 @@ class SlideShow: slideshow.brightness -= 0.001 """ - def __init__(self, display, backlight_pwm, *, folder="/", order=PlayBackOrder.ALPHABETICAL, loop=True, - dwell=3, fade_effect=True, auto_advance=True, direction=PlayBackDirection.FORWARD): + def __init__(self, display, backlight_pwm, *, folder="/", order=PlayBackOrder.ALPHABETICAL, + loop=True, dwell=3, fade_effect=True, auto_advance=True, + direction=PlayBackDirection.FORWARD): self.loop = loop """Specifies whether to loop through the images continuously or play through the list once. ``True`` will continue to loop, ``False`` will play only once.""" @@ -172,6 +175,7 @@ def __init__(self, display, backlight_pwm, *, folder="/", order=PlayBackOrder.AL self._img_start = None self._file_list = list(filter(lambda x: x.endswith("bmp"), os.listdir(folder))) + self._order = None self.order = order """The order in which the images display. You can choose random (``RANDOM``) or alphabetical (``ALPHA``).""" @@ -190,6 +194,10 @@ def __init__(self, display, backlight_pwm, *, folder="/", order=PlayBackOrder.AL # Show the first image self.advance() + @property + def current_image_name(self): + return self._file_list[self._current_image] + @property def order(self): """Specifies the order in which the images are displayed. Options are random (``RANDOM``) or diff --git a/examples/slideshow_simpletest.py b/examples/slideshow_simpletest.py index 8b15c75..0a9af19 100644 --- a/examples/slideshow_simpletest.py +++ b/examples/slideshow_simpletest.py @@ -1,5 +1,5 @@ -from adafruit_slideshow import PlayBackOrder, SlideShow import board +from adafruit_slideshow import PlayBackOrder, SlideShow import pulseio # Create the slideshow object that plays through once alphabetically. diff --git a/examples/slideshow_touch.py b/examples/slideshow_touch.py index b0e0e92..f013a44 100644 --- a/examples/slideshow_touch.py +++ b/examples/slideshow_touch.py @@ -1,6 +1,6 @@ -from adafruit_slideshow import PlayBackOrder, SlideShow, PlayBackDirection -import touchio import board +from adafruit_slideshow import SlideShow, PlayBackDirection +import touchio import pulseio forward_button = touchio.TouchIn(board.TOUCH4) From 83bef67284db5ff559f5f1a6929d9a0272549224 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 10 Oct 2018 18:36:06 -0400 Subject: [PATCH 14/14] Moar linting. --- adafruit_slideshow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 8511961..2396025 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -196,6 +196,7 @@ def __init__(self, display, backlight_pwm, *, folder="/", order=PlayBackOrder.AL @property def current_image_name(self): + """Returns the current image name.""" return self._file_list[self._current_image] @property @@ -293,7 +294,7 @@ def advance(self): self._image_file = open(imagename, "rb") try: odb = displayio.OnDiskBitmap(self._image_file) - except ValueError as error: + except ValueError: self._image_file.close() self._image_file = None del self._file_list[self._current_image]