Skip to content
This repository was archived by the owner on Apr 20, 2022. It is now read-only.

Implement adafruit_pypixelbuf necessary for neopixel and dotstar without _pixelbuf #3

Merged
merged 15 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ __pycache__
_build
*.pyc
.env
build*
bundles
*.DS_Store
.eggs
dist
**/*.egg-info
**/*.egg-info
.vscode
45 changes: 0 additions & 45 deletions .travis.yml

This file was deleted.

4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand Down
69 changes: 9 additions & 60 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/adafruit/Adafruit_CircuitPython_Bundle>`_.
`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_.

Installing from PyPI
=====================
Expand Down Expand Up @@ -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
============
Expand All @@ -82,52 +76,7 @@ Contributions are welcome! Please read our `Code of Conduct
<https://github.com/adafruit/Adafruit_CircuitPython_Pypixelbuf/blob/master/CODE_OF_CONDUCT.md>`_
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 <https://github.com/adafruit/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 <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
107 changes: 40 additions & 67 deletions pypixelbuf.py → adafruit_pypixelbuf.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -22,62 +22,36 @@
# THE SOFTWARE.

"""
`pypixelbuf` - A pure python implementation of pixelbuf
`adafruit_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.
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):
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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):
"""
Expand All @@ -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):
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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):
Expand All @@ -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):
"""
Expand All @@ -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
Loading