diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..66ce4db --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,50 @@ +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install deps + run: | + source actions-ci/install.sh + - name: Library version + run: git describe --dirty --always --tags + - name: PyLint + run: | + pylint $( find . -path './adafruit*.py' ) + ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace examples/*.py) + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . + - name: Build docs + working-directory: docs + run: sphinx-build -E -W -b html . _build/html diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..18efb9c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Release Actions + +on: + release: + types: [published] + +jobs: + upload-release-assets: + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install deps + run: | + source actions-ci/install.sh + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . + - name: Upload Release Assets + # the 'official' actions version does not yet support dynamically + # supplying asset names to upload. @csexton's version chosen based on + # discussion in the issue below, as its the simplest to implement and + # allows for selecting files with a pattern. + # https://github.com/actions/upload-release-asset/issues/4 + #uses: actions/upload-release-asset@v1.0.1 + uses: csexton/release-asset-action@master + with: + pattern: "bundles/*" + github-token: ${{ secrets.GITHUB_TOKEN }} + + upload-pypi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Check For setup.py + id: need-pypi + run: | + echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + - name: Set up Python + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + env: + TWINE_USERNAME: ${{ secrets.pypi_username }} + TWINE_PASSWORD: ${{ secrets.pypi_password }} + run: | + python setup.py sdist + twine upload dist/* diff --git a/.gitignore b/.gitignore index 55f127b..e7e0f2d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,9 @@ __pycache__ _build *.pyc .env -build* bundles *.DS_Store .eggs dist -**/*.egg-info \ No newline at end of file +**/*.egg-info +.vscode diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8a46702..0000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -# 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: xenial -language: python -python: - - "3.6" - -cache: - pip: true - -env: - - DEPLOY_PYPI="true" - -deploy: - - provider: releases - api_key: "$GITHUB_TOKEN" - file_glob: true - file: "$TRAVIS_BUILD_DIR/bundles/*" - skip_cleanup: true - overwrite: true - on: - tags: true - # TODO: Use 'travis encrypt --com -r adafruit/' to generate - # the encrypted password for adafruit-travis. Paste result below. - - provider: pypi - user: adafruit-travis - password: - secure: #-- PASTE ENCRYPTED PASSWORD HERE --# - on: - tags: true - condition: $DEPLOY_PYPI = "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 pypixelbuf.py - - ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace examples/*.py) - - circuitpython-build-bundles --filename_prefix adafruit-circuitpython-pypixelbuf --library_location . - - cd docs && sphinx-build -E -W -b html . _build/html && cd .. diff --git a/LICENSE b/LICENSE index 4070f96..2251cda 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The MIT License (MIT) -Based on the Adafruit NeoPixel and Adafruit DotStar CircuitPython drivers. -Copyright (c) 2019 Roy Hooper +Based on the Adafruit NeoPixel and Adafruit DotStar CircuitPython libraries. +Copyright (c) 2019-2020 Roy Hooper Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 1be5ff6..c99be96 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,8 @@ Introduction :target: https://discord.gg/nBQh6qu :alt: Discord -.. image:: https://travis-ci.com/adafruit/Adafruit_CircuitPython_Pypixelbuf.svg?branch=master - :target: https://travis-ci.com/adafruit/Adafruit_CircuitPython_Pypixelbuf +.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Pypixelbuf/workflows/Build%20CI/badge.svg + :target: https://github.com/adafruit/Adafruit_CircuitPython_Pypixelbuf/actions :alt: Build Status Pure python implementation of _pixelbuf for smaller boards. @@ -24,7 +24,7 @@ This driver depends on: Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading -`the Adafruit library and driver bundle `_. +`the Adafruit library and driver bundle `_. Installing from PyPI ===================== @@ -58,22 +58,16 @@ This example tests that the pypixelbuf works. .. code-block:: python - import pypixelbuf + class TestBuf(adafruit_pypixelbuf.PixelBuf): + called = False - callback_called = False + def show(self): + self.called = True - def callback(): - global callback_called - callback_called = True - - - buffer = pypixelbuf.PixelBuf(20, bytearray(20 * 3), pypixelbuf.RGB, 1.0, auto_write=True, write_function=callback) - + buffer = TestBuf(20, bytearray(20 * 3), "RGB", 1.0, auto_write=True) buffer[0] = (1, 2, 3) - print(callback_called) - Contributing ============ @@ -82,52 +76,7 @@ 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-pypixelbuf --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 +Documentation +============= -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. +For information on building library documentation, please check out `this guide `_. diff --git a/pypixelbuf.py b/adafruit_pypixelbuf.py similarity index 79% rename from pypixelbuf.py rename to adafruit_pypixelbuf.py index b101496..833638d 100644 --- a/pypixelbuf.py +++ b/adafruit_pypixelbuf.py @@ -1,7 +1,7 @@ # The MIT License (MIT) # # Based on the Adafruit NeoPixel and Adafruit Dotstar CircuitPython drivers. -# Copyright (c) 2019 Roy Hooper +# Copyright (c) 2019-2020 Roy Hooper # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -22,62 +22,36 @@ # THE SOFTWARE. """ -`pypixelbuf` - A pure python implementation of pixelbuf -======================================================= -This class is used when _pixelbuf is not available in a build. - -This is a work in progress. +`adafruit_pypixelbuf` - A pure python implementation of _pixelbuf +================================================================= +This class is used when _pixelbuf is not available in CircuitPython. It is based on the work +in neopixel.py and adafruit_dotstar.py. * Author(s): Damien P. George & Limor Fried & Scott Shawcroft & Roy Hooper """ -import math import re -RGB = "RGB" -RBG = "RBG" -GRB = "GRB" -GBR = "GBR" -BRG = "BRG" -BGR = "BGR" -RGBW = "RGBW" -RBGW = "RBGW" -GRBW = "GRBW" -GBRW = "GBRW" -BRGW = "BRGW" -BGRW = "BGRW" -RGBD = "RGBD" -RBGD = "RBGD" -GRBD = "GRBD" -GBRD = "GBRD" -BRGD = "BRGD" -BGRD = "BGRD" - DOTSTAR_LED_START_FULL_BRIGHT = 0xFF DOTSTAR_LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits DOTSTAR_LED_BRIGHTNESS = 0b00011111 -IS_PURE_PYTHON = True - class PixelBuf(object): # pylint: disable=too-many-instance-attributes """ A sequence of RGB/RGBW pixels. - This is the pure python implementation of _pixelbuf. + This is the pure python implementation of CircuitPython's _pixelbuf. :param ~int n: Number of pixels :param ~bytearray buf: Bytearray to store pixel data in - :param ~str byteorder: Byte order string constant (also sets the bpp) + :param ~str byteorder: Byte order string constant (also sets bpp) :param ~float brightness: Brightness (0 to 1.0, default 1.0) :param ~bytearray rawbuf: Bytearray to store raw pixel colors in :param ~int offset: Offset from start of buffer (default 0) :param ~bool auto_write: Whether to automatically write pixels (Default False) - :param ~callable write_function: (optional) Callable to use to send pixels - :param ~list write_args: (optional) Tuple or list of args to pass to ``write_function``. The - PixelBuf instance is appended after these args. """ - def __init__(self, n, buf, byteorder=BGR, brightness=1.0, # pylint: disable=too-many-locals,too-many-arguments - rawbuf=None, offset=0, auto_write=False, write_function=None, write_args=None): + def __init__(self, n, buf, byteorder="BGR", brightness=1.0, # pylint: disable=too-many-locals,too-many-arguments + rawbuf=None, offset=0, auto_write=False): bpp, byteorder_tuple, has_white, dotstar_mode = self.parse_byteorder(byteorder) if not isinstance(buf, bytearray): @@ -115,11 +89,6 @@ def __init__(self, n, buf, byteorder=BGR, brightness=1.0, # pylint: disable=too self._byteorder_tuple = (byteorder_tuple[0] + 1, byteorder_tuple[1] + 1, byteorder_tuple[2] + 1, 0) - self._write_function = write_function - self._write_args = () - if write_args: - self._write_args = tuple(self._write_args) + (self, ) - self._brightness = min(1.0, max(0, brightness)) if dotstar_mode: @@ -137,7 +106,7 @@ def parse_byteorder(byteorder): G - Green B - Blue W - White - D - Dotstar luminositry + P - PWM (PWM Duty cycle for pixel - dotstars 0 - 1.0) :param: ~str bpp: bpp string. :return: ~tuple: bpp, byteorder, has_white, dotstar_mode @@ -146,7 +115,7 @@ def parse_byteorder(byteorder): dotstar_mode = False has_white = False - if re.search(r'[^RGBWd]', byteorder): + if re.search(r'[^RGBWP]', byteorder): raise ValueError("Invalid Byteorder string") try: @@ -158,15 +127,15 @@ def parse_byteorder(byteorder): if 'W' in byteorder: w = byteorder.index("W") byteorder = (r, g, b, w) - elif 'd' in byteorder: - lum = byteorder.index("D") + elif 'P' in byteorder: + lum = byteorder.index("P") byteorder = (r, g, b, lum) + dotstar_mode = True else: byteorder = (r, g, b) return bpp, byteorder, has_white, dotstar_mode - @property def bpp(self): """ @@ -190,7 +159,7 @@ def brightness(self): def brightness(self, value): self._brightness = min(max(value, 0.0), 1.0) - # Adjust brightness when two buffers are available + # Adjust brightness of existing pixels when two buffers are available if self._two_buffers: offset_check = self._offset % self._pixel_step for i in range(self._offset, self._bytes + self._offset): @@ -217,8 +186,7 @@ def show(self): """ Call the associated write function to display the pixels """ - if self._write_function: - self._write_function(*self._write_args) + raise NotImplementedError("Must be subclassed") def _set_item(self, index, value): # pylint: disable=too-many-locals,too-many-branches if index < 0: @@ -262,6 +230,7 @@ def _set_item(self, index, value): # pylint: disable=too-many-locals,too-many-b self._bytearray[offset + self._byteorder[0]] = int(r * self._brightness) self._bytearray[offset + self._byteorder[1]] = int(g * self._brightness) self._bytearray[offset + self._byteorder[2]] = int(b * self._brightness) + if has_w: if self._dotstar_mode: # LED startframe is three "1" bits, followed by 5 brightness bits @@ -282,11 +251,6 @@ def _set_item(self, index, value): # pylint: disable=too-many-locals,too-many-b def __setitem__(self, index, val): if isinstance(index, slice): start, stop, step = index.indices(self._pixels) - length = stop - start - if step != 0: - length = math.ceil(length / step) - if len(val) != length: - raise ValueError("Slice and input sequence size do not match.") for val_i, in_i in enumerate(range(start, stop, step)): self._set_item(in_i, val[val_i]) else: @@ -296,14 +260,18 @@ def __setitem__(self, index, val): self.show() def _getitem(self, index): + start = self._offset + (index * self.bpp) + value = [ + self._bytearray[start + self._byteorder[0]], + self._bytearray[start + self._byteorder[1]], + self._bytearray[start + self._byteorder[2]], + ] if self._has_white: - return tuple(self._bytearray[self._offset + (index * self.bpp) + - self._byteorder[i]] for i in range(self.bpp)) - return tuple( - [self._bytearray[self._offset + (index * self.bpp) + self._byteorder[i]] - for i in range(3)] + [(self._bytearray[self._offset + (index * self.bpp) + - self._byteorder[3]] & - DOTSTAR_LED_BRIGHTNESS) / 31.0]) + value.append(self._bytearray[start + self._byteorder[2]]) + elif self._dotstar_mode: + value.append((self._bytearray[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS) / + 31.0) + return value def __getitem__(self, index): if isinstance(index, slice): @@ -317,14 +285,6 @@ def __getitem__(self, index): raise IndexError return self._getitem(index) - def fill_wheel(self, n, step): - """ - fill the buffer with a colorwheel starting at offset n, and stepping by step - """ - self[0:len(self)] = [wheel((n + (step * i)) % 255) for i in range(len(self))] - if self.auto_write: - self.show() - def wheel(pos): """ @@ -344,3 +304,16 @@ def wheel(pos): return 0, 255 - pos * 3, pos * 3 pos -= 170 return pos * 3, 0, 255 - pos * 3 + + +def fill(pixelbuf, color): + """ + Helper to fill the strip a specific color. + :param pixelbuf: A pixel object. + :param color: Color to set. + """ + auto_write = pixelbuf.auto_write + pixelbuf.auto_write = False + for i, _ in enumerate(pixelbuf): + pixelbuf[i] = color + pixelbuf.auto_write = auto_write diff --git a/docs/api.rst b/docs/api.rst index ad8c970..8ea35e3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4,5 +4,5 @@ .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) .. use this format as the module name: "adafruit_foo.foo" -.. automodule:: pypixelbuf +.. automodule:: adafruit_pypixelbuf :members: diff --git a/docs/conf.py b/docs/conf.py index a1ada90..4fbae69 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,8 +34,8 @@ master_doc = 'index' # General information about the project. -project = u'Adafruit Pypixelbuf Library' -copyright = u'2019 Roy Hooper' +project = u'Adafruit PyPixelBuf Library' +copyright = u'2020 Roy Hooper' author = u'Roy Hooper' # The version info for the project you're documenting, acts as replacement for @@ -135,7 +135,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'AdafruitPypixelbufLibrary.tex', u'AdafruitPypixelbuf Library Documentation', + (master_doc, 'AdafruitPyPixelBufLibrary.tex', u'AdafruitPyPixelBuf Library Documentation', author, 'manual'), ] @@ -144,7 +144,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'AdafruitPypixelbuflibrary', u'Adafruit Pypixelbuf Library Documentation', + (master_doc, 'AdafruitPyPixelBuflibrary', u'Adafruit PyPixelBuf Library Documentation', [author], 1) ] @@ -154,7 +154,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'AdafruitPypixelbufLibrary', u'Adafruit Pypixelbuf Library Documentation', - author, 'AdafruitPypixelbufLibrary', 'One line description of project.', + (master_doc, 'AdafruitPyPixelBufLibrary', u'Adafruit PyPixelBuf Library Documentation', + author, 'AdafruitPyPixelBufLibrary', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/examples.rst b/docs/examples.rst index ff17fe6..2ababc0 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -3,6 +3,6 @@ Simple test Ensure your device works with this simple test. -.. literalinclude:: ../examples/pypixelbuf_simpletest.py - :caption: examples/pypixelbuf_simpletest.py +.. literalinclude:: ../examples/adafruit_pypixelbuf_simpletest.py + :caption: examples/adafruit_pypixelbuf_simpletest.py :linenos: diff --git a/docs/index.rst b/docs/index.rst index 8ebd914..cf05ae3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ Table of Contents .. toctree:: :caption: Tutorials + .. toctree:: :caption: Related Products diff --git a/examples/adafruit_pypixelbuf_simpletest.py b/examples/adafruit_pypixelbuf_simpletest.py new file mode 100644 index 0000000..64efbb0 --- /dev/null +++ b/examples/adafruit_pypixelbuf_simpletest.py @@ -0,0 +1,17 @@ +import adafruit_pypixelbuf + + +class TestBuf(adafruit_pypixelbuf.PixelBuf): + called = False + + def show(self): + self.called = True + + +buffer = TestBuf(20, bytearray(20 * 3), "RGB", 1.0, auto_write=True) +buffer[0] = (1, 2, 3) + +print(buffer[0]) +print(buffer[0:2]) +print(buffer[0:2:2]) +print(buffer.called) diff --git a/examples/pypixelbuf_simpletest.py b/examples/pypixelbuf_simpletest.py deleted file mode 100644 index 6e5adcc..0000000 --- a/examples/pypixelbuf_simpletest.py +++ /dev/null @@ -1,17 +0,0 @@ -import pypixelbuf - -STATUS = { - "callback_called": False -} - - -def callback(): - STATUS["callback_called"] = True - - -buffer = pypixelbuf.PixelBuf(20, bytearray(20 * 3), pypixelbuf.RGB, 1.0, auto_write=True, - write_function=callback) - -buffer[0] = (1, 2, 3) - -print(STATUS["callback_called"]) diff --git a/setup.py b/setup.py index c621297..f3cc825 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ use_scm_version=True, setup_requires=['setuptools_scm'], - description='Pure python implementation of _pixelbuf', + description='Pure python implementation of _pixelbuf for smaller boards.', long_description=long_description, long_description_content_type='text/x-rst', @@ -53,11 +53,7 @@ ], # What does your project relate to? - keywords='adafruit blinka circuitpython micropython pypixelbuf pixelbuf led', + keywords='adafruit blinka circuitpython micropython pypixelbuf pypixelbuf pixelbuf led', - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, - # CHANGE `py_modules=['...']` TO `packages=['...']` - py_modules=['pypixelbuf'], + py_modules=['adafruit_pypixelbuf'], )