From 6b38fcc12b3c2a630b8ba188151ec1d00116da22 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 10 Feb 2020 13:22:19 -0800 Subject: [PATCH] re-organized structure for autocorrect (#193) re-structuring for auto-completion --- src/microbit/__init__.py | 25 ++++- src/microbit/__model/button.py | 42 +++++++ src/microbit/{model => __model}/constants.py | 0 src/microbit/{model => __model}/display.py | 64 ++++++++++- src/microbit/{model => __model}/image.py | 106 +++++++++++++++++- .../{model => __model}/microbit_model.py | 2 +- .../{model => __model}/producer_property.py | 0 src/microbit/model/button.py | 27 ----- src/microbit/shim.py | 14 --- src/microbit/test/test_button.py | 2 +- src/microbit/test/test_display.py | 9 +- src/microbit/test/test_image.py | 4 +- src/microbit/test/test_microbit_model.py | 12 +- src/microbit/test/test_shim.py | 19 ++-- src/process_user_code.py | 2 +- 15 files changed, 259 insertions(+), 69 deletions(-) create mode 100644 src/microbit/__model/button.py rename src/microbit/{model => __model}/constants.py (100%) rename src/microbit/{model => __model}/display.py (75%) rename src/microbit/{model => __model}/image.py (75%) rename src/microbit/{model => __model}/microbit_model.py (91%) rename src/microbit/{model => __model}/producer_property.py (100%) delete mode 100644 src/microbit/model/button.py delete mode 100644 src/microbit/shim.py diff --git a/src/microbit/__init__.py b/src/microbit/__init__.py index a0d5418e9..f6e847f48 100644 --- a/src/microbit/__init__.py +++ b/src/microbit/__init__.py @@ -1 +1,24 @@ -from .shim import * +from .__model.image import Image +from .__model.microbit_model import __mb + +button_a = __mb.button_a +button_b = __mb.button_b +display = __mb.display + + +def sleep(n): + """ + Wait for ``n`` milliseconds. One second is 1000 milliseconds, so:: + microbit.sleep(1000) + will pause the execution for one second. ``n`` can be an integer or + a floating point number. + """ + __mb.sleep(n) + + +def running_time(): + """ + Return the number of milliseconds since the board was switched on or + restarted. + """ + __mb.running_time() diff --git a/src/microbit/__model/button.py b/src/microbit/__model/button.py new file mode 100644 index 000000000..ee00ad350 --- /dev/null +++ b/src/microbit/__model/button.py @@ -0,0 +1,42 @@ +class Button: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/button.html. + def __init__(self): + self.__pressed = False + self.__presses = 0 + self.__prev_pressed = False + + def is_pressed(self): + """ + Returns ``True`` if the specified button ``button`` is currently being + held down, and ``False`` otherwise. + """ + return self.__pressed + + def was_pressed(self): + """ + Returns ``True`` or ``False`` to indicate if the button was pressed + (went from up to down) since the device started or the last time this + method was called. Calling this method will clear the press state so + that the button must be pressed again before this method will return + ``True`` again. + """ + res = self.__prev_pressed + self.__prev_pressed = False + return res + + def get_presses(self): + """ + Returns the running total of button presses, and resets this total + to zero before returning. + """ + res = self.__presses + self.__presses = 0 + return res + + def __press_down(self): + self.__pressed = True + self.__prev_pressed = True + self.__presses += 1 + + def __release(self): + self.__pressed = False diff --git a/src/microbit/model/constants.py b/src/microbit/__model/constants.py similarity index 100% rename from src/microbit/model/constants.py rename to src/microbit/__model/constants.py diff --git a/src/microbit/model/display.py b/src/microbit/__model/display.py similarity index 75% rename from src/microbit/model/display.py rename to src/microbit/__model/display.py index d0531c131..2dfd7cc36 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/__model/display.py @@ -5,11 +5,10 @@ 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. + # The implementation based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/display.html. def __init__(self): self.__image = Image() @@ -20,6 +19,23 @@ def __init__(self): self.__lock = threading.Lock() def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): + """ + Scrolls ``value`` horizontally on the display. If ``value`` is an integer or float it is + first converted to a string using ``str()``. The ``delay`` parameter controls how fast + the text is scrolling. + + If ``wait`` is ``True``, this function will block until the animation is + finished, otherwise the animation will happen in the background. + + If ``loop`` is ``True``, the animation will repeat forever. + + If ``monospace`` is ``True``, the characters will all take up 5 pixel-columns + in width, otherwise there will be exactly 1 blank pixel-column between each + character as they scroll. + + Note that the ``wait``, ``loop`` and ``monospace`` arguments must be specified + using their keyword. + """ if not wait: thread = threading.Thread( target=self.scroll, args=(value, delay, True, loop, monospace) @@ -82,6 +98,23 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): break def show(self, value, delay=400, wait=True, loop=False, clear=False): + """ + Display the ``image``. + + If ``value`` is a string, float or integer, display letters/digits in sequence. + Otherwise, if ``value`` is an iterable sequence of images, display these images in sequence. + Each letter, digit or image is shown with ``delay`` milliseconds between them. + + If ``wait`` is ``True``, this function will block until the animation is + finished, otherwise the animation will happen in the background. + + If ``loop`` is ``True``, the animation will repeat forever. + + If ``clear`` is ``True``, the display will be cleared after the iterable has finished. + + Note that the ``wait``, ``loop`` and ``clear`` arguments must be specified + using their keyword. + """ if not wait: thread = threading.Thread( target=self.show, args=(value, delay, True, loop, clear) @@ -145,33 +178,60 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): self.clear() def get_pixel(self, x, y): + """ + Return the brightness of the LED at column ``x`` and row ``y`` as an + integer between 0 (off) and 9 (bright). + """ self.__lock.acquire() pixel = self.__image.get_pixel(x, y) self.__lock.release() return pixel def set_pixel(self, x, y, value): + """ + Set the brightness of the LED at column ``x`` and row ``y`` to ``value``, + which has to be an integer between 0 and 9. + """ self.__lock.acquire() self.__image.set_pixel(x, y, value) self.__lock.release() self.__update_client() def clear(self): + """ + Set the brightness of all LEDs to 0 (off). + """ self.__lock.acquire() self.__image = Image() self.__lock.release() self.__update_client() def on(self): + """ + Use on() to turn on the display. + """ self.__on = True def off(self): + """ + Use off() to turn off the display. + """ self.__on = False def is_on(self): + """ + Returns ``True`` if the display is on, otherwise returns ``False``. + """ return self.__on def read_light_level(self): + """ + Not implemented yet. + + Use the display's LEDs in reverse-bias mode to sense the amount of light + falling on the display. Returns an integer between 0 and 255 representing + the light level, with larger meaning more light. + """ raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) # Helpers diff --git a/src/microbit/model/image.py b/src/microbit/__model/image.py similarity index 75% rename from src/microbit/model/image.py rename to src/microbit/__model/image.py index 18d75cd1d..a1aef09c6 100644 --- a/src/microbit/model/image.py +++ b/src/microbit/__model/image.py @@ -3,6 +3,43 @@ class Image: + """ + If ``string`` is used, it has to consist of digits 0-9 arranged into + lines, describing the image, for example:: + + image = Image("90009:" + "09090:" + "00900:" + "09090:" + "90009") + + will create a 5×5 image of an X. The end of a line is indicated by a colon. + It's also possible to use a newline (\\n) to indicate the end of a line + like this:: + + image = Image("90009\\n" + "09090\\n" + "00900\\n" + "09090\\n" + "90009") + + The other form creates an empty image with ``width`` columns and + ``height`` rows. Optionally ``buffer`` can be an array of + ``width``×``height`` integers in range 0-9 to initialize the image:: + + Image(2, 2, b'\x08\x08\x08\x08') + + or:: + + Image(2, 2, bytearray([9,9,9,9])) + + Will create a 2 x 2 pixel image at full brightness. + + .. note:: + + Keyword arguments cannot be passed to ``buffer``. + """ + # Attributes assigned (to functions) later; # having this here helps the pylint. HEART = None @@ -72,10 +109,9 @@ class Image: ALL_ARROWS = None # implementing image model as described here: - # https://microbit-micropython.readthedocs.io/en/latest/image.html + # https://microbit-micropython.readthedocs.io/en/v1.0.1/image.html def __init__(self, *args, **kwargs): - # Depending on the number of arguments # in constructor, it treat args differently. @@ -108,15 +144,28 @@ def __init__(self, *args, **kwargs): self.read_only = False def width(self): + """ + Return the number of columns in the image. + """ if len(self.__LED) > 0: return len(self.__LED[0]) else: return 0 def height(self): + """ + Return the numbers of rows in the image. + """ return len(self.__LED) def set_pixel(self, x, y, value): + """ + Set the brightness of the pixel at column ``x`` and row ``y`` to the + ``value``, which has to be between 0 (dark) and 9 (bright). + + This method will raise an exception when called on any of the built-in + read-only images, like ``Image.HEART``. + """ if self.read_only: raise TypeError(CONSTANTS.COPY_ERR_MESSAGE) elif not self.__valid_pos(x, y): @@ -127,47 +176,88 @@ def set_pixel(self, x, y, value): self.__LED[y][x] = value def get_pixel(self, x, y): + """ + Return the brightness of pixel at column ``x`` and row ``y`` as an + integer between 0 and 9. + """ if self.__valid_pos(x, y): return self.__LED[y][x] else: raise ValueError(CONSTANTS.INDEX_ERR) def shift_up(self, n): + """ + Return a new image created by shifting the picture up by ``n`` rows. + """ return self.__shift_vertical(-n) def shift_down(self, n): + """ + Return a new image created by shifting the picture down by ``n`` rows. + """ return self.__shift_vertical(n) def shift_right(self, n): + """ + Return a new image created by shifting the picture right by ``n`` + columns. + """ return self.__shift_horizontal(n) def shift_left(self, n): + """ + Return a new image created by shifting the picture left by ``n`` + columns. + """ return self.__shift_horizontal(-n) def crop(self, x, y, w, h): + """ + Return a new image by cropping the picture to a width of ``w`` and a + height of ``h``, starting with the pixel at column ``x`` and row ``y``. + """ res = Image(w, h) res.blit(self, x, y, w, h) return res def copy(self): + """ + Return an exact copy of the image. + """ return Image(self.__create_string()) # This inverts the brightness of each LED. # ie: Pixel that is at brightness 4 would become brightness 5 # and pixel that is at brightness 9 would become brightness 0. def invert(self): + """ + Return a new image by inverting the brightness of the pixels in the + source image. + """ for y in range(self.height()): for x in range(self.width()): self.set_pixel(x, y, CONSTANTS.BRIGHTNESS_MAX - self.get_pixel(x, y)) # This fills all LEDs with same brightness. def fill(self, value): + """ + Set the brightness of all the pixels in the image to the + ``value``, which has to be between 0 (dark) and 9 (bright). + + This method will raise an exception when called on any of the built-in + read-only images, like ``Image.HEART``. + """ for y in range(self.height()): for x in range(self.width()): self.set_pixel(x, y, value) # This transposes a certain area (w x h) on src onto the current image. def blit(self, src, x, y, w, h, xdest=0, ydest=0): + """ + Copy the rectangle defined by ``x``, ``y``, ``w``, ``h`` from the image ``src`` into + this image at ``xdest``, ``ydest``. + Areas in the source rectangle, but outside the source image are treated as having a value of 0. + """ if not src.__valid_pos(x, y): raise ValueError(CONSTANTS.INDEX_ERR) @@ -183,6 +273,9 @@ def blit(self, src, x, y, w, h, xdest=0, ydest=0): # This adds two images (if other object is not an image, throws error). # The images must be the same size. def __add__(self, other): + """ + Create a new image by adding the brightness values from the two images for each pixel. + """ if not isinstance(other, Image): raise TypeError( CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(self)}', '{type(other)}'" @@ -202,6 +295,9 @@ def __add__(self, other): # This multiplies image by number (if other factor is not a number, it throws an error). def __mul__(self, other): + """ + Create a new image by multiplying the brightness of each pixel by n. + """ try: float_val = float(other) except TypeError: @@ -217,6 +313,9 @@ def __mul__(self, other): return res def __repr__(self): + """ + Get a compact string representation of the image. + """ ret_str = "Image('" for index_y in range(self.height()): ret_str += self.__row_to_str(index_y) @@ -226,6 +325,9 @@ def __repr__(self): return ret_str def __str__(self): + """ + Get a readable string representation of the image. + """ ret_str = "Image('\n" for index_y in range(self.height()): ret_str += "\t" + self.__row_to_str(index_y) + "\n" diff --git a/src/microbit/model/microbit_model.py b/src/microbit/__model/microbit_model.py similarity index 91% rename from src/microbit/model/microbit_model.py rename to src/microbit/__model/microbit_model.py index 521f8def1..a1de5045d 100644 --- a/src/microbit/model/microbit_model.py +++ b/src/microbit/__model/microbit_model.py @@ -20,4 +20,4 @@ def running_time(self): return time.time() - self.__start_time -mb = MicrobitModel() +__mb = MicrobitModel() diff --git a/src/microbit/model/producer_property.py b/src/microbit/__model/producer_property.py similarity index 100% rename from src/microbit/model/producer_property.py rename to src/microbit/__model/producer_property.py diff --git a/src/microbit/model/button.py b/src/microbit/model/button.py deleted file mode 100644 index 54a60e8a4..000000000 --- a/src/microbit/model/button.py +++ /dev/null @@ -1,27 +0,0 @@ -class Button: - # The implementation is based off of https://github.com/bbcmicrobit/micropython/blob/master/docs/button.rst. - def __init__(self): - self.__pressed = False - self.__presses = 0 - self.__prev_pressed = False - - def is_pressed(self): - return self.__pressed - - def was_pressed(self): - res = self.__prev_pressed - self.__prev_pressed = False - return res - - def get_presses(self): - res = self.__presses - self.__presses = 0 - return res - - def __press_down(self): - self.__pressed = True - self.__prev_pressed = True - self.__presses += 1 - - def __release(self): - self.__pressed = False diff --git a/src/microbit/shim.py b/src/microbit/shim.py deleted file mode 100644 index 86302057a..000000000 --- a/src/microbit/shim.py +++ /dev/null @@ -1,14 +0,0 @@ -from .model import microbit_model -from .model import image - -microbit = microbit_model.mb -Image = image.Image -display = microbit.display - - -def sleep(n): - microbit_model.mb.sleep(n) - - -def running_time(): - microbit_model.mb.running_time() diff --git a/src/microbit/test/test_button.py b/src/microbit/test/test_button.py index aee9cbb43..9e0b3cbcb 100644 --- a/src/microbit/test/test_button.py +++ b/src/microbit/test/test_button.py @@ -1,5 +1,5 @@ import pytest -from ..model.button import Button +from ..__model.button import Button class TestButton(object): diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 47559af88..fc5c0ab34 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -1,12 +1,11 @@ import pytest import threading from unittest import mock - from common import utils -from ..model import constants as CONSTANTS -from ..model.display import Display -from ..model.image import Image -from .. import shim + +from ..__model import constants as CONSTANTS +from ..__model.display import Display +from ..__model.image import Image STR_A = "09900:90090:99990:90090:90090" diff --git a/src/microbit/test/test_image.py b/src/microbit/test/test_image.py index 78029674f..c4d2731d6 100644 --- a/src/microbit/test/test_image.py +++ b/src/microbit/test/test_image.py @@ -1,7 +1,7 @@ import pytest -from ..model.image import Image +from ..__model.image import Image -from ..model import constants as CONSTANTS +from ..__model import constants as CONSTANTS class TestImage(object): diff --git a/src/microbit/test/test_microbit_model.py b/src/microbit/test/test_microbit_model.py index e37df6019..4ce6ca0a2 100644 --- a/src/microbit/test/test_microbit_model.py +++ b/src/microbit/test/test_microbit_model.py @@ -2,23 +2,25 @@ import pytest from unittest import mock -from ..model.microbit_model import MicrobitModel +from ..__model.microbit_model import MicrobitModel class TestMicrobitModel(object): def setup_method(self): - self.mb = MicrobitModel() + self.__mb = MicrobitModel() @pytest.mark.parametrize("value", [9, 30, 1999]) def test_sleep(self, value): time.sleep = mock.Mock() - self.mb.sleep(value) + self.__mb.sleep(value) time.sleep.assert_called_with(value / 1000) def test_running_time(self): mock_start_time = 10 mock_end_time = 300 - self.mb._MicrobitModel__start_time = mock_start_time + self.__mb._MicrobitModel__start_time = mock_start_time time.time = mock.MagicMock(return_value=mock_end_time) print(time.time()) - assert mock_end_time - mock_start_time == pytest.approx(self.mb.running_time()) + assert mock_end_time - mock_start_time == pytest.approx( + self.__mb.running_time() + ) diff --git a/src/microbit/test/test_shim.py b/src/microbit/test/test_shim.py index 68853ec9b..25c1afac8 100644 --- a/src/microbit/test/test_shim.py +++ b/src/microbit/test/test_shim.py @@ -2,18 +2,21 @@ import pytest from unittest import mock -from .. import shim -from ..model import microbit_model + +from .. import * +from ..__model.microbit_model import MicrobitModel + +# tests methods in __init__.py class TestShim(object): def test_sleep(self): milliseconds = 100 - microbit_model.mb.sleep = mock.Mock() - shim.sleep(milliseconds) - microbit_model.mb.sleep.assert_called_with(milliseconds) + MicrobitModel.sleep = mock.Mock() + sleep(milliseconds) + MicrobitModel.sleep.assert_called_with(milliseconds) def test_running_time(self): - microbit_model.mb.running_time = mock.Mock() - shim.running_time() - microbit_model.mb.running_time.assert_called_once() + MicrobitModel.running_time = mock.Mock() + running_time() + MicrobitModel.running_time.assert_called_once() diff --git a/src/process_user_code.py b/src/process_user_code.py index 8bc283c7e..fbcf08ff8 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -30,7 +30,7 @@ # This import must happen after the sys.path is modified from adafruit_circuitplayground.express import cpx from adafruit_circuitplayground.telemetry import telemetry_py -from microbit.model.microbit_model import mb +from microbit.__model.microbit_model import __mb as mb # Handle User Inputs Thread