Ejemplo n.º 1
0
class LedMatrix(collections.abc.Sequence):
    """Abstraction over the NeoPixel class for working with a NeoPixel strip as a matrix."""
    def __init__(
            self,
            gpio_pin_name=DEFAULT_GPIO_PIN_NAME,  # type: str
            num_rows=DEFAULT_NUM_ROWS,  # type: int
            num_cols=DEFAULT_NUM_COLS,  # type: int
            brightness=1,  # type: float
            auto_write=False,  # type: bool
            pixel_order=GRB,  # type: ColorOrder
            origin=MATRIX_ORIGIN.NORTHEAST,  # type: MATRIX_ORIGIN
            orientation=MATRIX_ORIENTATION.ROW,  # type: MATRIX_ORIENTATION
            default_color=RED,  # type: Color
    ):  # type: (...) -> None
        num_pixels = num_rows * num_cols
        gpio_pin = getattr(board, gpio_pin_name)
        self.width = num_cols
        self.height = num_rows
        self.origin = origin
        self.orientation = orientation
        self.default_color = default_color
        self.pixel_order = pixel_order

        # coerce pixel_order to plain tuple
        if pixel_order.white is None:
            pixel_order_raw = (pixel_order.red, pixel_order.green,
                               pixel_order.blue)
        else:
            pixel_order_raw = (  # type: ignore
                pixel_order.red,
                pixel_order.green,
                pixel_order.blue,
                pixel_order.white,
            )

        # initialize underlying NeoPixel
        self._neopixel = NeoPixel(
            gpio_pin,
            num_pixels,
            brightness=brightness,
            auto_write=auto_write,
            pixel_order=pixel_order_raw,
        )

        # initialize each row in matrix
        self._matrix = deque([], maxlen=num_rows)  # type: Deque[_LedMatrixRow]
        for row_index in range(num_rows):
            self._matrix.append(_LedMatrixRow(self, row_index, num_cols))

    def render(self):  # type: () -> None
        """Render current state of matrix to the neopixel (only useful when auto_write is False)."""
        # print to STDOUT if using a mock
        if isinstance(self._neopixel, mock_neopixel.MockNeoPixel):
            os.system('clear')  # noqa: S605 S607
            print(self)
        # otherwise call the "show" method on the underlying neopixel
        else:
            self._neopixel.show()

    def fill(self, value):  # type: (Color) -> None
        """Fill the entire matrix with the given color value."""
        for row in self._matrix:
            row.fill(value)

    def shift_left(self, values):  # type: (List[Color]) -> None
        """Shift all current pixel values left one unit."""
        for row_index in range(self.height):
            row = self._matrix[row_index]
            value = values[row_index]
            row.shift_left(value)

    def deinit(self):  # type: () -> None
        """Turn off and unmount the neopixel."""
        self._neopixel.deinit()

    def _neopixel_set(
            self,
            matrix_row_index,  # type: int
            matrix_col_index,  # type: int
            value,  # type: Color
    ):  # type: (...) -> None
        """Update the NeoPixel pixel at the index corresponding to this position in the matrix.

        TODO: make this less of a clusterfuck.
        """
        # do nothing if row index is out of range
        if matrix_row_index < 0 or matrix_row_index >= self.height:
            return None

        # do nothing if col index is out of range
        if matrix_col_index < 0 or matrix_col_index >= self.width:
            return None

        # the "neopixel row index" is the index of the first pixel for the specified row
        neopixel_row_index = matrix_row_index * self.width
        if self.origin == MATRIX_ORIGIN.NORTHWEST:
            neopixel_col_index = matrix_col_index * self.height
        else:
            neopixel_col_index = (
                (self.width - 1) - matrix_col_index) * self.height

        # whether this row / column orientation is swapped
        neopixel_col_alt = neopixel_col_index % 2
        neopixel_row_alt = neopixel_row_index % 2

        # strips are laid horizontal across the board
        if self.orientation == MATRIX_ORIENTATION.ROW:
            # the first pixel is in the top-left corner of the board
            if self.origin == MATRIX_ORIGIN.NORTHWEST:
                neopixel_index = neopixel_row_index + matrix_col_index
            # the first pixel is in the bottom-right corner of the board
            elif self.origin == MATRIX_ORIGIN.SOUTHEAST:
                # NOTE: this is probably wrong
                neopixel_index = (self.height - neopixel_row_index) + (
                    self.width - matrix_col_index) - 2  # noqa: E501
            # the first pixel is in the top-right corner of the board
            else:
                neopixel_index = neopixel_row_index + (self.width -
                                                       matrix_col_index) - 1

        # strips are laid vertically across the board
        elif self.orientation == MATRIX_ORIENTATION.COLUMN:
            # the first pixel is in the top-left corner of the board
            if self.origin == MATRIX_ORIGIN.NORTHWEST:
                neopixel_index = neopixel_col_index + matrix_row_index
            # the first pixel is in the bottom-right corner of the board
            elif self.origin == MATRIX_ORIGIN.SOUTHEAST:
                # NOTE: this is probably wrong
                neopixel_index = neopixel_col_index + matrix_row_index
            # the first pixel is in the top-right corner of the board
            else:
                neopixel_index = neopixel_col_index + (self.height -
                                                       matrix_row_index) - 1

        # strips are laid horizontally across the board and alternate left-right orientations
        elif self.orientation == MATRIX_ORIENTATION.ALTERNATING_ROW:
            # the first pixel is in the top-left corner of the board
            if self.origin == MATRIX_ORIGIN.NORTHWEST:
                # this strip is oriented left-to-right
                if not neopixel_row_alt:
                    neopixel_index = neopixel_row_index + matrix_col_index
                # this strip's orientation is switched right-to-left
                else:
                    neopixel_index = (neopixel_row_index -
                                      (self.width - 1)) + matrix_col_index
            # the first pixel is in the bottom-right corner of the board
            elif self.origin == MATRIX_ORIGIN.SOUTHEAST:
                # this strip is oriented right-to-left
                if neopixel_row_alt:
                    neopixel_index = neopixel_row_index + (
                        (self.width - 1) - matrix_col_index)
                # this strip's orientation is switched left-to-right
                else:
                    neopixel_index = neopixel_row_index + matrix_col_index
            # the first pixel is in the top-right corner of the board
            else:
                # this strip is oriented right-to-left
                if not neopixel_row_alt:
                    neopixel_index = neopixel_row_index + (
                        (self.width - 1) - matrix_col_index)
                # this strip's orientation is switched left-to-right
                else:
                    neopixel_index = neopixel_row_index + matrix_col_index

        # strips are laid vertically across the board and alternate down-up orientations
        else:
            # the first pixel is in the top-left corner of the board
            if self.origin == MATRIX_ORIGIN.NORTHWEST:
                # this strip is oriented top-to-bottom
                if not neopixel_col_alt:
                    neopixel_index = neopixel_col_index + matrix_row_index
                # this strip's orientation is switched bottom-to-top
                else:
                    neopixel_index = neopixel_col_index + (
                        (self.height - 1) - matrix_row_index)
            # the first pixel is in the bottom-right corner of the board
            elif self.origin == MATRIX_ORIGIN.SOUTHEAST:
                # this strip is oriented top-to-bottom
                if neopixel_col_alt:
                    neopixel_index = neopixel_col_index + matrix_row_index
                # this strip's orientation is switched bottom-to-top
                else:
                    neopixel_index = neopixel_col_index + (
                        (self.height - 1) - matrix_row_index)
            # the first pixel is in the top-right corner of the board
            else:
                # this strip is oriented top-to-bottom
                if not neopixel_col_alt:
                    neopixel_index = neopixel_col_index + matrix_row_index
                # this strip's orientation is switched bottom-to-top
                else:
                    neopixel_index = neopixel_col_index + (
                        (self.height - 1) - matrix_row_index)

        # set value on pixel
        if value.white is None:
            self._neopixel[neopixel_index] = (value.red, value.green,
                                              value.blue)
        else:
            self._neopixel[neopixel_index] = (value.red, value.green,
                                              value.blue, value.white)

        # print if using a mock neopixel and auto_write is True
        if isinstance(self._neopixel, mock_neopixel.MockNeoPixel):
            if self._neopixel.auto_write:
                os.system('clear')  # noqa: S605 S607
                print(self)

    def __repr__(self):  # type: () -> str
        buf = ''
        for row in self._matrix:
            for color in row:
                if not isinstance(color, Color):
                    if len(color) == 4:
                        color = Color(*color)
                    else:
                        color = Color(color[0], color[1], color[2], None)
                buf += color.__repr__()
            buf += '\n'
        return buf

    def __len__(self):  # type: () -> int
        return len(self._neopixel)

    def __getitem__(self, index):  # type: (Union[int, slice]) -> Color
        return self._matrix[index]  # type: ignore

    def __setitem__(self, index, color):  # type: (int, Color) -> None
        return self._matrix[index]  # type: ignore