Skip to content
This repository was archived by the owner on Dec 23, 2021. It is now read-only.

Display module for microbit library #189

Merged
merged 45 commits into from
Feb 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
806576b
shim design example
andreamah Jan 25, 2020
deceeb7
some effort on image object design
andreamah Jan 28, 2020
8ed9e5b
initial look at image class
andreamah Jan 28, 2020
32060ce
fixes to image
andreamah Jan 28, 2020
ef6498c
finished first draft of most image methods
andreamah Jan 28, 2020
d2a7672
width and height modifications
andreamah Jan 29, 2020
16beee3
more additions to image
andreamah Jan 29, 2020
52ba07e
display microbit library
Jan 29, 2020
91352a0
resolve merge conflicts
Jan 29, 2020
cc0ac13
update dusplay
Jan 29, 2020
c761e8b
integrated image into display class
andreamah Jan 29, 2020
4cbda2b
fixed LED array reference issue
andreamah Jan 29, 2020
3557047
changes to display
Jan 29, 2020
0717e6b
fixed merge conflicts
Jan 29, 2020
ecd96e0
added bytearray compatability
andreamah Jan 29, 2020
9652a23
Before merge
Jan 29, 2020
ddcf389
reserve merge conflicts
Jan 29, 2020
20690b2
display show working, still need to add tests
Jan 30, 2020
b627989
updated display tests and refactored some code
Jan 30, 2020
2a6553e
started scroll method for display
Jan 31, 2020
7593b04
before merge
Feb 4, 2020
ccc752c
fixed merge conflicts
Feb 4, 2020
5a11e1c
Changed comment
Feb 4, 2020
39643f4
new test
Feb 4, 2020
6f0cbe9
test
Feb 4, 2020
88a1226
Done unit tests
Feb 4, 2020
8a76684
Removed unused file
Feb 4, 2020
a6d24b9
Deleted comments
Feb 4, 2020
fa53c7d
black formatting
Feb 4, 2020
dee99d4
Updated microbit model to include display
Feb 4, 2020
6be2586
Addressed comments
Feb 4, 2020
4dfd964
Merge branch 'dev' into users/t-vali/display
vandyliu Feb 4, 2020
e940be0
added comment
Feb 4, 2020
d5c5b58
Merge branch 'users/t-vali/display' of https://github.com/microsoft/v…
Feb 4, 2020
e8105f1
Updated how display on and off functionality work
Feb 5, 2020
e35a11b
incorporated PR feedback
Feb 6, 2020
cd5386f
Refactored code
vandyliu Feb 6, 2020
972057f
formatted with black
vandyliu Feb 6, 2020
164a481
Lock acquisition added
Feb 6, 2020
fcb741a
Lock acquisition added
Feb 6, 2020
0a62a0c
Added image lock
Feb 6, 2020
975ab1d
updated image lock
Feb 6, 2020
1e502f9
Updated locks
Feb 6, 2020
c2ca23c
Updated locks
Feb 6, 2020
8fc9b3b
Updated locks
Feb 6, 2020
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
8 changes: 8 additions & 0 deletions src/microbit/model/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@
],
}

# 5x5 Alphabet
# Taken from https://github.com/raw/micropython/micropython/264d80c84e034541bd6e4b461bfece4443ffd0ac/ports/nrf/boards/microbit/modules/microbitfont.h
ALPHABET = b"\x00\x00\x00\x00\x00\x08\x08\x08\x00\x08\x0a\x4a\x40\x00\x00\x0a\x5f\xea\x5f\xea\x0e\xd9\x2e\xd3\x6e\x19\x32\x44\x89\x33\x0c\x92\x4c\x92\x4d\x08\x08\x00\x00\x00\x04\x88\x08\x08\x04\x08\x04\x84\x84\x88\x00\x0a\x44\x8a\x40\x00\x04\x8e\xc4\x80\x00\x00\x00\x04\x88\x00\x00\x0e\xc0\x00\x00\x00\x00\x08\x00\x01\x22\x44\x88\x10\x0c\x92\x52\x52\x4c\x04\x8c\x84\x84\x8e\x1c\x82\x4c\x90\x1e\x1e\xc2\x44\x92\x4c\x06\xca\x52\x5f\xe2\x1f\xf0\x1e\xc1\x3e\x02\x44\x8e\xd1\x2e\x1f\xe2\x44\x88\x10\x0e\xd1\x2e\xd1\x2e\x0e\xd1\x2e\xc4\x88\x00\x08\x00\x08\x00\x00\x04\x80\x04\x88\x02\x44\x88\x04\x82\x00\x0e\xc0\x0e\xc0\x08\x04\x82\x44\x88\x0e\xd1\x26\xc0\x04\x0e\xd1\x35\xb3\x6c\x0c\x92\x5e\xd2\x52\x1c\x92\x5c\x92\x5c\x0e\xd0\x10\x10\x0e\x1c\x92\x52\x52\x5c\x1e\xd0\x1c\x90\x1e\x1e\xd0\x1c\x90\x10\x0e\xd0\x13\x71\x2e\x12\x52\x5e\xd2\x52\x1c\x88\x08\x08\x1c\x1f\xe2\x42\x52\x4c\x12\x54\x98\x14\x92\x10\x10\x10\x10\x1e\x11\x3b\x75\xb1\x31\x11\x39\x35\xb3\x71\x0c\x92\x52\x52\x4c\x1c\x92\x5c\x90\x10\x0c\x92\x52\x4c\x86\x1c\x92\x5c\x92\x51\x0e\xd0\x0c\x82\x5c\x1f\xe4\x84\x84\x84\x12\x52\x52\x52\x4c\x11\x31\x31\x2a\x44\x11\x31\x35\xbb\x71\x12\x52\x4c\x92\x52\x11\x2a\x44\x84\x84\x1e\xc4\x88\x10\x1e\x0e\xc8\x08\x08\x0e\x10\x08\x04\x82\x41\x0e\xc2\x42\x42\x4e\x04\x8a\x40\x00\x00\x00\x00\x00\x00\x1f\x08\x04\x80\x00\x00\x00\x0e\xd2\x52\x4f\x10\x10\x1c\x92\x5c\x00\x0e\xd0\x10\x0e\x02\x42\x4e\xd2\x4e\x0c\x92\x5c\x90\x0e\x06\xc8\x1c\x88\x08\x0e\xd2\x4e\xc2\x4c\x10\x10\x1c\x92\x52\x08\x00\x08\x08\x08\x02\x40\x02\x42\x4c\x10\x14\x98\x14\x92\x08\x08\x08\x08\x06\x00\x1b\x75\xb1\x31\x00\x1c\x92\x52\x52\x00\x0c\x92\x52\x4c\x00\x1c\x92\x5c\x90\x00\x0e\xd2\x4e\xc2\x00\x0e\xd0\x10\x10\x00\x06\xc8\x04\x98\x08\x08\x0e\xc8\x07\x00\x12\x52\x52\x4f\x00\x11\x31\x2a\x44\x00\x11\x31\x35\xbb\x00\x12\x4c\x8c\x92\x00\x11\x2a\x44\x98\x00\x1e\xc4\x88\x1e\x06\xc4\x8c\x84\x86\x08\x08\x08\x08\x08\x18\x08\x0c\x88\x18\x00\x00\x0c\x83\x60"
# We support ASCII characters between these indexes on the microbit
ASCII_START = 32
ASCII_END = 126
SPACE_BETWEEN_LETTERS_WIDTH = 1
WHITESPACE_WIDTH = 3

# numerical LED values
LED_HEIGHT = 5
Expand Down
269 changes: 269 additions & 0 deletions src/microbit/model/display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import copy
import time
import threading

from . import constants as CONSTANTS
from .image import Image
from .. import shim


class Display:
# The implementation based off of https://github.com/bbcmicrobit/micropython/blob/master/docs/display.rst.

def __init__(self):
self.__image = Image()
self.__on = True
self.__current_pid = None
self.__blank_image = Image()

self.__lock = threading.Lock()

def scroll(self, value, delay=150, wait=True, loop=False, monospace=False):
if not wait:
thread = threading.Thread(
target=self.scroll, args=(value, delay, True, loop, monospace)
)
thread.start()
return

# Set current_pid to the thread's identifier
self.__lock.acquire()
self.__current_pid = threading.get_ident()
self.__lock.release()

if isinstance(value, (str, int, float)):
value = str(value)
else:
raise TypeError(f"can't convert {type(value)} object to str implicitly")

letters = []
for c in value:
if monospace:
letters.append(Display.__get_image_from_char(c))
letters.append(
Image(CONSTANTS.SPACE_BETWEEN_LETTERS_WIDTH, CONSTANTS.LED_HEIGHT)
)
else:
if c == " ":
letters.append(
Image(CONSTANTS.WHITESPACE_WIDTH, CONSTANTS.LED_HEIGHT)
)
else:
letters.append(
Display.__strip_unlit_columns(Display.__get_image_from_char(c))
)
letters.append(
Image(
CONSTANTS.SPACE_BETWEEN_LETTERS_WIDTH, CONSTANTS.LED_HEIGHT,
)
)
appended_image = Display.__create_scroll_image(letters)

while True:
# Show the scrolled image one square at a time.
for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1):
self.__lock.acquire()

# If show or scroll is called again, there will be a different pid and break
if self.__current_pid != threading.get_ident():
self.__lock.release()
break

self.__image.blit(
appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT
)
self.__lock.release()

Display.sleep_ms(delay)

if not loop:
break

def show(self, value, delay=400, wait=True, loop=False, clear=False):
if not wait:
thread = threading.Thread(
target=self.show, args=(value, delay, True, loop, clear)
)
thread.start()
return

# Set current_pid to the thread's identifier
self.__lock.acquire()
self.__current_pid = threading.get_ident()
self.__lock.release()

images = []
use_delay = False
if isinstance(value, Image):
images.append(value.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT))
elif isinstance(value, (str, int, float)):
chars = list(str(value))
for c in chars:
images.append(Display.__get_image_from_char(c))
if len(chars) > 1:
use_delay = True
else:
# Check if iterable
try:
_ = iter(value)
except TypeError as e:
raise e

for elem in value:
if isinstance(elem, Image):
images.append(
elem.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT)
)
elif isinstance(elem, str) and len(elem) == 1:
images.append(Display.__get_image_from_char(elem))
# If elem is not char or image, break without iterating through rest of list
else:
break
use_delay = True

while True:
for image in images:
self.__lock.acquire()

# If show or scroll is called again, there will be a different pid and break
if self.__current_pid != threading.get_ident():
self.__lock.release()
break

self.__image = image
self.__lock.release()

if use_delay:
Display.sleep_ms(delay)

if not loop:
break
if clear:
self.clear()

def get_pixel(self, x, y):
self.__lock.acquire()
pixel = self.__image.get_pixel(x, y)
self.__lock.release()
return pixel

def set_pixel(self, x, y, value):
self.__lock.acquire()
self.__image.set_pixel(x, y, value)
self.__lock.release()

def clear(self):
self.__lock.acquire()
self.__image = Image()
self.__lock.release()

def on(self):
self.__on = True

def off(self):
self.__on = False

def is_on(self):
return self.__on

def read_light_level(self):
raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR)

# Helpers

def __get_array(self):
if self.is_on():
self.__lock.acquire()
leds = copy.deepcopy(self.__image._Image__LED)
self.__lock.release()
return leds
else:
return self.__blank_image._Image__LED

@staticmethod
def __get_image_from_char(c):
# If c is not between the ASCII alphabet we cover, make it a question mark
if ord(c) < CONSTANTS.ASCII_START or ord(c) > CONSTANTS.ASCII_END:
c = "?"
offset = (ord(c) - CONSTANTS.ASCII_START) * CONSTANTS.LED_WIDTH
representative_bytes = CONSTANTS.ALPHABET[
offset : offset + CONSTANTS.LED_HEIGHT
]
return Image(Display.__convert_bytearray_to_image_str(representative_bytes))

# Removes columns that are not lit
@staticmethod
def __strip_unlit_columns(image):
min_index = CONSTANTS.LED_WIDTH - 1
max_index = 0
for row in image._Image__LED:
for index, bit in enumerate(row):
if bit > 0:
min_index = min(min_index, index)
max_index = max(max_index, index)
return image.crop(min_index, 0, max_index - min_index + 1, CONSTANTS.LED_HEIGHT)

# This method is different from Image's __bytes_to_array.
# This one requires a conversion from binary of the ALPHABET constant to an image.
@staticmethod
def __convert_bytearray_to_image_str(byte_array):
arr = []
for b in byte_array:
# Convert byte to binary
b_as_bits = str(bin(b))[2:]
sub_arr = []
while len(sub_arr) < 5:
# Iterate throught bits
# If there is a 1 at b, then the pixel at column b is lit
for bit in b_as_bits[::-1]:
if len(sub_arr) < 5:
sub_arr.insert(0, int(bit) * CONSTANTS.BRIGHTNESS_MAX)
else:
break
# Add 0s to the front until the list is 5 long
while len(sub_arr) < 5:
sub_arr.insert(0, 0)
arr.append(sub_arr)
image_str = ""
for row in arr:
for elem in row:
image_str += str(elem)
image_str += ":"
return image_str

@staticmethod
def __insert_blank_column(image):
for row in image._Image__LED:
row.append(0)

@staticmethod
def __create_scroll_image(images):
blank_5x5_image = Image()
front_of_scroll_image = Image(4, 5)
images.insert(0, front_of_scroll_image)

scroll_image = Image._Image__append_images(images)
end_of_scroll_image = Image()
# Insert columns of 0s until the ending is a 5x5 blank
end_of_scroll_image.blit(
scroll_image,
scroll_image.width() - CONSTANTS.LED_WIDTH,
0,
CONSTANTS.LED_WIDTH,
CONSTANTS.LED_HEIGHT,
)
while not Image._Image__same_image(end_of_scroll_image, blank_5x5_image):
Display.__insert_blank_column(scroll_image)
end_of_scroll_image.blit(
scroll_image,
scroll_image.width() - CONSTANTS.LED_WIDTH,
0,
CONSTANTS.LED_WIDTH,
CONSTANTS.LED_HEIGHT,
)

return scroll_image

@staticmethod
def sleep_ms(ms):
time.sleep(ms / 1000)
56 changes: 40 additions & 16 deletions src/microbit/model/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,24 @@ def __mul__(self, other):

return res

def __repr__(self):
ret_str = "Image('"
for index_y in range(self.height()):
ret_str += self.__row_to_str(index_y)

ret_str += "')"

return ret_str

def __str__(self):
ret_str = "Image('\n"
for index_y in range(self.height()):
ret_str += "\t" + self.__row_to_str(index_y) + "\n"

ret_str += "')"

return ret_str

# HELPER FUNCTIONS

# This create 2D array of off LEDs with
Expand Down Expand Up @@ -338,23 +356,29 @@ def __row_to_str(self, y):

return new_str

def __repr__(self):
ret_str = "Image('"
for index_y in range(self.height()):
ret_str += self.__row_to_str(index_y)

ret_str += "')"

return ret_str

def __str__(self):
ret_str = "Image('\n"
for index_y in range(self.height()):
ret_str += "\t" + self.__row_to_str(index_y) + "\n"

ret_str += "')"
@staticmethod
def __append_images(images):
width = 0
height = 0
for image in images:
width += image.width()
height = max(height, image.height())
res = Image(width, height)
x_ind = 0
for image in images:
res.blit(image, 0, 0, image.width(), image.height(), xdest=x_ind)
x_ind += image.width()
return res

return ret_str
@staticmethod
def __same_image(i1, i2):
if i1.width() != i2.width() or i1.height() != i2.height():
return False
for y in range(i1.height()):
for x in range(i1.width()):
if i1.get_pixel(x, y) != i2.get_pixel(x, y):
return False
return True


# This is for generating functions like Image.HEART
Expand Down
2 changes: 2 additions & 0 deletions src/microbit/model/microbit_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time

from .button import Button
from .display import Display


class MicrobitModel:
Expand All @@ -9,6 +10,7 @@ def __init__(self):
self.button_a = Button()
self.button_b = Button()
self.__start_time = time.time()
self.display = Display()

def sleep(self, n):
time.sleep(n / 1000)
Expand Down
Loading