Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/setting colors for individual leds on pixel kit 15 #18

Open
wants to merge 2 commits into
base: python
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ __pycache__
.DS_Store
.pytest_cache
.coverage
.idea
51 changes: 45 additions & 6 deletions communitysdk/retailpixelkit.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from .serialdevice import SerialDevice
from base64 import b64encode


COLUMNS = 16
ROWS = 8
PIXEL_COUNT = COLUMNS * ROWS
OFF = '#000000'
BLANK_SCREEN = [OFF] * PIXEL_COUNT


class RetailPixelKitSerial(SerialDevice):
def __init__(self, path):
super().__init__(path)
Expand Down Expand Up @@ -33,18 +41,49 @@ def hex_to_base64(self, hex_colors):
return result.decode()

def stream_frame(self, frame):
'''
"""
Just send to serial, don't wait until `rpc-response`
'''
if len(frame) != 128:
raise Exception('Frame must contain 128 values')
encodedFrame = self.hex_to_base64(frame)
:param frame:
:return: None
"""
if len(frame) != PIXEL_COUNT:
exc = 'Frame must contain {} values'.format(PIXEL_COUNT)
raise Exception(exc)

encoded_frame = self.hex_to_base64(frame)
method = 'lightboard:on'
params = [{ 'map': encodedFrame }]
params = [{'map': encoded_frame}]
request_obj = self.get_request_object(method, params)
request_str = self.get_request_string(request_obj)
self.conn_send(request_str)

@classmethod
def coord_to_index(cls, x, y):
"""
Convert an x, y coordinate to an index in a frame list
:param x:
:param y:
:return: int
"""
return (y * COLUMNS + (x - COLUMNS)) - 1

@classmethod
def pixels_to_frame(cls, pixels):
"""
Create a frame list of hex colors
:param pixels: list tuple (x, y, color)
:return: list
"""
frame = BLANK_SCREEN.copy()
for x, y, color in pixels:
index = cls.coord_to_index(x, y)
frame[index] = color
return frame

def stream_pixels(self, pixels):
frame = RetailPixelKitSerial.pixels_to_frame(pixels)
self.stream_frame(frame)

def get_battery_status(self):
return self.rpc_request('battery-status', [])

Expand Down
40 changes: 40 additions & 0 deletions example_pixel_kit_stream_pixel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
This example will stream a single color pixel to Pixel Kit.
"""
from communitysdk import list_connected_devices
from communitysdk import RetailPixelKitSerial as PixelKit
from communitysdk.retailpixelkit import COLUMNS, ROWS
from random import randint
from time import sleep

devices = list_connected_devices()
pk_filter = filter(lambda device: isinstance(device, PixelKit), devices)
pk = next(pk_filter, None) # Get first Pixel Kit


def get_random_hex() -> str:
random_number = randint(0, 16777215) # number of possible colors
hex_number = str(hex(random_number))
hex_number = '#{}'.format(hex_number[2:])
return hex_number


def get_random_pixel() -> tuple:
x = randint(1, COLUMNS)
y = randint(1, ROWS)
color = get_random_hex()
return x, y, color


if pk is not None:
"""
A pixel is a tuple with an x and y coordinate and a hexadecimal color
prefixed with `#`.
We'll create a random color and coordinate to stream to the Pixel Kit.
"""
while True:
pixels = [get_random_pixel()]
pk.stream_pixels(pixels)
sleep(0.1)
else:
print('No Pixel Kit was found :(')
60 changes: 60 additions & 0 deletions test/test_retailpixelkitserial.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,63 @@ def test_connect_to_wifi_wrong(rpk):
patch.object(rpk, 'rpc_request'):
rpk.connect_to_wifi(ssid, value)
assert "`password` must be a string" in str(err.value)


def test_coord_to_index():
"""
Should return the correct index in the pixel array from x and y coordinates
"""
assert PixelKit.coord_to_index(1, 1) == 0
assert PixelKit.coord_to_index(16, 8) == 127


@pytest.fixture
def pixel1() -> tuple:
return 1, 1, '#ffffff'


@pytest.fixture
def pixel2() -> tuple:
return 16, 8, '#2b2b2b'


class TestPixelsToFrame:
from communitysdk.retailpixelkit import BLANK_SCREEN

def test_empty_list(self):
"""
Should return a blank screen list when the pixels argument is an empty list
"""
assert PixelKit.pixels_to_frame([]) == self.BLANK_SCREEN

def test_pixels_to_frame(self, pixel1, pixel2):
"""
Should return a list with the pixels at the correct indices
"""
expected_frame = self.BLANK_SCREEN.copy()
color1 = pixel1[2]
color2 = pixel2[2]
expected_frame[0] = color1
expected_frame[127] = color2
assert PixelKit.pixels_to_frame([pixel1, pixel2]) == expected_frame


class TestStreamPixels:
@pytest.fixture
def pixels(self, pixel1, pixel2) -> list:
return [pixel1, pixel2]

@pytest.fixture
def frame(self, pixels) -> list:
return PixelKit.pixels_to_frame(pixels)

def test_calls_pixels_to_frame(self, rpk, pixels, frame):
with patch.object(PixelKit, 'pixels_to_frame', return_value=frame) as pixels_to_frame, \
patch.object(rpk, 'stream_frame'):
rpk.stream_pixels(pixels)
pixels_to_frame.assert_called_with(pixels)

def test_calls_stream_frame(self, rpk, pixels, frame):
with patch.object(rpk, 'stream_frame') as stream_frame:
rpk.stream_pixels(pixels)
stream_frame.assert_called_with(frame)