From 806576be5f9d34c68415abe9103c33f23401de07 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 24 Jan 2020 16:34:34 -0800 Subject: [PATCH 01/38] shim design example --- gulpfile.js | 271 ++++++++++++++------------- src/microbit/__init__.py | 1 + src/microbit/code_processing_shim.py | 13 ++ src/microbit/display.py | 6 + src/microbit/microbit_model.py | 15 ++ 5 files changed, 171 insertions(+), 135 deletions(-) create mode 100644 src/microbit/__init__.py create mode 100644 src/microbit/code_processing_shim.py create mode 100644 src/microbit/display.py create mode 100644 src/microbit/microbit_model.py diff --git a/gulpfile.js b/gulpfile.js index 3c106798d..c642abfed 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,135 +1,136 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const gulp = require("gulp"); - -const ts = require("gulp-typescript"); -const sourcemaps = require("gulp-sourcemaps"); -const typescript = require("typescript"); -const del = require("del"); -const es = require("event-stream"); -const vsce = require("vsce"); -const nls = require("vscode-nls-dev"); - -const tsProject = ts.createProject("./tsconfig.json", { typescript }); - -const inlineMap = true; -const inlineSource = false; -const outDest = "out"; - -// A list of all locales supported by VSCode can be found here: https://code.visualstudio.com/docs/getstarted/locales -const languages = [{ folderName: "en", id: "en" }]; - -gulp.task("clean", () => { - return del( - [ - "out/*", - "package.nls.*.json", - "../../dist/*0.0.0-UNTRACKEDVERSION.vsix" - ], - { force: true } - ); -}); - -const pythonToMove = [ - "./src/adafruit_circuitplayground/*.*", - "./src/*.py", - "./src/requirements.txt", -]; - -gulp.task("python-compile", () => { - // the base option sets the relative root for the set of files, - // preserving the folder structure - return gulp.src(pythonToMove, { base: "./src/" }).pipe(gulp.dest("out")); -}); - -gulp.task("internal-compile", () => { - return compile(false); -}); - -gulp.task("internal-nls-compile", () => { - return compile(true); -}); - -gulp.task("add-locales", () => { - return gulp - .src(["package.nls.json"]) - .pipe(nls.createAdditionalLanguageFiles(languages, "locales")) - .pipe(gulp.dest(".")); -}); - -gulp.task("vsce:publish", () => { - return vsce.publish(); -}); - -gulp.task("vsce:package", () => { - return vsce.createVSIX({ - packagePath: "../../dist/deviceSimulatorExpress-0.0.0-UNTRACKEDVERSION.vsix" - }); -}); - -gulp.task( - "compile", - gulp.series("clean", "internal-compile", "python-compile", callback => { - callback(); - }) -); - -gulp.task( - "build", - gulp.series( - "clean", - "internal-nls-compile", - "python-compile", - "add-locales", - callback => { - callback(); - } - ) -); - -gulp.task( - "publish", - gulp.series("compile", "vsce:publish", callback => { - callback(); - }) -); - -gulp.task( - "package", - gulp.series("compile", "vsce:package", callback => { - callback(); - }) -); - -//---- internal - -function compile(buildNls) { - var r = tsProject - .src() - .pipe(sourcemaps.init()) - .pipe(tsProject()) - .js.pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through()) - .pipe( - buildNls - ? nls.createAdditionalLanguageFiles(languages, "locales", "out") - : es.through() - ); - - if (inlineMap && inlineSource) { - r = r.pipe(sourcemaps.write()); - } else { - r = r.pipe( - sourcemaps.write("../out", { - // no inlined source - includeContent: inlineSource, - // Return relative source map root directories per file. - sourceRoot: "../src" - }) - ); - } - - return r.pipe(gulp.dest(outDest)); -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const gulp = require("gulp"); + +const ts = require("gulp-typescript"); +const sourcemaps = require("gulp-sourcemaps"); +const typescript = require("typescript"); +const del = require("del"); +const es = require("event-stream"); +const vsce = require("vsce"); +const nls = require("vscode-nls-dev"); + +const tsProject = ts.createProject("./tsconfig.json", { typescript }); + +const inlineMap = true; +const inlineSource = false; +const outDest = "out"; + +// A list of all locales supported by VSCode can be found here: https://code.visualstudio.com/docs/getstarted/locales +const languages = [{ folderName: "en", id: "en" }]; + +gulp.task("clean", () => { + return del( + [ + "out/*", + "package.nls.*.json", + "../../dist/*0.0.0-UNTRACKEDVERSION.vsix" + ], + { force: true } + ); +}); + +const pythonToMove = [ + "./src/adafruit_circuitplayground/*.*", + "./src/microbit/*.*", + "./src/*.py", + "./src/requirements.txt", +]; + +gulp.task("python-compile", () => { + // the base option sets the relative root for the set of files, + // preserving the folder structure + return gulp.src(pythonToMove, { base: "./src/" }).pipe(gulp.dest("out")); +}); + +gulp.task("internal-compile", () => { + return compile(false); +}); + +gulp.task("internal-nls-compile", () => { + return compile(true); +}); + +gulp.task("add-locales", () => { + return gulp + .src(["package.nls.json"]) + .pipe(nls.createAdditionalLanguageFiles(languages, "locales")) + .pipe(gulp.dest(".")); +}); + +gulp.task("vsce:publish", () => { + return vsce.publish(); +}); + +gulp.task("vsce:package", () => { + return vsce.createVSIX({ + packagePath: "../../dist/deviceSimulatorExpress-0.0.0-UNTRACKEDVERSION.vsix" + }); +}); + +gulp.task( + "compile", + gulp.series("clean", "internal-compile", "python-compile", callback => { + callback(); + }) +); + +gulp.task( + "build", + gulp.series( + "clean", + "internal-nls-compile", + "python-compile", + "add-locales", + callback => { + callback(); + } + ) +); + +gulp.task( + "publish", + gulp.series("compile", "vsce:publish", callback => { + callback(); + }) +); + +gulp.task( + "package", + gulp.series("compile", "vsce:package", callback => { + callback(); + }) +); + +//---- internal + +function compile(buildNls) { + var r = tsProject + .src() + .pipe(sourcemaps.init()) + .pipe(tsProject()) + .js.pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through()) + .pipe( + buildNls + ? nls.createAdditionalLanguageFiles(languages, "locales", "out") + : es.through() + ); + + if (inlineMap && inlineSource) { + r = r.pipe(sourcemaps.write()); + } else { + r = r.pipe( + sourcemaps.write("../out", { + // no inlined source + includeContent: inlineSource, + // Return relative source map root directories per file. + sourceRoot: "../src" + }) + ); + } + + return r.pipe(gulp.dest(outDest)); +} diff --git a/src/microbit/__init__.py b/src/microbit/__init__.py new file mode 100644 index 000000000..3f308d8fb --- /dev/null +++ b/src/microbit/__init__.py @@ -0,0 +1 @@ +from .code_processing_shim import * \ No newline at end of file diff --git a/src/microbit/code_processing_shim.py b/src/microbit/code_processing_shim.py new file mode 100644 index 000000000..a3996c21f --- /dev/null +++ b/src/microbit/code_processing_shim.py @@ -0,0 +1,13 @@ +from . import microbit_model + +# EXAMPLE +# can be called simply as "show_message("string")" +def show_message(message): + microbit_model.mb.show_message(message) + +# EXAMPLE +# can be called with display.scroll("string") +class display: + @staticmethod + def scroll(self, message): + microbit_model.mb.display.scroll(self,message) \ No newline at end of file diff --git a/src/microbit/display.py b/src/microbit/display.py new file mode 100644 index 000000000..699c4397b --- /dev/null +++ b/src/microbit/display.py @@ -0,0 +1,6 @@ +# class for microbit led display +class Display: + + # SAMPLE FUNCTION + def scroll(self, message): + print("scroll!! " + message) \ No newline at end of file diff --git a/src/microbit/microbit_model.py b/src/microbit/microbit_model.py new file mode 100644 index 000000000..1d6ebec95 --- /dev/null +++ b/src/microbit/microbit_model.py @@ -0,0 +1,15 @@ +from .display import Display + +class MicrobitModel: + def __init__(self): + # State in the Python process + self.display = Display() + self.__state = { } + self.__debug_mode = False + self.__abs_path_to_code_file = '' + + # SAMPLE FUNCTION + def show_message(self, message): + print("message!! " + message) + +mb = MicrobitModel() \ No newline at end of file From deceeb75debae7df4e92f0e6306ad28273b15bfc Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 28 Jan 2020 10:27:16 -0800 Subject: [PATCH 02/38] some effort on image object design --- src/microbit/code_processing_shim.py | 22 ++++++++---- src/microbit/constants.py | 5 +++ src/microbit/display.py | 7 +++- src/microbit/image.py | 52 ++++++++++++++++++++++++++++ src/test_code/code.py | 38 ++++++++++---------- 5 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 src/microbit/constants.py create mode 100644 src/microbit/image.py diff --git a/src/microbit/code_processing_shim.py b/src/microbit/code_processing_shim.py index a3996c21f..c4b5d6146 100644 --- a/src/microbit/code_processing_shim.py +++ b/src/microbit/code_processing_shim.py @@ -1,13 +1,23 @@ from . import microbit_model +from . import image +from . import constants as CONSTANTS # EXAMPLE # can be called simply as "show_message("string")" def show_message(message): microbit_model.mb.show_message(message) -# EXAMPLE -# can be called with display.scroll("string") -class display: - @staticmethod - def scroll(self, message): - microbit_model.mb.display.scroll(self,message) \ No newline at end of file +# def Image(pattern = CONSTANTS.BLANK): +# img = image.Image(pattern) +# assign_constants(img) + +def assign_constants(obj): + obj.BOAT = image.MicrobitImage(CONSTANTS.BOAT) + +display = microbit_model.mb.display +Image = image.Image + + +# define "constants" here +# Image.BOAT = image.Image(CONSTANTS.BOAT) + diff --git a/src/microbit/constants.py b/src/microbit/constants.py new file mode 100644 index 000000000..9ac1e2858 --- /dev/null +++ b/src/microbit/constants.py @@ -0,0 +1,5 @@ +BOAT = ("05050:","05050:","05050:","99999:","09990") + +BLANK= "00000:00000:00000:00000:00000" + +COPY_ERR_MESSAGE = "please copy() first" \ No newline at end of file diff --git a/src/microbit/display.py b/src/microbit/display.py index 699c4397b..6ce2043bd 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -1,6 +1,11 @@ # class for microbit led display class Display: + def __init__(self): + # State in the Python process + self.count = 4 + # SAMPLE FUNCTION def scroll(self, message): - print("scroll!! " + message) \ No newline at end of file + print("scroll!! " + str(self.count)) + self.count = self.count + 1 \ No newline at end of file diff --git a/src/microbit/image.py b/src/microbit/image.py new file mode 100644 index 000000000..f9b1d727c --- /dev/null +++ b/src/microbit/image.py @@ -0,0 +1,52 @@ +from . import microbit_model +from . import constants as CONSTANTS +from . import display + + +class Image: + _BOAT = None + def __init__(self, pattern = CONSTANTS.BLANK, width=5,height=5): + # State in the Python process + self.width = width + self.height = height + if type(pattern) is str: + self.LED = self.convert_to_array(pattern) + else: + self.LED = pattern + + + def convert_to_array(self, pattern): + arr = [] + sub_str = "" + for elem in pattern: + sub_str= sub_str + elem + if elem == ":": + arr.append(sub_str) + sub_str = "" + + + arr.append(sub_str) + return arr + + + def set_pixel(self,x,y,value): + + sub_arr = self.LED[y] + + new_list = list(sub_arr) + new_list[x] = value + + self.LED[y] = "".join(new_list) + + + def get_pixel(self,x,y): + return self.LED[y][x] + + def copy(self): + return Image(list(self.LED)) + + def getvalue(self): + self._BOAT = Image(CONSTANTS.BOAT) + return self._BOAT + + BOAT = property(getvalue) diff --git a/src/test_code/code.py b/src/test_code/code.py index 369561cc5..15f94fb04 100644 --- a/src/test_code/code.py +++ b/src/test_code/code.py @@ -1,19 +1,19 @@ -from adafruit_circuitplayground.express import cpx -import time - -cpx.pixels.brightness = 0.3 -cpx.pixels.fill((0, 0, 0)) # Turn off the NeoPixels if they're on! -cpx.pixels.show() - -while True: - if cpx.button_a: - cpx.pixels[2] = (0, 255, 0) - else: - cpx.pixels[2] = (0, 0, 0) - - if cpx.button_b: - cpx.pixels[7] = (0, 0, 255) - else: - cpx.pixels[7] = (0, 0, 0) - cpx.pixels.show() - +# from adafruit_circuitplayground.express import cpx +# import time + +# cpx.pixels.brightness = 0.3 +# cpx.pixels.fill((0, 0, 0)) # Turn off the NeoPixels if they're on! +# cpx.pixels.show() + +# while True: +# if cpx.button_a: +# cpx.pixels[2] = (0, 255, 0) +# else: +# cpx.pixels[2] = (0, 0, 0) + +# if cpx.button_b: +# cpx.pixels[7] = (0, 0, 255) +# else: +# cpx.pixels[7] = (0, 0, 0) +# cpx.pixels.show() + From 8ed9e5b9db4a19e11415ab36c2e506762f2e8a0b Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 28 Jan 2020 14:02:42 -0800 Subject: [PATCH 03/38] initial look at image class --- src/microbit/code_processing_shim.py | 11 +---- src/microbit/constants.py | 18 ++++++-- src/microbit/image.py | 61 +++++++++++++++++----------- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/microbit/code_processing_shim.py b/src/microbit/code_processing_shim.py index c4b5d6146..eb53e10ab 100644 --- a/src/microbit/code_processing_shim.py +++ b/src/microbit/code_processing_shim.py @@ -7,17 +7,10 @@ def show_message(message): microbit_model.mb.show_message(message) -# def Image(pattern = CONSTANTS.BLANK): -# img = image.Image(pattern) -# assign_constants(img) - -def assign_constants(obj): - obj.BOAT = image.MicrobitImage(CONSTANTS.BOAT) display = microbit_model.mb.display -Image = image.Image +microbit = microbit_model.mb +Image = image.Image -# define "constants" here -# Image.BOAT = image.Image(CONSTANTS.BOAT) diff --git a/src/microbit/constants.py b/src/microbit/constants.py index 9ac1e2858..71d5b297b 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -1,5 +1,17 @@ -BOAT = ("05050:","05050:","05050:","99999:","09990") -BLANK= "00000:00000:00000:00000:00000" +BOAT = ([0, 5, 0, 5, 0], + [0, 5, 0, 5, 0], + [0, 5, 0, 5, 0], + [9, 9, 9, 9, 9], + [0, 9, 9, 9, 0]) -COPY_ERR_MESSAGE = "please copy() first" \ No newline at end of file +BLANK= [[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]] + + +COPY_ERR_MESSAGE = "please copy() first" + +LED_MAX = 5 \ No newline at end of file diff --git a/src/microbit/image.py b/src/microbit/image.py index f9b1d727c..58a670af7 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -2,13 +2,9 @@ from . import constants as CONSTANTS from . import display - class Image: - _BOAT = None - def __init__(self, pattern = CONSTANTS.BLANK, width=5,height=5): + def __init__(self, pattern = CONSTANTS.BLANK): # State in the Python process - self.width = width - self.height = height if type(pattern) is str: self.LED = self.convert_to_array(pattern) else: @@ -17,26 +13,21 @@ def __init__(self, pattern = CONSTANTS.BLANK, width=5,height=5): def convert_to_array(self, pattern): arr = [] - sub_str = "" - for elem in pattern: - sub_str= sub_str + elem + sub_arr = [] + for elem in pattern: + sub_arr.append(elem) if elem == ":": - arr.append(sub_str) - sub_str = "" - - - arr.append(sub_str) + arr.append(sub_arr) + sub_arr = [] + arr.append(sub_arr) return arr def set_pixel(self,x,y,value): - - sub_arr = self.LED[y] - - new_list = list(sub_arr) - new_list[x] = value - - self.LED[y] = "".join(new_list) + try: + self.LED[y][x] = value + except TypeError: + print(CONSTANTS.COPY_ERR_MESSAGE) def get_pixel(self,x,y): @@ -45,8 +36,30 @@ def get_pixel(self,x,y): def copy(self): return Image(list(self.LED)) - def getvalue(self): - self._BOAT = Image(CONSTANTS.BOAT) - return self._BOAT + def fill(self,value): + for y in range(0,self.height): + for x in range(0,self.width): + self.LED[y][x] = value - BOAT = property(getvalue) + @property + def width(self): + if len(self.LED): + return len(self.LED[0]) + else: + return 0 + + @width.setter + def width(self): + # will name exception later + raise Exception + + @property + def height(self): + return len(self.LED) + + @height.setter + def height(self): + # will name exception later + raise Exception + + From 32060ceb9c443613bd8e67ff1207de6983ed3889 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 28 Jan 2020 14:05:14 -0800 Subject: [PATCH 04/38] fixes to image --- src/microbit/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/microbit/image.py b/src/microbit/image.py index 58a670af7..15ad3e6a8 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -34,8 +34,8 @@ def get_pixel(self,x,y): return self.LED[y][x] def copy(self): - return Image(list(self.LED)) - + return Image(self.LED) + def fill(self,value): for y in range(0,self.height): for x in range(0,self.width): From ef6498cf02e11eb90cf0f9ab018a9e6bca5af2a0 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 28 Jan 2020 15:56:15 -0800 Subject: [PATCH 05/38] finished first draft of most image methods --- src/microbit/code_processing_shim.py | 8 +++ src/microbit/constants.py | 6 ++ src/microbit/image.py | 101 +++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 6 deletions(-) diff --git a/src/microbit/code_processing_shim.py b/src/microbit/code_processing_shim.py index eb53e10ab..6ba6366a7 100644 --- a/src/microbit/code_processing_shim.py +++ b/src/microbit/code_processing_shim.py @@ -13,4 +13,12 @@ def show_message(message): microbit = microbit_model.mb Image = image.Image +def repr(image): + + ret_str = "Image(\'" + for index_y in range(0,image.height): + ret_str += image.row_to_str(index_y) + + ret_str = ret_str + "\')" + return ret_str \ No newline at end of file diff --git a/src/microbit/constants.py b/src/microbit/constants.py index 71d5b297b..a156233f0 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -5,6 +5,12 @@ [9, 9, 9, 9, 9], [0, 9, 9, 9, 0]) +HEART = [[0, 9, 0, 9, 0], + [9, 9, 9, 9, 9], + [9, 9, 9, 9, 9], + [0, 9, 9, 9, 0], + [0, 0, 9, 0, 0]] + BLANK= [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], diff --git a/src/microbit/image.py b/src/microbit/image.py index 15ad3e6a8..95e62fd9f 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -3,14 +3,25 @@ from . import display class Image: - def __init__(self, pattern = CONSTANTS.BLANK): + def __init__(self, *args, **kwargs): + print("args") + print(args) # State in the Python process - if type(pattern) is str: - self.LED = self.convert_to_array(pattern) + if (len(args)==0): + self.LED = CONSTANTS.BLANK + elif (len(args)==1): + pattern = args[0] + if type(pattern) is str: + self.LED = self.convert_to_array(pattern) + else: + self.LED = pattern else: - self.LED = pattern + width = args[0] + height = args[1] + self.LED = self.create_leds(width,height) + + - def convert_to_array(self, pattern): arr = [] sub_arr = [] @@ -22,6 +33,17 @@ def convert_to_array(self, pattern): arr.append(sub_arr) return arr + def create_leds(self, w, h): + arr = [] + for _ in range(0,h): + sub_arr = [] + for _ in range(0,w): + sub_arr.append(0) + + arr.append(sub_arr) + + return arr + def set_pixel(self,x,y,value): try: @@ -36,10 +58,16 @@ def get_pixel(self,x,y): def copy(self): return Image(self.LED) + def invert(self,value): + for y in range(0,self.height): + for x in range(0,self.width): + self.set_pixel(x, y, 9-value) + + def fill(self,value): for y in range(0,self.height): for x in range(0,self.width): - self.LED[y][x] = value + self.set_pixel(x, y, value) @property def width(self): @@ -62,4 +90,65 @@ def height(self): # will name exception later raise Exception + def blit(self, src, x, y, w, h, xdest=0, ydest=0): + for count_y in range(0, h): + for count_x in range(0, w): + if (ydest + count_y < self.height and + xdest + count_x < self.width and + y + count_y < src.height and + x + count_x < src.width): + transfer_pixel = src.get_pixel(x + count_x, y + count_y) + self.set_pixel(xdest + count_x, ydest + count_y, transfer_pixel) + + def crop(self, x, y, w, h): + res = Image(w, h) + res.blit(self, x, y, w, h) + return res + + def shift_vertical(self,n): + + res = Image(self.width, self.height) + if n > 0: + # up + res.blit(self, 0, n, self.width, self.height-n, 0, 0) + else: + # down + res.blit(self, 0, 0, self.width, self.height-abs(n), 0, abs(n)) + + return res + + def shift_horizontal(self,n): + res = Image(self.width, self.height) + if n > 0: + # right + res.blit(self, 0, 0, self.width-n, self.height, n, 0) + else: + # left + res.blit(self, n, 0, self.width-n, self.height, 0, 0) + + return res + + + def shift_up(self,n): + return self.shift_vertical(n) + + def shift_down(self,n): + return self.shift_vertical(n*-1) + + def shift_right(self,n): + return self.shift_horizontal(n) + + def shift_left(self,n): + return self.shift_horizontal(n*-1) + + + def row_to_str(self, y): + new_str = "" + for x in range(0,self.width): + new_str = new_str + str(self.get_pixel(x,y)) + + new_str = new_str + ":" + + return new_str + From d2a76721b7b647fb1d13ed1988615279c9350aaf Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 28 Jan 2020 16:00:38 -0800 Subject: [PATCH 06/38] width and height modifications --- src/microbit/code_processing_shim.py | 2 +- src/microbit/image.py | 43 ++++++++++------------------ 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/microbit/code_processing_shim.py b/src/microbit/code_processing_shim.py index 6ba6366a7..d4a5c1115 100644 --- a/src/microbit/code_processing_shim.py +++ b/src/microbit/code_processing_shim.py @@ -16,7 +16,7 @@ def show_message(message): def repr(image): ret_str = "Image(\'" - for index_y in range(0,image.height): + for index_y in range(0,image.height()): ret_str += image.row_to_str(index_y) ret_str = ret_str + "\')" diff --git a/src/microbit/image.py b/src/microbit/image.py index 95e62fd9f..f24248b6a 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -59,44 +59,32 @@ def copy(self): return Image(self.LED) def invert(self,value): - for y in range(0,self.height): - for x in range(0,self.width): + for y in range(0,self.height()): + for x in range(0,self.width()): self.set_pixel(x, y, 9-value) def fill(self,value): - for y in range(0,self.height): - for x in range(0,self.width): + for y in range(0,self.height()): + for x in range(0,self.width()): self.set_pixel(x, y, value) - @property def width(self): if len(self.LED): return len(self.LED[0]) else: return 0 - @width.setter - def width(self): - # will name exception later - raise Exception - - @property def height(self): return len(self.LED) - - @height.setter - def height(self): - # will name exception later - raise Exception def blit(self, src, x, y, w, h, xdest=0, ydest=0): for count_y in range(0, h): for count_x in range(0, w): - if (ydest + count_y < self.height and - xdest + count_x < self.width and - y + count_y < src.height and - x + count_x < src.width): + if (ydest + count_y < self.height() and + xdest + count_x < self.width() and + y + count_y < src.height() and + x + count_x < src.width()): transfer_pixel = src.get_pixel(x + count_x, y + count_y) self.set_pixel(xdest + count_x, ydest + count_y, transfer_pixel) @@ -107,25 +95,25 @@ def crop(self, x, y, w, h): def shift_vertical(self,n): - res = Image(self.width, self.height) + res = Image(self.width(), self.height()) if n > 0: # up - res.blit(self, 0, n, self.width, self.height-n, 0, 0) + res.blit(self, 0, n, self.width(), self.height()-n, 0, 0) else: # down - res.blit(self, 0, 0, self.width, self.height-abs(n), 0, abs(n)) + res.blit(self, 0, 0, self.width(), self.height()-abs(n), 0, abs(n)) return res def shift_horizontal(self,n): - res = Image(self.width, self.height) + res = Image(self.width(), self.height()) if n > 0: # right - res.blit(self, 0, 0, self.width-n, self.height, n, 0) + res.blit(self, 0, 0, self.width()-n, self.height(), n, 0) else: # left - res.blit(self, n, 0, self.width-n, self.height, 0, 0) + res.blit(self, n, 0, self.width()-n, self.height(), 0, 0) return res @@ -145,10 +133,9 @@ def shift_left(self,n): def row_to_str(self, y): new_str = "" - for x in range(0,self.width): + for x in range(0,self.width()): new_str = new_str + str(self.get_pixel(x,y)) new_str = new_str + ":" return new_str - From 16beee34091b58bf23a1756de1e81d6cc6b40e40 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 28 Jan 2020 17:36:04 -0800 Subject: [PATCH 07/38] more additions to image --- src/microbit/code_processing_shim.py | 16 ++++++- src/microbit/image.py | 66 +++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/microbit/code_processing_shim.py b/src/microbit/code_processing_shim.py index d4a5c1115..231f5e9b3 100644 --- a/src/microbit/code_processing_shim.py +++ b/src/microbit/code_processing_shim.py @@ -21,4 +21,18 @@ def repr(image): ret_str = ret_str + "\')" - return ret_str \ No newline at end of file + return ret_str + + +def str(image): + if type(image) is Image: + ret_str = "Image(\'\n" + for index_y in range(0,image.height()): + ret_str += "\t" + image.row_to_str(index_y) + "\n" + + ret_str = ret_str + "\')" + + return ret_str + else: + # if not image, call regular str class + return image.__str__() \ No newline at end of file diff --git a/src/microbit/image.py b/src/microbit/image.py index f24248b6a..5eee4c3c9 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -8,17 +8,22 @@ def __init__(self, *args, **kwargs): print(args) # State in the Python process if (len(args)==0): - self.LED = CONSTANTS.BLANK + self.__LED = CONSTANTS.BLANK elif (len(args)==1): pattern = args[0] if type(pattern) is str: - self.LED = self.convert_to_array(pattern) + self.__LED = self.convert_to_array(pattern) else: - self.LED = pattern + self.__LED = pattern else: + width = args[0] height = args[1] - self.LED = self.create_leds(width,height) + + if (width < 0 or height < 0): + raise Exception + + self.__LED = self.create_leds(width,height) @@ -26,11 +31,11 @@ def convert_to_array(self, pattern): arr = [] sub_arr = [] for elem in pattern: - sub_arr.append(elem) if elem == ":": arr.append(sub_arr) sub_arr = [] - arr.append(sub_arr) + else: + sub_arr.append(int(elem)) return arr def create_leds(self, w, h): @@ -47,16 +52,16 @@ def create_leds(self, w, h): def set_pixel(self,x,y,value): try: - self.LED[y][x] = value + self.__LED[y][x] = value except TypeError: print(CONSTANTS.COPY_ERR_MESSAGE) def get_pixel(self,x,y): - return self.LED[y][x] + return self.__LED[y][x] def copy(self): - return Image(self.LED) + return Image(self.__LED) def invert(self,value): for y in range(0,self.height()): @@ -70,13 +75,13 @@ def fill(self,value): self.set_pixel(x, y, value) def width(self): - if len(self.LED): - return len(self.LED[0]) + if len(self.__LED): + return len(self.__LED[0]) else: return 0 def height(self): - return len(self.LED) + return len(self.__LED) def blit(self, src, x, y, w, h, xdest=0, ydest=0): for count_y in range(0, h): @@ -139,3 +144,40 @@ def row_to_str(self, y): new_str = new_str + ":" return new_str + + + def limit_result(self,limit,result): + if (result > limit): + return limit + else: + return result + + def __add__(self, other): + if not (type(other) is Image): + raise TypeError(f"unsupported types for __add__: '{type(self)}', '{type(other)}'") + elif not (other.height() == self.height() and + other.width() == self.width()): + raise ValueError("images must be the same size") + else: + res = Image(self.width(), self.height()) + + for y in range(0,self.height()): + for x in range(0,self.width()): + sum = other.get_pixel(x,y) + self.get_pixel(x,y) + display_result = self.limit_result(9, sum) + res.set_pixel(x, y, display_result) + + return res + + + def __mul__(self, other): + float_val = float(other) + res = Image(self.width(), self.height()) + + for y in range(0,self.height()): + for x in range(0,self.width()): + product = self.get_pixel(x,y) * float_val + res.set_pixel(x, y, self.limit_result(9, product)) + + return res + \ No newline at end of file From 52ba07ebb21dc650c9bcbbc76ca791f23904d87b Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 28 Jan 2020 17:37:38 -0800 Subject: [PATCH 08/38] display microbit library --- src/microbit/constants.py | 15 ++- src/microbit/display.py | 76 +++++++++++++-- src/microbit/image.py | 147 ++++++++++++++++++++++++------ src/microbit/test/__init__.py | 0 src/microbit/test/test_display.py | 76 +++++++++++++++ 5 files changed, 276 insertions(+), 38 deletions(-) create mode 100644 src/microbit/test/__init__.py create mode 100644 src/microbit/test/test_display.py diff --git a/src/microbit/constants.py b/src/microbit/constants.py index 9ac1e2858..c64e085ee 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -1,5 +1,14 @@ -BOAT = ("05050:","05050:","05050:","99999:","09990") +BOAT = ("05050:", "05050:", "05050:", "99999:", "09990") -BLANK= "00000:00000:00000:00000:00000" +BLANK = "00000:00000:00000:00000:00000" -COPY_ERR_MESSAGE = "please copy() first" \ No newline at end of file +COPY_ERR_MESSAGE = "please copy() first" + +INDEX_ERR = "index out of bounds" + +BRIGHTNESS_ERR = "brightness out of bounds" + +LED_WIDTH = 5 +LED_HEIGHT = 5 + +NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" diff --git a/src/microbit/display.py b/src/microbit/display.py index 6ce2043bd..ec45ee2f1 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -1,11 +1,75 @@ -# class for microbit led display -class Display: +from . import constants as CONSTANTS +from .image import Image + +class Display: def __init__(self): # State in the Python process - self.count = 4 + self.__LEDs = [[0] * 5] * 5 + self.__on = True - # SAMPLE FUNCTION def scroll(self, message): - print("scroll!! " + str(self.count)) - self.count = self.count + 1 \ No newline at end of file + raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + + def show(self, value, delay=400, wait=True, loop=False, clear=False): + if isinstance(value, Image): + width = ( + value.width() + if value.width() <= CONSTANTS.LED_WIDTH + else CONSTANTS.LED_WIDTH + ) + height = ( + value.height() + if value.height() <= CONSTANTS.LED_HEIGHT + else CONSTANTS.LED_HEIGHT + ) + self.__LEDs = value.LED.copy() + self.__print() + elif isinstance(value, str): + pass + elif isinstance(value, float): + pass + elif isinstance(value, int): + pass + + def get_pixel(self, x, y): + if self.__valid_pos(x, y): + return self.__LEDs[y][x] + else: + raise ValueError(CONSTANTS.INDEX_ERR) + + def set_pixel(self, x, y, value): + if not self.__valid_pos(x, y): + raise ValueError(CONSTANTS.INDEX_ERR) + elif not self.__valid_brightness(value): + raise ValueError(CONSTANTS.BRIGHTNESS_ERR) + else: + self.__LEDs[y][x] = value + + def clear(self): + for y in range(CONSTANTS.LED_WIDTH): + for x in range(CONSTANTS.LED_HEIGHT): + self.__LEDs[y][x] = 0 + + 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 __valid_pos(self, x, y): + return 0 <= x and x <= 4 and 0 <= y and y <= 4 + + def __valid_brightness(self, value): + return 0 <= value and value <= 9 + + def __print(self): + for i in range(5): + print(self.__LEDs[i]) diff --git a/src/microbit/image.py b/src/microbit/image.py index f9b1d727c..e9fcf4e40 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -2,51 +2,140 @@ from . import constants as CONSTANTS from . import display - class Image: - _BOAT = None - def __init__(self, pattern = CONSTANTS.BLANK, width=5,height=5): + def __init__(self, *args, **kwargs): + print("args") + print(args) # State in the Python process - self.width = width - self.height = height - if type(pattern) is str: - self.LED = self.convert_to_array(pattern) + if (len(args)==0): + self.LED = CONSTANTS.BLANK + elif (len(args)==1): + pattern = args[0] + if type(pattern) is str: + self.LED = self.convert_to_array(pattern) + else: + self.LED = pattern else: - self.LED = pattern + width = args[0] + height = args[1] + self.LED = self.create_leds(width,height) + + - def convert_to_array(self, pattern): arr = [] - sub_str = "" - for elem in pattern: - sub_str= sub_str + elem + sub_arr = [] + for elem in pattern: + sub_arr.append(elem) if elem == ":": - arr.append(sub_str) - sub_str = "" - - - arr.append(sub_str) + arr.append(sub_arr) + sub_arr = [] + arr.append(sub_arr) return arr + def create_leds(self, w, h): + arr = [] + for _ in range(0,h): + sub_arr = [] + for _ in range(0,w): + sub_arr.append(0) - def set_pixel(self,x,y,value): + arr.append(sub_arr) + + return arr - sub_arr = self.LED[y] - new_list = list(sub_arr) - new_list[x] = value + def set_pixel(self,x,y,value): + try: + self.LED[y][x] = value + except TypeError: + print(CONSTANTS.COPY_ERR_MESSAGE) - self.LED[y] = "".join(new_list) - def get_pixel(self,x,y): return self.LED[y][x] - + def copy(self): - return Image(list(self.LED)) + return Image(self.LED) + + def invert(self,value): + for y in range(0,self.height()): + for x in range(0,self.width()): + self.set_pixel(x, y, 9-value) + + + def fill(self,value): + for y in range(0,self.height()): + for x in range(0,self.width()): + self.set_pixel(x, y, value) + + def width(self): + if len(self.LED): + return len(self.LED[0]) + else: + return 0 + + def height(self): + return len(self.LED) + + def blit(self, src, x, y, w, h, xdest=0, ydest=0): + for count_y in range(0, h): + for count_x in range(0, w): + if (ydest + count_y < self.height() and + xdest + count_x < self.width() and + y + count_y < src.height() and + x + count_x < src.width()): + transfer_pixel = src.get_pixel(x + count_x, y + count_y) + self.set_pixel(xdest + count_x, ydest + count_y, transfer_pixel) + + def crop(self, x, y, w, h): + res = Image(w, h) + res.blit(self, x, y, w, h) + return res + + def shift_vertical(self,n): + + res = Image(self.width(), self.height()) + if n > 0: + # up + res.blit(self, 0, n, self.width(), self.height()-n, 0, 0) + else: + # down + res.blit(self, 0, 0, self.width(), self.height()-abs(n), 0, abs(n)) + + return res + + + def shift_horizontal(self,n): + res = Image(self.width(), self.height()) + if n > 0: + # right + res.blit(self, 0, 0, self.width()-n, self.height(), n, 0) + else: + # left + res.blit(self, n, 0, self.width()-n, self.height(), 0, 0) + + return res + + + def shift_up(self,n): + return self.shift_vertical(n) + + def shift_down(self,n): + return self.shift_vertical(n*-1) + + def shift_right(self,n): + return self.shift_horizontal(n) + + def shift_left(self,n): + return self.shift_horizontal(n*-1) + + + def row_to_str(self, y): + new_str = "" + for x in range(0,self.width()): + new_str = new_str + str(self.get_pixel(x,y)) - def getvalue(self): - self._BOAT = Image(CONSTANTS.BOAT) - return self._BOAT + new_str = new_str + ":" - BOAT = property(getvalue) + return new_str \ No newline at end of file diff --git a/src/microbit/test/__init__.py b/src/microbit/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py new file mode 100644 index 000000000..582347bf0 --- /dev/null +++ b/src/microbit/test/test_display.py @@ -0,0 +1,76 @@ +import pytest + +from .. import constants as CONSTANTS +from ..display import Display +from ..image import Image + + +class TestDisplay(object): + def setup_method(self): + self.display = Display() + + @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) + def test_get_pixel(self, x, y, brightness): + self.display._Display__LEDs[y][x] = brightness + assert brightness == self.display.get_pixel(x, y) + + @pytest.mark.parametrize("x, y", [(5, 0), (0, -1), (0, 5)]) + def test_get_pixel_error(self, x, y): + with pytest.raises(ValueError, match=CONSTANTS.INDEX_ERR): + self.display.get_pixel(x, y) + + @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) + def test_set_pixel(self, x, y, brightness): + self.display.set_pixel(x, y, brightness) + assert brightness == self.display._Display__LEDs[y][x] + + @pytest.mark.parametrize( + "x, y, brightness, err_msg", + [ + (5, 0, 0, CONSTANTS.INDEX_ERR), + (0, -1, 0, CONSTANTS.INDEX_ERR), + (0, 0, -1, CONSTANTS.BRIGHTNESS_ERR), + ], + ) + def test_set_pixel_error(self, x, y, brightness, err_msg): + with pytest.raises(ValueError, match=err_msg): + self.display.set_pixel(x, y, brightness) + + def test_clear(self): + self.display._Display__LEDs[0][0] = 7 + self.display._Display__LEDs[3][4] = 6 + self.display._Display__LEDs[4][4] = 9 + assert not self.__is_clear() + self.display.clear() + assert self.__is_clear() + + def test_on(self): + self.display._Display__on = False + self.display.on() + assert self.display._Display__on + + def test_off(self): + self.display._Display__on = True + self.display.off() + assert False == self.display._Display__on + + @pytest.mark.parametrize("on", [True, False]) + def test_is_on(self, on): + self.display._Display__on = on + assert on == self.display.is_on() + + # Helpers + def __is_clear(self): + for y in range(CONSTANTS.LED_WIDTH): + for x in range(CONSTANTS.LED_HEIGHT): + if 0 != self.display._Display__LEDs[y][x]: + return False + return True + + def test_use_me(self): + img = Image(5, 5) + img.set_pixel(0, 0, 8) + img.set_pixel(0, 1, 9) + img.set_pixel(0, 2, 7) + img.set_pixel(2, 2, 6) + self.display.show(img) From cc0ac13660bff7e1556fa311db36bfe3060e1303 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 28 Jan 2020 17:41:44 -0800 Subject: [PATCH 09/38] update dusplay --- src/microbit/display.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/microbit/display.py b/src/microbit/display.py index ec45ee2f1..85bfd437e 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -23,7 +23,6 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): if value.height() <= CONSTANTS.LED_HEIGHT else CONSTANTS.LED_HEIGHT ) - self.__LEDs = value.LED.copy() self.__print() elif isinstance(value, str): pass From c761e8b209c3f6a6626c4a91c365d0c1028e72e9 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 29 Jan 2020 10:29:49 -0800 Subject: [PATCH 10/38] integrated image into display class --- src/microbit/constants.py | 1 - src/microbit/display.py | 17 +++-------------- src/microbit/image.py | 34 +++++++++++++++++++++------------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/microbit/constants.py b/src/microbit/constants.py index f3a1f20bb..ba0bd789f 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -1,5 +1,4 @@ INDEX_ERR = "index out of bounds" - BRIGHTNESS_ERR = "brightness out of bounds" LED_WIDTH = 5 diff --git a/src/microbit/display.py b/src/microbit/display.py index 85bfd437e..250e4de50 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -5,7 +5,7 @@ class Display: def __init__(self): # State in the Python process - self.__LEDs = [[0] * 5] * 5 + self.__image = Image() self.__on = True def scroll(self, message): @@ -32,18 +32,10 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): pass def get_pixel(self, x, y): - if self.__valid_pos(x, y): - return self.__LEDs[y][x] - else: - raise ValueError(CONSTANTS.INDEX_ERR) + return self.__image.get_pixel(x,y) def set_pixel(self, x, y, value): - if not self.__valid_pos(x, y): - raise ValueError(CONSTANTS.INDEX_ERR) - elif not self.__valid_brightness(value): - raise ValueError(CONSTANTS.BRIGHTNESS_ERR) - else: - self.__LEDs[y][x] = value + self.__image.set_pixel(x, y, value) def clear(self): for y in range(CONSTANTS.LED_WIDTH): @@ -66,9 +58,6 @@ def read_light_level(self): def __valid_pos(self, x, y): return 0 <= x and x <= 4 and 0 <= y and y <= 4 - def __valid_brightness(self, value): - return 0 <= value and value <= 9 - def __print(self): for i in range(5): print(self.__LEDs[i]) diff --git a/src/microbit/image.py b/src/microbit/image.py index dd99f2198..cc906ace3 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -24,7 +24,7 @@ def __init__(self, *args, **kwargs): if width < 0 or height < 0: raise Exception - self.__LED = self.create_leds(width, height) + self.__LED = [[0] * width] * height def convert_to_array(self, pattern): arr = [] @@ -37,25 +37,24 @@ def convert_to_array(self, pattern): sub_arr.append(int(elem)) return arr - def create_leds(self, w, h): - arr = [] - for _ in range(0, h): - sub_arr = [] - for _ in range(0, w): - sub_arr.append(0) - - arr.append(sub_arr) - - return arr def set_pixel(self, x, y, value): try: - self.__LED[y][x] = value + if not self.__valid_pos(x, y): + raise ValueError(CONSTANTS.INDEX_ERR) + elif not self.__valid_brightness(value): + raise ValueError(CONSTANTS.BRIGHTNESS_ERR) + else: + self.__LED[y][x] = value except TypeError: print(CONSTANTS.COPY_ERR_MESSAGE) def get_pixel(self, x, y): - return self.__LED[y][x] + if self.__valid_pos(x,y): + return self.__LED[y][x] + else: + raise ValueError(CONSTANTS.INDEX_ERR) + def copy(self): return Image(self.__LED) @@ -96,6 +95,10 @@ def crop(self, x, y, w, h): res.blit(self, x, y, w, h) return res + + def __valid_pos(self, x, y): + return 0 <= x and x < self.width() and 0 <= y and y < self.height() + def shift_vertical(self, n): res = Image(self.width(), self.height()) @@ -174,3 +177,8 @@ def __mul__(self, other): res.set_pixel(x, y, self.limit_result(9, product)) return res + + def __valid_brightness(self, value): + return 0 <= value and value <= 9 + + \ No newline at end of file From 4cbda2bded2becb9b58de91d36a19034c40b9760 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 29 Jan 2020 11:01:49 -0800 Subject: [PATCH 11/38] fixed LED array reference issue --- src/microbit/code_processing_shim.py | 18 ++- src/microbit/constants.py | 3 +- src/microbit/image.py | 184 +++++++++++++-------------- 3 files changed, 106 insertions(+), 99 deletions(-) diff --git a/src/microbit/code_processing_shim.py b/src/microbit/code_processing_shim.py index 231f5e9b3..8479d5a18 100644 --- a/src/microbit/code_processing_shim.py +++ b/src/microbit/code_processing_shim.py @@ -17,7 +17,7 @@ def repr(image): ret_str = "Image(\'" for index_y in range(0,image.height()): - ret_str += image.row_to_str(index_y) + ret_str += row_to_str(image, index_y) ret_str = ret_str + "\')" @@ -28,11 +28,23 @@ def str(image): if type(image) is Image: ret_str = "Image(\'\n" for index_y in range(0,image.height()): - ret_str += "\t" + image.row_to_str(index_y) + "\n" + ret_str += "\t" + row_to_str(image,index_y) + "\n" ret_str = ret_str + "\')" return ret_str else: # if not image, call regular str class - return image.__str__() \ No newline at end of file + return image.__str__() + + + +# method to help with string formation +def row_to_str(image, y): + new_str = "" + for x in range(0, image.width()): + new_str = new_str + str(image.get_pixel(x, y)) + + new_str = new_str + ":" + + return new_str \ No newline at end of file diff --git a/src/microbit/constants.py b/src/microbit/constants.py index ba0bd789f..61705fbef 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -1,6 +1,7 @@ INDEX_ERR = "index out of bounds" BRIGHTNESS_ERR = "brightness out of bounds" - +SAME_SIZE_ERR = "images must be the same size" +UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" LED_WIDTH = 5 LED_HEIGHT = 5 diff --git a/src/microbit/image.py b/src/microbit/image.py index cc906ace3..3788ecc5b 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -5,15 +5,12 @@ class Image: def __init__(self, *args, **kwargs): - print("args") - print(args) - # State in the Python process if len(args) == 0: self.__LED = CONSTANTS.BLANK elif len(args) == 1: pattern = args[0] if type(pattern) is str: - self.__LED = self.convert_to_array(pattern) + self.__LED = self.__string_to_array(pattern) else: self.__LED = pattern else: @@ -22,21 +19,20 @@ def __init__(self, *args, **kwargs): height = args[1] if width < 0 or height < 0: - raise Exception + # not in original, but ideally, + # image should fail non-silently + raise ValueError(CONSTANTS.INDEX_ERR) - self.__LED = [[0] * width] * height + self.__LED = self.__create_leds(width,height) - def convert_to_array(self, pattern): - arr = [] - sub_arr = [] - for elem in pattern: - if elem == ":": - arr.append(sub_arr) - sub_arr = [] - else: - sub_arr.append(int(elem)) - return arr + def width(self): + if len(self.__LED): + return len(self.__LED[0]) + else: + return 0 + def height(self): + return len(self.__LED) def set_pixel(self, x, y, value): try: @@ -55,6 +51,22 @@ def get_pixel(self, x, y): else: raise ValueError(CONSTANTS.INDEX_ERR) + def shift_up(self, n): + return self.__shift_vertical(n) + + def shift_down(self, n): + return self.__shift_vertical(n * -1) + + def shift_right(self, n): + return self.__shift_horizontal(n) + + def shift_left(self, n): + return self.__shift_horizontal(n * -1) + + def crop(self, x, y, w, h): + res = Image(w, h) + res.blit(self, x, y, w, h) + return res def copy(self): return Image(self.__LED) @@ -69,100 +81,29 @@ def fill(self, value): for x in range(0, self.width()): self.set_pixel(x, y, value) - def width(self): - if len(self.__LED): - return len(self.__LED[0]) - else: - return 0 - - def height(self): - return len(self.__LED) def blit(self, src, x, y, w, h, xdest=0, ydest=0): for count_y in range(0, h): for count_x in range(0, w): - if ( - ydest + count_y < self.height() - and xdest + count_x < self.width() - and y + count_y < src.height() - and x + count_x < src.width() - ): + if (self.__valid_pos(xdest + count_x, ydest + count_y) and + src.__valid_pos(x + count_x, y + count_y)): transfer_pixel = src.get_pixel(x + count_x, y + count_y) self.set_pixel(xdest + count_x, ydest + count_y, transfer_pixel) - def crop(self, x, y, w, h): - res = Image(w, h) - res.blit(self, x, y, w, h) - return res - - - def __valid_pos(self, x, y): - return 0 <= x and x < self.width() and 0 <= y and y < self.height() - - def shift_vertical(self, n): - - res = Image(self.width(), self.height()) - if n > 0: - # up - res.blit(self, 0, n, self.width(), self.height() - n, 0, 0) - else: - # down - res.blit(self, 0, 0, self.width(), self.height() - abs(n), 0, abs(n)) - - return res - - def shift_horizontal(self, n): - res = Image(self.width(), self.height()) - if n > 0: - # right - res.blit(self, 0, 0, self.width() - n, self.height(), n, 0) - else: - # left - res.blit(self, n, 0, self.width() - n, self.height(), 0, 0) - - return res - - def shift_up(self, n): - return self.shift_vertical(n) - - def shift_down(self, n): - return self.shift_vertical(n * -1) - - def shift_right(self, n): - return self.shift_horizontal(n) - - def shift_left(self, n): - return self.shift_horizontal(n * -1) - - def row_to_str(self, y): - new_str = "" - for x in range(0, self.width()): - new_str = new_str + str(self.get_pixel(x, y)) - - new_str = new_str + ":" - - return new_str - - def limit_result(self, limit, result): - if result > limit: - return limit - else: - return result - def __add__(self, other): if not (type(other) is Image): raise TypeError( - f"unsupported types for __add__: '{type(self)}', '{type(other)}'" + CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(self)}', '{type(other)}'" ) elif not (other.height() == self.height() and other.width() == self.width()): - raise ValueError("images must be the same size") + raise ValueError(CONSTANTS.SAME_SIZE_ERR) else: res = Image(self.width(), self.height()) - + for y in range(0, self.height()): for x in range(0, self.width()): sum = other.get_pixel(x, y) + self.get_pixel(x, y) - display_result = self.limit_result(9, sum) + display_result = self.__limit_result(9, sum) res.set_pixel(x, y, display_result) return res @@ -174,11 +115,64 @@ def __mul__(self, other): for y in range(0, self.height()): for x in range(0, self.width()): product = self.get_pixel(x, y) * float_val - res.set_pixel(x, y, self.limit_result(9, product)) + res.set_pixel(x, y, self.__limit_result(9, product)) return res + # helpers! + + def __create_leds(self, w, h): + arr = [] + for _ in range(0,h): + sub_arr = [] + for _ in range(0,w): + sub_arr.append(0) + arr.append(sub_arr) + return arr + + def __string_to_array(self, pattern): + arr = [] + sub_arr = [] + for elem in pattern: + if elem == ":": + arr.append(sub_arr) + sub_arr = [] + else: + sub_arr.append(int(elem)) + return arr + + def __limit_result(self, limit, result): + if result > limit: + return limit + else: + return result + def __valid_brightness(self, value): return 0 <= value and value <= 9 - \ No newline at end of file + + def __valid_pos(self, x, y): + return 0 <= x and x < self.width() and 0 <= y and y < self.height() + + def __shift_vertical(self, n): + + res = Image(self.width(), self.height()) + if n > 0: + # up + res.blit(self, 0, n, self.width(), self.height() - n, 0, 0) + else: + # down + res.blit(self, 0, 0, self.width(), self.height() - abs(n), 0, abs(n)) + + return res + + def __shift_horizontal(self, n): + res = Image(self.width(), self.height()) + if n > 0: + # right + res.blit(self, 0, 0, self.width() - n, self.height(), n, 0) + else: + # left + res.blit(self, n, 0, self.width() - n, self.height(), 0, 0) + + return res \ No newline at end of file From 355704735d78d8b1587f7d370ab0c46c65d97be8 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Wed, 29 Jan 2020 11:02:50 -0800 Subject: [PATCH 12/38] changes to display --- src/microbit/display.py | 16 ++++++---------- src/microbit/image.py | 8 +------- src/microbit/test/test_display.py | 30 +++++++++++++++++------------- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/microbit/display.py b/src/microbit/display.py index 250e4de50..7b41a9bf6 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -1,5 +1,6 @@ from . import constants as CONSTANTS from .image import Image +from . import code_processing_shim class Display: @@ -23,7 +24,7 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): if value.height() <= CONSTANTS.LED_HEIGHT else CONSTANTS.LED_HEIGHT ) - self.__print() + self.__image = value elif isinstance(value, str): pass elif isinstance(value, float): @@ -32,15 +33,13 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): pass def get_pixel(self, x, y): - return self.__image.get_pixel(x,y) + return self.__image.get_pixel(x, y) def set_pixel(self, x, y, value): self.__image.set_pixel(x, y, value) def clear(self): - for y in range(CONSTANTS.LED_WIDTH): - for x in range(CONSTANTS.LED_HEIGHT): - self.__LEDs[y][x] = 0 + self.__image = Image() def on(self): self.__on = True @@ -54,10 +53,7 @@ def is_on(self): def read_light_level(self): raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) - # Helpers - def __valid_pos(self, x, y): - return 0 <= x and x <= 4 and 0 <= y and y <= 4 - def __print(self): + print("") for i in range(5): - print(self.__LEDs[i]) + print(self._Display__image[i]) diff --git a/src/microbit/image.py b/src/microbit/image.py index cc906ace3..c5464947e 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -5,8 +5,6 @@ class Image: def __init__(self, *args, **kwargs): - print("args") - print(args) # State in the Python process if len(args) == 0: self.__LED = CONSTANTS.BLANK @@ -37,7 +35,6 @@ def convert_to_array(self, pattern): sub_arr.append(int(elem)) return arr - def set_pixel(self, x, y, value): try: if not self.__valid_pos(x, y): @@ -50,12 +47,11 @@ def set_pixel(self, x, y, value): print(CONSTANTS.COPY_ERR_MESSAGE) def get_pixel(self, x, y): - if self.__valid_pos(x,y): + if self.__valid_pos(x, y): return self.__LED[y][x] else: raise ValueError(CONSTANTS.INDEX_ERR) - def copy(self): return Image(self.__LED) @@ -95,7 +91,6 @@ def crop(self, x, y, w, h): res.blit(self, x, y, w, h) return res - def __valid_pos(self, x, y): return 0 <= x and x < self.width() and 0 <= y and y < self.height() @@ -181,4 +176,3 @@ def __mul__(self, other): def __valid_brightness(self, value): return 0 <= value and value <= 9 - \ No newline at end of file diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 582347bf0..b6f8b8d4f 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -3,6 +3,7 @@ from .. import constants as CONSTANTS from ..display import Display from ..image import Image +from .. import code_processing_shim class TestDisplay(object): @@ -11,7 +12,7 @@ def setup_method(self): @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) def test_get_pixel(self, x, y, brightness): - self.display._Display__LEDs[y][x] = brightness + self.display._Display__image._Image__LED[y][x] = brightness assert brightness == self.display.get_pixel(x, y) @pytest.mark.parametrize("x, y", [(5, 0), (0, -1), (0, 5)]) @@ -22,7 +23,7 @@ def test_get_pixel_error(self, x, y): @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) def test_set_pixel(self, x, y, brightness): self.display.set_pixel(x, y, brightness) - assert brightness == self.display._Display__LEDs[y][x] + assert brightness == self.display._Display__image._Image__LED[y][x] @pytest.mark.parametrize( "x, y, brightness, err_msg", @@ -37,9 +38,9 @@ def test_set_pixel_error(self, x, y, brightness, err_msg): self.display.set_pixel(x, y, brightness) def test_clear(self): - self.display._Display__LEDs[0][0] = 7 - self.display._Display__LEDs[3][4] = 6 - self.display._Display__LEDs[4][4] = 9 + self.display._Display__image._Image__LED[2][3] = 7 + self.display._Display__image._Image__LED[3][4] = 6 + self.display._Display__image._Image__LED[4][4] = 9 assert not self.__is_clear() self.display.clear() assert self.__is_clear() @@ -59,18 +60,21 @@ def test_is_on(self, on): self.display._Display__on = on assert on == self.display.is_on() + def test_show_one_image(self): + img = Image(CONSTANTS.BOAT) + img.set_pixel(0, 0, 8) + img.set_pixel(0, 1, 9) + img.set_pixel(0, 2, 7) + img.set_pixel(2, 2, 6) + self.display.show(img) + assert img == self.display._Display__image + # Helpers def __is_clear(self): for y in range(CONSTANTS.LED_WIDTH): for x in range(CONSTANTS.LED_HEIGHT): - if 0 != self.display._Display__LEDs[y][x]: + if 0 != self.display._Display__image._Image__LED[y][x]: + print(f"Not clear at x: {x}, y: {y}") return False return True - def test_use_me(self): - img = Image(5, 5) - img.set_pixel(0, 0, 8) - img.set_pixel(0, 1, 9) - img.set_pixel(0, 2, 7) - img.set_pixel(2, 2, 6) - self.display.show(img) From ecd96e0cc476968d038b51ff207e14e1c7966c00 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 29 Jan 2020 14:55:46 -0800 Subject: [PATCH 13/38] added bytearray compatability --- src/microbit/constants.py | 1 + src/microbit/image.py | 36 ++++++++- src/microbit/test/test_display.py | 126 +++++++++++++++--------------- src/microbit/test/test_image.py | 94 ++++++++++++++++++++++ 4 files changed, 191 insertions(+), 66 deletions(-) create mode 100644 src/microbit/test/test_image.py diff --git a/src/microbit/constants.py b/src/microbit/constants.py index 61705fbef..9492554cc 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -2,6 +2,7 @@ BRIGHTNESS_ERR = "brightness out of bounds" SAME_SIZE_ERR = "images must be the same size" UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" +INCORR_IMAGE_SIZE = "image data is incorrect size" LED_WIDTH = 5 LED_HEIGHT = 5 diff --git a/src/microbit/image.py b/src/microbit/image.py index 3788ecc5b..f91b5e40b 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -1,12 +1,12 @@ from . import microbit_model from . import constants as CONSTANTS from . import display - +import copy class Image: def __init__(self, *args, **kwargs): if len(args) == 0: - self.__LED = CONSTANTS.BLANK + self.__LED = copy.deepcopy(CONSTANTS.BLANK) elif len(args) == 1: pattern = args[0] if type(pattern) is str: @@ -22,8 +22,12 @@ def __init__(self, *args, **kwargs): # not in original, but ideally, # image should fail non-silently raise ValueError(CONSTANTS.INDEX_ERR) + if (len(args) == 3): + byte_arr = args[2] + self.__LED = self.__bytes_to_array(width,height,byte_arr) + else: + self.__LED = self.__create_leds(width,height) - self.__LED = self.__create_leds(width,height) def width(self): if len(self.__LED): @@ -83,6 +87,10 @@ def fill(self, value): def blit(self, src, x, y, w, h, xdest=0, ydest=0): + + if (not self.__valid_pos(x,y) or not src.__valid_pos(xdest, ydest)): + raise ValueError(CONSTANTS.INDEX_ERR) + for count_y in range(0, h): for count_x in range(0, w): if (self.__valid_pos(xdest + count_x, ydest + count_y) and @@ -130,6 +138,28 @@ def __create_leds(self, w, h): arr.append(sub_arr) return arr + + def __bytes_to_array(self, height, width, byte_arr): + bytes_translated = bytes(byte_arr) + + if (not (len(bytes_translated)) == height*width): + raise ValueError(CONSTANTS.INCORR_IMAGE_SIZE) + + arr = [] + sub_arr = [] + + for index,elem in enumerate(bytes_translated): + if index % width == 0 and not index is 0: + arr.append(sub_arr) + sub_arr = [] + + sub_arr.append(elem) + + arr.append(sub_arr) + return arr + + + def __string_to_array(self, pattern): arr = [] sub_arr = [] diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 582347bf0..3f3a27119 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -1,76 +1,76 @@ -import pytest +# import pytest -from .. import constants as CONSTANTS -from ..display import Display -from ..image import Image +# from .. import constants as CONSTANTS +# from ..display import Display +# from ..image import Image -class TestDisplay(object): - def setup_method(self): - self.display = Display() +# class TestDisplay(object): +# def setup_method(self): +# self.display = Display() - @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) - def test_get_pixel(self, x, y, brightness): - self.display._Display__LEDs[y][x] = brightness - assert brightness == self.display.get_pixel(x, y) +# @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) +# def test_get_pixel(self, x, y, brightness): +# self.display._Display__LEDs[y][x] = brightness +# assert brightness == self.display.get_pixel(x, y) - @pytest.mark.parametrize("x, y", [(5, 0), (0, -1), (0, 5)]) - def test_get_pixel_error(self, x, y): - with pytest.raises(ValueError, match=CONSTANTS.INDEX_ERR): - self.display.get_pixel(x, y) +# @pytest.mark.parametrize("x, y", [(5, 0), (0, -1), (0, 5)]) +# def test_get_pixel_error(self, x, y): +# with pytest.raises(ValueError, match=CONSTANTS.INDEX_ERR): +# self.display.get_pixel(x, y) - @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) - def test_set_pixel(self, x, y, brightness): - self.display.set_pixel(x, y, brightness) - assert brightness == self.display._Display__LEDs[y][x] +# @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) +# def test_set_pixel(self, x, y, brightness): +# self.display.set_pixel(x, y, brightness) +# assert brightness == self.display._Display__LEDs[y][x] - @pytest.mark.parametrize( - "x, y, brightness, err_msg", - [ - (5, 0, 0, CONSTANTS.INDEX_ERR), - (0, -1, 0, CONSTANTS.INDEX_ERR), - (0, 0, -1, CONSTANTS.BRIGHTNESS_ERR), - ], - ) - def test_set_pixel_error(self, x, y, brightness, err_msg): - with pytest.raises(ValueError, match=err_msg): - self.display.set_pixel(x, y, brightness) +# @pytest.mark.parametrize( +# "x, y, brightness, err_msg", +# [ +# (5, 0, 0, CONSTANTS.INDEX_ERR), +# (0, -1, 0, CONSTANTS.INDEX_ERR), +# (0, 0, -1, CONSTANTS.BRIGHTNESS_ERR), +# ], +# ) +# def test_set_pixel_error(self, x, y, brightness, err_msg): +# with pytest.raises(ValueError, match=err_msg): +# self.display.set_pixel(x, y, brightness) - def test_clear(self): - self.display._Display__LEDs[0][0] = 7 - self.display._Display__LEDs[3][4] = 6 - self.display._Display__LEDs[4][4] = 9 - assert not self.__is_clear() - self.display.clear() - assert self.__is_clear() +# def test_clear(self): +# self.display._Display__LEDs[0][0] = 7 +# self.display._Display__LEDs[3][4] = 6 +# self.display._Display__LEDs[4][4] = 9 +# assert not self.__is_clear() +# self.display.clear() +# assert self.__is_clear() - def test_on(self): - self.display._Display__on = False - self.display.on() - assert self.display._Display__on +# def test_on(self): +# self.display._Display__on = False +# self.display.on() +# assert self.display._Display__on - def test_off(self): - self.display._Display__on = True - self.display.off() - assert False == self.display._Display__on +# def test_off(self): +# self.display._Display__on = True +# self.display.off() +# assert False == self.display._Display__on - @pytest.mark.parametrize("on", [True, False]) - def test_is_on(self, on): - self.display._Display__on = on - assert on == self.display.is_on() +# @pytest.mark.parametrize("on", [True, False]) +# def test_is_on(self, on): +# self.display._Display__on = on +# assert on == self.display.is_on() - # Helpers - def __is_clear(self): - for y in range(CONSTANTS.LED_WIDTH): - for x in range(CONSTANTS.LED_HEIGHT): - if 0 != self.display._Display__LEDs[y][x]: - return False - return True +# # Helpers +# def __is_clear(self): +# for y in range(CONSTANTS.LED_WIDTH): +# for x in range(CONSTANTS.LED_HEIGHT): +# if 0 != self.display._Display__LEDs[y][x]: +# return False +# return True - def test_use_me(self): - img = Image(5, 5) - img.set_pixel(0, 0, 8) - img.set_pixel(0, 1, 9) - img.set_pixel(0, 2, 7) - img.set_pixel(2, 2, 6) - self.display.show(img) +# def test_use_me(self): +# img = Image(5, 5) +# img.set_pixel(0, 0, 8) +# img.set_pixel(0, 1, 9) +# img.set_pixel(0, 2, 7) +# img.set_pixel(2, 2, 6) +# self.display.show(img) diff --git a/src/microbit/test/test_image.py b/src/microbit/test/test_image.py new file mode 100644 index 000000000..3b0fac63a --- /dev/null +++ b/src/microbit/test/test_image.py @@ -0,0 +1,94 @@ +import pytest + +from .. import constants as CONSTANTS +from .. import code_processing_shim +from ..display import Display +from ..image import Image + + + + +class TestImage(object): + def setup_method(self): + self.image = Image() + self.image_heart = Image(CONSTANTS.HEART) + # self.image_3x3 = + # self.image_empty = Image("") + + # GET PIXEL + @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) + def test_get_pixel(self, x, y, brightness): + self.image._Image__LED[y][x] = brightness + assert brightness == self.image.get_pixel(x, y) + + # SET PIXEL + @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) + def test_set_pixel(self, x, y, brightness): + self.image.set_pixel(x, y, brightness) + assert brightness == self.image._Image__LED[y][x] + + + # GET PIXEL - INDEX ERROR + @pytest.mark.parametrize("x, y", [(5, 0), (0, -1), (0, 5)]) + def test_get_pixel_error(self, x, y): + with pytest.raises(ValueError, match=CONSTANTS.INDEX_ERR): + self.image.get_pixel(x, y) + + # SET PIXEL - VARIOUS ERRORS + @pytest.mark.parametrize( + "x, y, brightness, err_msg", + [ + (5, 0, 0, CONSTANTS.INDEX_ERR), + (0, -1, 0, CONSTANTS.INDEX_ERR), + (0, 0, -1, CONSTANTS.BRIGHTNESS_ERR), + ], + ) + def test_set_pixel_error(self, x, y, brightness, err_msg): + with pytest.raises(ValueError, match=err_msg): + self.image.set_pixel(x, y, brightness) + + # WIDTH & HEIGHT + @pytest.mark.parametrize("image", [(Image()), (Image(3,3)), (Image(""))]) + def test_width_and_height(self, image): + assert image.height() == len(image._Image__LED) + if len(image._Image__LED) == 0: + assert image.width() == 0 + else: + assert image.width() == len(image._Image__LED[0]) + + assert image.height() == image.width() + + # BLIT + # @pytest.mark.parametrize("x, y, w, h, x_dest, y_dest", [(0,0,3,3,4,3),(1,1,2,4,0,1),(1,3,1,2,0,2)]) + # def test_blit(self, x, y, w, h, x_dest, y_dest): + # x_offset = x_dest-x + # y_offset = y_dest-y + # result = Image() + + # print("here") + # result.blit(self.image_heart, x, y, w, h, x_dest, y_dest) + # self.__check_blit(result, self.image_heart, x, y, w, h, x_offset, y_offset) + + # # helper! :D + # def __check_value(self,src,x,y,value): + # if src._Image__valid_pos(x,y): + # assert(src._Image__LED[y][x] == value) + # def __check_blit(self,target, src,x,y, w, h, x_offset,y_offset): + # for index_y, val_y in enumerate(src._Image__LED[y:]): + # if index_y >= h: + # break + # for index_x,val_x in enumerate(val_y[x:]): + # print(f"{index_x} {val_x}") + # if index_x >= w: + # break + # if (src._Image__valid_pos(index_x+x_offset,index_y+y_offset)): + # try: + # self.__check_value(target,index_x+x_offset, index_y+y_offset, val_x) + # except AssertionError as e: + # print("uuuwu") + # print(f"{index_x} {index_y} {w} {h} {x_offset} {y_offset}") + # print(code_processing_shim.str(src)) + # print(code_processing_shim.str(target)) + # print(f"{index_x+x_offset} {index_y+y_offset} {val_x}") + # print(e) + From 9652a23f746c2a742e363cedfd6fd49cd15dd218 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Wed, 29 Jan 2020 15:10:04 -0800 Subject: [PATCH 14/38] Before merge --- src/microbit/button.py | 26 +++++++++++++++ src/microbit/constants.py | 8 +++-- src/microbit/display.py | 53 ++++++++++++++++++++----------- src/microbit/image.py | 24 +++++++------- src/microbit/microbit_model.py | 11 +++++-- src/microbit/test/test_button.py | 41 ++++++++++++++++++++++++ src/microbit/test/test_display.py | 38 ++++++++++++++++++---- 7 files changed, 160 insertions(+), 41 deletions(-) create mode 100644 src/microbit/button.py create mode 100644 src/microbit/test/test_button.py diff --git a/src/microbit/button.py b/src/microbit/button.py new file mode 100644 index 000000000..a28d13ed9 --- /dev/null +++ b/src/microbit/button.py @@ -0,0 +1,26 @@ +class Button: + 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.__presses += 1 + + def __release(self): + self.__pressed = False + self.__prev_pressed = True diff --git a/src/microbit/constants.py b/src/microbit/constants.py index 61705fbef..4af98d262 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -4,9 +4,10 @@ UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" LED_WIDTH = 5 LED_HEIGHT = 5 - +MAX_BRIGHTNESS = 9 NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" - +ASCII_START = 32 +ASCII_END = 126 BOAT = ( [0, 5, 0, 5, 0], [0, 5, 0, 5, 0], @@ -35,3 +36,6 @@ COPY_ERR_MESSAGE = "please copy() first" LED_MAX = 5 + +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" + diff --git a/src/microbit/display.py b/src/microbit/display.py index 7b41a9bf6..815b83b18 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -1,3 +1,5 @@ +import time + from . import constants as CONSTANTS from .image import Image from . import code_processing_shim @@ -13,24 +15,39 @@ def scroll(self, message): raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def show(self, value, delay=400, wait=True, loop=False, clear=False): - if isinstance(value, Image): - width = ( - value.width() - if value.width() <= CONSTANTS.LED_WIDTH - else CONSTANTS.LED_WIDTH - ) - height = ( - value.height() - if value.height() <= CONSTANTS.LED_HEIGHT - else CONSTANTS.LED_HEIGHT - ) - self.__image = value - elif isinstance(value, str): - pass - elif isinstance(value, float): - pass - elif isinstance(value, int): + # wait has no effect + while True: + # Need to check if iterable + # if iterable: + # for c in value: + # if isinstance(c, image): + # self.__image = value.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT) + # elif isinstance(c, str) and len(c) == 1: + # show letter + # else: + # break + + # if isinstance(value, Image): + # self.__image = value.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT) + # elif isinstance(value, str): + # chars = list(value) + # for c in chars: + # if c < CONSTANTS.ASCII_START or c > ASCII_END: + # c = "?" + # offset = (c - ASCII_START) * 5 + # representative_bytes = CONSTANTS.ALPHABET[offset : offset + 25] + # representative_image = Image(5, 5, representative_bytes) + # self.__image = representative_image + # time.sleep(delay / 1000) + # elif isinstance(value, float): + # pass + # elif isinstance(value, int): + # pass + # if not loop: + # break pass + if clear: + self.clear() def get_pixel(self, x, y): return self.__image.get_pixel(x, y) @@ -56,4 +73,4 @@ def read_light_level(self): def __print(self): print("") for i in range(5): - print(self._Display__image[i]) + print(self._Display__image._Image__LED[i]) diff --git a/src/microbit/image.py b/src/microbit/image.py index 3788ecc5b..f4116adcb 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -1,12 +1,13 @@ from . import microbit_model from . import constants as CONSTANTS from . import display +import copy class Image: def __init__(self, *args, **kwargs): if len(args) == 0: - self.__LED = CONSTANTS.BLANK + self.__LED = copy.deepcopy(CONSTANTS.BLANK) elif len(args) == 1: pattern = args[0] if type(pattern) is str: @@ -23,7 +24,7 @@ def __init__(self, *args, **kwargs): # image should fail non-silently raise ValueError(CONSTANTS.INDEX_ERR) - self.__LED = self.__create_leds(width,height) + self.__LED = self.__create_leds(width, height) def width(self): if len(self.__LED): @@ -46,7 +47,7 @@ def set_pixel(self, x, y, value): print(CONSTANTS.COPY_ERR_MESSAGE) def get_pixel(self, x, y): - if self.__valid_pos(x,y): + if self.__valid_pos(x, y): return self.__LED[y][x] else: raise ValueError(CONSTANTS.INDEX_ERR) @@ -81,12 +82,12 @@ def fill(self, value): for x in range(0, self.width()): self.set_pixel(x, y, value) - def blit(self, src, x, y, w, h, xdest=0, ydest=0): for count_y in range(0, h): for count_x in range(0, w): - if (self.__valid_pos(xdest + count_x, ydest + count_y) and - src.__valid_pos(x + count_x, y + count_y)): + if self.__valid_pos( + xdest + count_x, ydest + count_y + ) and src.__valid_pos(x + count_x, y + count_y): transfer_pixel = src.get_pixel(x + count_x, y + count_y) self.set_pixel(xdest + count_x, ydest + count_y, transfer_pixel) @@ -99,7 +100,7 @@ def __add__(self, other): raise ValueError(CONSTANTS.SAME_SIZE_ERR) else: res = Image(self.width(), self.height()) - + for y in range(0, self.height()): for x in range(0, self.width()): sum = other.get_pixel(x, y) + self.get_pixel(x, y) @@ -123,13 +124,13 @@ def __mul__(self, other): def __create_leds(self, w, h): arr = [] - for _ in range(0,h): + for _ in range(0, h): sub_arr = [] - for _ in range(0,w): + for _ in range(0, w): sub_arr.append(0) arr.append(sub_arr) return arr - + def __string_to_array(self, pattern): arr = [] sub_arr = [] @@ -150,7 +151,6 @@ def __limit_result(self, limit, result): def __valid_brightness(self, value): return 0 <= value and value <= 9 - def __valid_pos(self, x, y): return 0 <= x and x < self.width() and 0 <= y and y < self.height() @@ -175,4 +175,4 @@ def __shift_horizontal(self, n): # left res.blit(self, n, 0, self.width() - n, self.height(), 0, 0) - return res \ No newline at end of file + return res diff --git a/src/microbit/microbit_model.py b/src/microbit/microbit_model.py index 1d6ebec95..a8fa7815c 100644 --- a/src/microbit/microbit_model.py +++ b/src/microbit/microbit_model.py @@ -1,15 +1,20 @@ from .display import Display +from .button import Button + class MicrobitModel: def __init__(self): # State in the Python process self.display = Display() - self.__state = { } + self.button_a = Button() + self.button_b = Button() + self.__state = {} self.__debug_mode = False - self.__abs_path_to_code_file = '' + self.__abs_path_to_code_file = "" # SAMPLE FUNCTION def show_message(self, message): print("message!! " + message) -mb = MicrobitModel() \ No newline at end of file + +mb = MicrobitModel() diff --git a/src/microbit/test/test_button.py b/src/microbit/test/test_button.py new file mode 100644 index 000000000..acd7826d0 --- /dev/null +++ b/src/microbit/test/test_button.py @@ -0,0 +1,41 @@ +import pytest +from ..button import Button + + +class TestButton(object): + def setup_method(self): + self.button = Button() + + @pytest.mark.parametrize("pressed", [True, False]) + def test_is_pressed(self, pressed): + self.button._Button__pressed = pressed + assert pressed == self.button.is_pressed() + + @pytest.mark.parametrize("was_pressed", [True, False]) + def test_was_pressed(self, was_pressed): + self.button._Button__prev_pressed = was_pressed + assert was_pressed == self.button.was_pressed() + # Button resets prev pressed after was_pressed() is called + assert not self.button.was_pressed() + + @pytest.mark.parametrize("presses", [0, 2, 4]) + def test_get_presses(self, presses): + self.button._Button__presses = presses + assert presses == self.button.get_presses() + # Presses is reset to 0 after get_presses() is called + assert 0 == self.button.get_presses() + + def test_press_down(self): + self.button._Button__press_down() + assert self.button._Button__presses == 1 + assert self.button._Button__pressed + self.button._Button__press_down() + assert self.button._Button__presses == 2 + assert self.button._Button__pressed + + def test_release(self): + self.button._Button__pressed = True + self.button._Button__prev_pressed = False + self.button._Button__release() + assert not self.button._Button__pressed + assert self.button._Button__prev_pressed diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index b6f8b8d4f..71966dadc 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -61,20 +61,46 @@ def test_is_on(self, on): assert on == self.display.is_on() def test_show_one_image(self): - img = Image(CONSTANTS.BOAT) + img = Image() img.set_pixel(0, 0, 8) img.set_pixel(0, 1, 9) img.set_pixel(0, 2, 7) img.set_pixel(2, 2, 6) self.display.show(img) - assert img == self.display._Display__image + assert self.__same_image(img, self.display._Display__image) + + def test_show_different_size_image(self): + img = Image(3, 7) + img.set_pixel(1, 1, 9) + img.set_pixel(2, 6, 9) # Will not be on display + expected = Image(5, 5) + expected.set_pixel(1, 1, 9) + self.display.show(img) + assert self.__same_image(expected, self.display._Display__image) + + def test_show_smaller_image(self): + img = Image(2, 2) + img.set_pixel(1, 1, 9) + expected = Image(5, 5) + expected.set_pixel(1, 1, 9) + self.display.show(img) + assert self.__same_image(expected, self.display._Display__image) # Helpers def __is_clear(self): - for y in range(CONSTANTS.LED_WIDTH): - for x in range(CONSTANTS.LED_HEIGHT): - if 0 != self.display._Display__image._Image__LED[y][x]: - print(f"Not clear at x: {x}, y: {y}") + i = Image() + return self.__same_image(i, self.display._Display__image) + + def __same_image(self, 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 + def __print(self, img): + print("") + for i in range(5): + print(img._Image__LED[i]) From 20690b29b617553219fe433ab4ebdf52342618e5 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Wed, 29 Jan 2020 17:21:45 -0800 Subject: [PATCH 15/38] display show working, still need to add tests --- src/microbit/constants.py | 1 - src/microbit/display.py | 90 +++++++++++++++++++++---------- src/microbit/image.py | 2 +- src/microbit/test/test_display.py | 20 +++++++ 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/microbit/constants.py b/src/microbit/constants.py index 919582b9a..c1c1177a7 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -33,7 +33,6 @@ [0, 0, 0, 0, 0], ] - COPY_ERR_MESSAGE = "please copy() first" LED_MAX = 5 diff --git a/src/microbit/display.py b/src/microbit/display.py index 10e0e7d25..531722baa 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -17,35 +17,37 @@ def scroll(self, message): def show(self, value, delay=400, wait=True, loop=False, clear=False): # wait has no effect while True: - # Need to check if iterable - # if iterable: - # for c in value: - # if isinstance(c, image): - # self.__image = value.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT) - # elif isinstance(c, str) and len(c) == 1: - # show letter - # else: - # break - - # if isinstance(value, Image): - # self.__image = value.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT) - # elif isinstance(value, str): - # chars = list(value) - # for c in chars: - # if c < CONSTANTS.ASCII_START or c > ASCII_END: - # c = "?" - # offset = (c - ASCII_START) * 5 - # representative_bytes = CONSTANTS.ALPHABET[offset : offset + 25] - # representative_image = Image(5, 5, representative_bytes) - # self.__image = representative_image - # time.sleep(delay / 1000) - # elif isinstance(value, float): - # pass - # elif isinstance(value, int): - # pass - # if not loop: - # break - break + if isinstance(value, Image): + self.__image = value.crop( + 0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT + ) + elif isinstance(value, (str, int, float)): + if isinstance(value, str): + chars = list(value) + else: + chars = list(str(value)) + for c in chars: + self.__image = self.__get_image_from_char(c) + self.__print() + time.sleep(delay / 1000) + else: + # Check if iterable + try: + _ = iter(value) + for elem in value: + if isinstance(elem, Image): + self.__image = elem.crop( + 0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT + ) + elif isinstance(elem, str) and len(elem) == 1: + self.__image = self.__get_image_from_char(elem) + else: + break + self.__print() + except TypeError: + pass # Not iterable + if not loop: + break if clear: self.clear() @@ -70,7 +72,37 @@ def is_on(self): def read_light_level(self): raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + # Helpers + def __print(self): print("") for i in range(5): print(self._Display__image._Image__LED[i]) + + def __get_image_from_char(self, 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) * 5 + representative_bytes = CONSTANTS.ALPHABET[offset : offset + 5] + return Image(self.__convert_bytearray_to_image_array(representative_bytes)) + + def __convert_bytearray_to_image_array(self, 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 recursively + # If there is a 1 at x, then the pixel at column x is lit + for bit in b_as_bits[::-1]: + if len(sub_arr) < 5: + sub_arr.insert(0, int(bit) * CONSTANTS.MAX_BRIGHTNESS) + 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) + return arr diff --git a/src/microbit/image.py b/src/microbit/image.py index 59b071424..56f20d2b7 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -148,7 +148,7 @@ def __bytes_to_array(self, height, width, byte_arr): sub_arr = [] for index, elem in enumerate(bytes_translated): - if index % width == 0 and not index is 0: + if index % width == 0 and index != 0: arr.append(sub_arr) sub_arr = [] diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 71966dadc..34ec2f3dc 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -86,6 +86,19 @@ def test_show_smaller_image(self): self.display.show(img) assert self.__same_image(expected, self.display._Display__image) + def test_show_char(self): + expected = Image( + [ + [0, 9, 0, 0, 0], + [0, 9, 0, 0, 0], + [0, 9, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 9, 0, 0, 0], + ] + ) + self.display.show("!") + assert self.__same_image(expected, self.display._Display__image) + # Helpers def __is_clear(self): i = Image() @@ -104,3 +117,10 @@ def __print(self, img): print("") for i in range(5): print(img._Image__LED[i]) + + # def __convert_bytearray_to_image(self, byte_array): + # print(byte_array) + # arr = [] + # for b in byte_array: + # print(f"b: {b} type: {type(b)}") + # return Image(arr) From b6279891f3ab3b194cf8710cf1c5a262909f0057 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 30 Jan 2020 11:14:50 -0800 Subject: [PATCH 16/38] updated display tests and refactored some code --- src/microbit/constants.py | 39 ++++++------------- src/microbit/display.py | 29 +++++++------- src/microbit/image.py | 2 +- src/microbit/test/image_constants.py | 56 ++++++++++++++++++++++++++++ src/microbit/test/test_display.py | 50 ++++++++++++++++--------- 5 files changed, 117 insertions(+), 59 deletions(-) create mode 100644 src/microbit/test/image_constants.py diff --git a/src/microbit/constants.py b/src/microbit/constants.py index c1c1177a7..7cee0f6ad 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -1,29 +1,6 @@ -INDEX_ERR = "index out of bounds" -BRIGHTNESS_ERR = "brightness out of bounds" -SAME_SIZE_ERR = "images must be the same size" -UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" -INCORR_IMAGE_SIZE = "image data is incorrect size" LED_WIDTH = 5 LED_HEIGHT = 5 MAX_BRIGHTNESS = 9 -NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" -ASCII_START = 32 -ASCII_END = 126 -BOAT = ( - [0, 5, 0, 5, 0], - [0, 5, 0, 5, 0], - [0, 5, 0, 5, 0], - [9, 9, 9, 9, 9], - [0, 9, 9, 9, 0], -) - -HEART = [ - [0, 9, 0, 9, 0], - [9, 9, 9, 9, 9], - [9, 9, 9, 9, 9], - [0, 9, 9, 9, 0], - [0, 0, 9, 0, 0], -] BLANK = [ [0, 0, 0, 0, 0], @@ -33,9 +10,17 @@ [0, 0, 0, 0, 0], ] -COPY_ERR_MESSAGE = "please copy() first" - -LED_MAX = 5 - +# 5x5 Alphabet +# Taken from https://raw.githubusercontent.com/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" +ASCII_START = 32 +ASCII_END = 126 +# Errors +INDEX_ERR = "index out of bounds" +BRIGHTNESS_ERR = "brightness out of bounds" +SAME_SIZE_ERR = "images must be the same size" +UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" +INCORR_IMAGE_SIZE = "image data is incorrect size" +NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" +COPY_ERR_MESSAGE = "please copy() first" diff --git a/src/microbit/display.py b/src/microbit/display.py index 531722baa..673ca9bb1 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -34,18 +34,21 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): # Check if iterable try: _ = iter(value) - for elem in value: - if isinstance(elem, Image): - self.__image = elem.crop( - 0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT - ) - elif isinstance(elem, str) and len(elem) == 1: - self.__image = self.__get_image_from_char(elem) - else: - break - self.__print() - except TypeError: - pass # Not iterable + except TypeError as e: + raise e + + for elem in value: + if isinstance(elem, Image): + self.__image = elem.crop( + 0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT + ) + elif isinstance(elem, str) and len(elem) == 1: + self.__image = self.__get_image_from_char(elem) + # If elem is not char or image, break without iterating through rest of list + else: + break + time.sleep(delay / 1000) + self.__print() if not loop: break if clear: @@ -76,7 +79,7 @@ def read_light_level(self): def __print(self): print("") - for i in range(5): + for i in range(CONSTANTS.LED_HEIGHT): print(self._Display__image._Image__LED[i]) def __get_image_from_char(self, c): diff --git a/src/microbit/image.py b/src/microbit/image.py index 56f20d2b7..dc1e74a01 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -13,7 +13,7 @@ def __init__(self, *args, **kwargs): if type(pattern) is str: self.__LED = self.__string_to_array(pattern) else: - self.__LED = pattern + self.__LED = copy.deepcopy(pattern) else: width = args[0] diff --git a/src/microbit/test/image_constants.py b/src/microbit/test/image_constants.py new file mode 100644 index 000000000..5ff982275 --- /dev/null +++ b/src/microbit/test/image_constants.py @@ -0,0 +1,56 @@ +BOAT = [ + [0, 5, 0, 5, 0], + [0, 5, 0, 5, 0], + [0, 5, 0, 5, 0], + [9, 9, 9, 9, 9], + [0, 9, 9, 9, 0], +] + +HEART = [ + [0, 9, 0, 9, 0], + [9, 9, 9, 9, 9], + [9, 9, 9, 9, 9], + [0, 9, 9, 9, 0], + [0, 0, 9, 0, 0], +] + +BLANK = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], +] + +EXCLAMATION_MARK = [ + [0, 9, 0, 0, 0], + [0, 9, 0, 0, 0], + [0, 9, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 9, 0, 0, 0], +] + +A = [ + [0, 9, 9, 0, 0], + [9, 0, 0, 9, 0], + [9, 9, 9, 9, 0], + [9, 0, 0, 9, 0], + [9, 0, 0, 9, 0], +] + +SIX = [ + [0, 0, 0, 9, 0], + [0, 0, 9, 0, 0], + [0, 9, 9, 9, 0], + [9, 0, 0, 0, 9], + [0, 9, 9, 9, 0], +] + +QUESTION_MARK = [ + [0, 9, 9, 9, 0], + [9, 0, 0, 0, 9], + [0, 0, 9, 9, 0], + [0, 0, 0, 0, 0], + [0, 0, 9, 0, 0], +] + diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 34ec2f3dc..b670b0772 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -4,6 +4,7 @@ from ..display import Display from ..image import Image from .. import code_processing_shim +from . import image_constants as TEST_IMAGES class TestDisplay(object): @@ -86,19 +87,39 @@ def test_show_smaller_image(self): self.display.show(img) assert self.__same_image(expected, self.display._Display__image) - def test_show_char(self): - expected = Image( - [ - [0, 9, 0, 0, 0], - [0, 9, 0, 0, 0], - [0, 9, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 9, 0, 0, 0], - ] - ) - self.display.show("!") + @pytest.mark.parametrize( + "value, expected_arr", + [ + ("!", TEST_IMAGES.EXCLAMATION_MARK), + ("A", TEST_IMAGES.A), + (" ", TEST_IMAGES.BLANK), + (6, TEST_IMAGES.SIX), + ("\x7F", TEST_IMAGES.QUESTION_MARK), # Character is out of our ASCII range + ], + ) + def test_show_char(self, value, expected_arr): + expected = Image(expected_arr) + self.display.show(value) + assert self.__same_image(expected, self.display._Display__image) + + def test_show_char_with_clear(self): + expected = Image(TEST_IMAGES.BLANK) + value = TEST_IMAGES.QUESTION_MARK + self.display.show(value, clear=True) + assert self.__same_image(expected, self.display._Display__image) + + def test_show_iterable(self): + expected = Image(TEST_IMAGES.A) + value = [Image(TEST_IMAGES.EXCLAMATION_MARK), "A", "ab"] + self.display.show(value) + print("TEST IMAGE ACTUAL BELOW") + self.__print(self.display._Display__image) assert self.__same_image(expected, self.display._Display__image) + def test_show_non_iterable(self): + with pytest.raises(TypeError): + self.display.show(TestDisplay()) + # Helpers def __is_clear(self): i = Image() @@ -117,10 +138,3 @@ def __print(self, img): print("") for i in range(5): print(img._Image__LED[i]) - - # def __convert_bytearray_to_image(self, byte_array): - # print(byte_array) - # arr = [] - # for b in byte_array: - # print(f"b: {b} type: {type(b)}") - # return Image(arr) From 2a6553e9e43eea5ab7431173472c66fc886cbe38 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 30 Jan 2020 16:56:12 -0800 Subject: [PATCH 17/38] started scroll method for display --- src/microbit/display.py | 88 +++++++++++++++++++++++++++++-- src/microbit/image.py | 2 +- src/microbit/test/test_display.py | 5 +- 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/microbit/display.py b/src/microbit/display.py index 673ca9bb1..536af0482 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -11,8 +11,86 @@ def __init__(self): self.__image = Image() self.__on = True - def scroll(self, message): - raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): + # wait has no effect + # going to implement first with monospace = True. therefore every char is 5 wide + while True: + try: + value = str(value) + except TypeError as e: + raise e + letters = [] + for c in value: + letters.append(self.__get_image_from_char(c)) + appended_image = self.__create_scroll_image(letters, monospace) + for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): + self.__image.blit( + appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT + ) + self.__print() + time.sleep(delay / 1000) + if not loop: + break + + def __strip_image(self, image): + # Find column that contains first lit pixel. Call that column number: c1. + # Go reverse, and find number of columns seen until we see the last lit pixel. Call that number: c2. + return image.crop(c1, 0, image.width() - c1 - c2, image.height()) + + def __insert_blank_column(self, image): + for row in image._Image__LED: + row.append(0) + + def __create_scroll_image(self, images, monospace): + blank_5x5_image = Image(CONSTANTS.BLANK) + front_image = blank_5x5_image.crop( + 0, 0, CONSTANTS.LED_WIDTH - 1, CONSTANTS.LED_HEIGHT + ) + images.insert(0, front_image) + + scroll_image = self.__append_images(images) + end_image = Image() + # Insert columns of 0s until the ending is a 5x5 blank + end_image.blit( + scroll_image, + scroll_image.width() - CONSTANTS.LED_WIDTH, + 0, + CONSTANTS.LED_WIDTH, + CONSTANTS.LED_HEIGHT, + ) + while not self.__same_image(end_image, blank_5x5_image): + self.__insert_blank_column(scroll_image) + end_image.blit( + scroll_image, + scroll_image.width() - CONSTANTS.LED_WIDTH, + 0, + CONSTANTS.LED_WIDTH, + CONSTANTS.LED_HEIGHT, + ) + + return scroll_image + + def __same_image(self, 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 + + def __append_images(self, 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 def show(self, value, delay=400, wait=True, loop=False, clear=False): # wait has no effect @@ -28,7 +106,6 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): chars = list(str(value)) for c in chars: self.__image = self.__get_image_from_char(c) - self.__print() time.sleep(delay / 1000) else: # Check if iterable @@ -48,7 +125,6 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): else: break time.sleep(delay / 1000) - self.__print() if not loop: break if clear: @@ -98,7 +174,7 @@ def __convert_bytearray_to_image_array(self, byte_array): sub_arr = [] while len(sub_arr) < 5: # Iterate throught bits recursively - # If there is a 1 at x, then the pixel at column x is lit + # 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.MAX_BRIGHTNESS) @@ -109,3 +185,5 @@ def __convert_bytearray_to_image_array(self, byte_array): sub_arr.insert(0, 0) arr.append(sub_arr) return arr + + # Can get stripped images by stripping the image, or creating a stripped image from the bits diff --git a/src/microbit/image.py b/src/microbit/image.py index dc1e74a01..ac3590acb 100644 --- a/src/microbit/image.py +++ b/src/microbit/image.py @@ -87,7 +87,7 @@ def fill(self, value): def blit(self, src, x, y, w, h, xdest=0, ydest=0): - if not self.__valid_pos(x, y) or not src.__valid_pos(xdest, ydest): + if not src.__valid_pos(x, y) or not self.__valid_pos(xdest, ydest): raise ValueError(CONSTANTS.INDEX_ERR) for count_y in range(0, h): diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index b670b0772..8638b19ff 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -112,14 +112,15 @@ def test_show_iterable(self): expected = Image(TEST_IMAGES.A) value = [Image(TEST_IMAGES.EXCLAMATION_MARK), "A", "ab"] self.display.show(value) - print("TEST IMAGE ACTUAL BELOW") - self.__print(self.display._Display__image) assert self.__same_image(expected, self.display._Display__image) def test_show_non_iterable(self): with pytest.raises(TypeError): self.display.show(TestDisplay()) + def test_scroll(self): + self.display.scroll("tasdf") + # Helpers def __is_clear(self): i = Image() From 7593b04d8b2f8ab0f079e0e95e024176d0d8a176 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Mon, 3 Feb 2020 16:36:44 -0800 Subject: [PATCH 18/38] before merge --- src/microbit/constants.py | 8 ++ src/microbit/display.py | 165 +++++++++++++++++------------- src/microbit/test/test_display.py | 14 +-- 3 files changed, 112 insertions(+), 75 deletions(-) diff --git a/src/microbit/constants.py b/src/microbit/constants.py index 7cee0f6ad..2e3719607 100644 --- a/src/microbit/constants.py +++ b/src/microbit/constants.py @@ -10,6 +10,14 @@ [0, 0, 0, 0, 0], ] +BLANK = [ + [0, 9, 0, 9, 0], + [9, 9, 9, 9, 9], + [9, 9, 9, 9, 9], + [0, 9, 9, 9, 0], + [0, 0, 9, 0, 0], +] + # 5x5 Alphabet # Taken from https://raw.githubusercontent.com/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" diff --git a/src/microbit/display.py b/src/microbit/display.py index 536af0482..7db3593ee 100644 --- a/src/microbit/display.py +++ b/src/microbit/display.py @@ -1,4 +1,5 @@ import time +import threading from . import constants as CONSTANTS from .image import Image @@ -7,13 +8,16 @@ class Display: def __init__(self): - # State in the Python process self.__image = Image() self.__on = True def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): - # wait has no effect - # going to implement first with monospace = True. therefore every char is 5 wide + if not wait: + thread = threading.Thread( + target=self.scroll, args=(value, delay, True, loop, monospace) + ) + thread.start() + return while True: try: value = str(value) @@ -21,8 +25,21 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): raise e letters = [] for c in value: - letters.append(self.__get_image_from_char(c)) - appended_image = self.__create_scroll_image(letters, monospace) + if monospace: + if c == " ": + letters.append(Image("000000:000000:000000:000000:000000")) + else: + letters.append(self.__get_image_from_char(c)) + letters.append(Image("0:0:0:0:0:")) + else: + if c == " ": + letters.append(Image("000:000:000:000:000")) + else: + letters.append( + self.__strip_image(self.__get_image_from_char(c)) + ) + letters.append(Image("0:0:0:0:0:")) + appended_image = self.__create_scroll_image(letters) for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): self.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT @@ -32,68 +49,13 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): if not loop: break - def __strip_image(self, image): - # Find column that contains first lit pixel. Call that column number: c1. - # Go reverse, and find number of columns seen until we see the last lit pixel. Call that number: c2. - return image.crop(c1, 0, image.width() - c1 - c2, image.height()) - - def __insert_blank_column(self, image): - for row in image._Image__LED: - row.append(0) - - def __create_scroll_image(self, images, monospace): - blank_5x5_image = Image(CONSTANTS.BLANK) - front_image = blank_5x5_image.crop( - 0, 0, CONSTANTS.LED_WIDTH - 1, CONSTANTS.LED_HEIGHT - ) - images.insert(0, front_image) - - scroll_image = self.__append_images(images) - end_image = Image() - # Insert columns of 0s until the ending is a 5x5 blank - end_image.blit( - scroll_image, - scroll_image.width() - CONSTANTS.LED_WIDTH, - 0, - CONSTANTS.LED_WIDTH, - CONSTANTS.LED_HEIGHT, - ) - while not self.__same_image(end_image, blank_5x5_image): - self.__insert_blank_column(scroll_image) - end_image.blit( - scroll_image, - scroll_image.width() - CONSTANTS.LED_WIDTH, - 0, - CONSTANTS.LED_WIDTH, - CONSTANTS.LED_HEIGHT, - ) - - return scroll_image - - def __same_image(self, 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 - - def __append_images(self, 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 - def show(self, value, delay=400, wait=True, loop=False, clear=False): - # wait has no effect + if not wait: + thread = threading.Thread( + target=self.show, args=(value, delay, True, loop, clear) + ) + thread.start() + return while True: if isinstance(value, Image): self.__image = value.crop( @@ -137,7 +99,7 @@ def set_pixel(self, x, y, value): self.__image.set_pixel(x, y, value) def clear(self): - self.__image = Image() + self.__image = Image("00000:00000:00000:00000:00000:") def on(self): self.__on = True @@ -166,6 +128,18 @@ def __get_image_from_char(self, c): representative_bytes = CONSTANTS.ALPHABET[offset : offset + 5] return Image(self.__convert_bytearray_to_image_array(representative_bytes)) + def __strip_image(self, image): + # Find column that contains first lit pixel. Call that column number: c1. + # Go reverse, and find number of columns seen until we see the last lit pixel. Call that number: c2. + 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) + def __convert_bytearray_to_image_array(self, byte_array): arr = [] for b in byte_array: @@ -173,7 +147,7 @@ def __convert_bytearray_to_image_array(self, byte_array): b_as_bits = str(bin(b))[2:] sub_arr = [] while len(sub_arr) < 5: - # Iterate throught bits recursively + # 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: @@ -186,4 +160,57 @@ def __convert_bytearray_to_image_array(self, byte_array): arr.append(sub_arr) return arr - # Can get stripped images by stripping the image, or creating a stripped image from the bits + def __insert_blank_column(self, image): + for row in image._Image__LED: + row.append(0) + + def __create_scroll_image(self, images): + blank_5x5_image = Image("00000:00000:00000:00000:00000:") + front_image = blank_5x5_image.crop( + 0, 0, CONSTANTS.LED_WIDTH - 1, CONSTANTS.LED_HEIGHT + ) + images.insert(0, front_image) + + scroll_image = self.__append_images(images) + end_image = Image("00000:00000:00000:00000:00000:") + # Insert columns of 0s until the ending is a 5x5 blank + end_image.blit( + scroll_image, + scroll_image.width() - CONSTANTS.LED_WIDTH, + 0, + CONSTANTS.LED_WIDTH, + CONSTANTS.LED_HEIGHT, + ) + while not self.__same_image(end_image, blank_5x5_image): + self.__insert_blank_column(scroll_image) + end_image.blit( + scroll_image, + scroll_image.width() - CONSTANTS.LED_WIDTH, + 0, + CONSTANTS.LED_WIDTH, + CONSTANTS.LED_HEIGHT, + ) + + return scroll_image + + def __same_image(self, 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 + + def __append_images(self, 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 diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 8638b19ff..6471b4a68 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -90,7 +90,7 @@ def test_show_smaller_image(self): @pytest.mark.parametrize( "value, expected_arr", [ - ("!", TEST_IMAGES.EXCLAMATION_MARK), + ("!", "09000:09000:09000:00000:09000:"), ("A", TEST_IMAGES.A), (" ", TEST_IMAGES.BLANK), (6, TEST_IMAGES.SIX), @@ -103,9 +103,11 @@ def test_show_char(self, value, expected_arr): assert self.__same_image(expected, self.display._Display__image) def test_show_char_with_clear(self): - expected = Image(TEST_IMAGES.BLANK) - value = TEST_IMAGES.QUESTION_MARK - self.display.show(value, clear=True) + expected = Image("00000:00000:00000:00000:00000:") + image = Image("09000:09000:09000:00000:09000:") + self.display.show(image, clear=True) + print(expected._Image__LED) + print(self.display._Display__image._Image__LED) assert self.__same_image(expected, self.display._Display__image) def test_show_iterable(self): @@ -119,11 +121,11 @@ def test_show_non_iterable(self): self.display.show(TestDisplay()) def test_scroll(self): - self.display.scroll("tasdf") + self.display.scroll("m m!", wait=False) # Helpers def __is_clear(self): - i = Image() + i = Image("00000:00000:00000:00000:00000:") return self.__same_image(i, self.display._Display__image) def __same_image(self, i1, i2): From 5a11e1c207b5ac28b3d00d505a9cf4f439f0a2d9 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Mon, 3 Feb 2020 17:35:06 -0800 Subject: [PATCH 19/38] Changed comment --- src/microbit/model/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 385760609..136b963c9 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -40,7 +40,7 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): ) letters.append(Image(1, 5)) appended_image = Display.__create_scroll_image(letters) - # Show the scrolled image a square at a time. + # Show the scrolled image one square at a time. for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): self.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT From 39643f407ad5644d59a96a8b8b9ce520353dc9bd Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Mon, 3 Feb 2020 17:36:13 -0800 Subject: [PATCH 20/38] new test --- src/microbit/test/test_display.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 59a0d512b..ebea47e62 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -140,3 +140,8 @@ def __print(self, img): # pytest src/microbit/test/test_display.py --cov-report=html --cov=src/microbit + +# Need tests for +# threaded show +# threaded scroll +# normal scroll From 6f0cbe9f4cc8569e5f2954b6974d9b502fb913d1 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 10:32:34 -0800 Subject: [PATCH 21/38] test --- src/microbit/model/display.py | 39 ++++----------------- src/microbit/model/image.py | 56 ++++++++++++++++++++++--------- src/microbit/test/test_display.py | 15 +++++---- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 136b963c9..52e1b869c 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -27,18 +27,18 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): for c in value: if monospace: if c == " ": - letters.append(Image(6, 5)) + letters.append(Image(6, CONSTANTS.LED_HEIGHT)) else: letters.append(Display.__get_image_from_char(c)) - letters.append(Image(1, 5)) + letters.append(Image(1, CONSTANTS.LED_HEIGHT)) else: if c == " ": - letters.append(Image(3, 5)) + letters.append(Image(3, CONSTANTS.LED_HEIGHT)) else: letters.append( Display.__strip_image(Display.__get_image_from_char(c)) ) - letters.append(Image(1, 5)) + letters.append(Image(1, CONSTANTS.LED_HEIGHT)) appended_image = Display.__create_scroll_image(letters) # Show the scrolled image one square at a time. for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): @@ -131,10 +131,9 @@ def __get_image_from_char(c): representative_bytes = CONSTANTS.ALPHABET[offset : offset + 5] return Image(Display.__convert_bytearray_to_image_str(representative_bytes)) + # Removes columns that are not lit @staticmethod def __strip_image(image): - # Find column that contains first lit pixel. Call that column number: c1. - # Go reverse, and find number of columns seen until we see the last lit pixel. Call that number: c2. min_index = CONSTANTS.LED_WIDTH - 1 max_index = 0 for row in image._Image__LED: @@ -183,7 +182,7 @@ def __create_scroll_image(images): ) images.insert(0, front_image) - scroll_image = Display.__append_images(images) + scroll_image = Image._Image__append_images(images) end_image = Image() # Insert columns of 0s until the ending is a 5x5 blank end_image.blit( @@ -193,7 +192,7 @@ def __create_scroll_image(images): CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT, ) - while not Display.__same_image(end_image, blank_5x5_image): + while not Image._Image__same_image(end_image, blank_5x5_image): Display.__insert_blank_column(scroll_image) end_image.blit( scroll_image, @@ -205,27 +204,3 @@ def __create_scroll_image(images): return scroll_image - @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 - - @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 - diff --git a/src/microbit/model/image.py b/src/microbit/model/image.py index a407c7ab0..18d75cd1d 100644 --- a/src/microbit/model/image.py +++ b/src/microbit/model/image.py @@ -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 @@ -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 diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index ebea47e62..23722608b 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -1,4 +1,5 @@ import pytest +import threading from ..model import constants as CONSTANTS from ..model.display import Display @@ -73,7 +74,7 @@ def test_show_one_image(self): img.set_pixel(0, 2, 7) img.set_pixel(2, 2, 6) self.display.show(img) - assert Display._Display__same_image(img, self.display._Display__image) + assert Image._Image__same_image(img, self.display._Display__image) def test_show_different_size_image(self): img = Image(3, 7) @@ -82,7 +83,7 @@ def test_show_different_size_image(self): expected = Image(5, 5) expected.set_pixel(1, 1, 9) self.display.show(img) - assert Display._Display__same_image(expected, self.display._Display__image) + assert Image._Image__same_image(expected, self.display._Display__image) def test_show_smaller_image(self): img = Image(2, 2) @@ -90,7 +91,7 @@ def test_show_smaller_image(self): expected = Image(5, 5) expected.set_pixel(1, 1, 9) self.display.show(img) - assert Display._Display__same_image(expected, self.display._Display__image) + assert Image._Image__same_image(expected, self.display._Display__image) @pytest.mark.parametrize( "value, expected_str", @@ -105,7 +106,7 @@ def test_show_smaller_image(self): def test_show_char(self, value, expected_str): expected = Image(expected_str) self.display.show(value) - assert Display._Display__same_image(expected, self.display._Display__image) + assert Image._Image__same_image(expected, self.display._Display__image) def test_show_char_with_clear(self): expected = Image(CONSTANTS.BLANK_5X5) @@ -113,13 +114,13 @@ def test_show_char_with_clear(self): self.display.show(image, clear=True) print(expected._Image__LED) print(self.display._Display__image._Image__LED) - assert Display._Display__same_image(expected, self.display._Display__image) + assert Image._Image__same_image(expected, self.display._Display__image) def test_show_iterable(self): expected = Image(STR_A) value = [Image(STR_EXCLAMATION_MARK), "A", "ab"] self.display.show(value) - assert Display._Display__same_image(expected, self.display._Display__image) + assert Image._Image__same_image(expected, self.display._Display__image) def test_show_non_iterable(self): with pytest.raises(TypeError): @@ -131,7 +132,7 @@ def test_scroll(self): # Helpers def __is_clear(self): i = Image(CONSTANTS.BLANK_5X5) - return Display._Display__same_image(i, self.display._Display__image) + return Image._Image__same_image(i, self.display._Display__image) def __print(self, img): print("") From 88a12269fcc7cb9984b01e50d234731e337c3216 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 11:06:08 -0800 Subject: [PATCH 22/38] Done unit tests --- src/microbit/model/display.py | 10 ++++++---- src/microbit/test/test_display.py | 28 ++++++++++++++++++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 52e1b869c..f1b4555ea 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -19,10 +19,10 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): thread.start() return while True: - try: + if isinstance(value, (str, int, float)): value = str(value) - except TypeError as e: - raise e + else: + raise TypeError(f"can't convert {type(value)} object to str implicitly") letters = [] for c in value: if monospace: @@ -45,7 +45,6 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): self.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) - self.__print() time.sleep(delay / 1000) if not loop: break @@ -117,6 +116,9 @@ def read_light_level(self): # Helpers + def __get_array(self): + return self.__image._Image__LED + def __print(self): print("") for i in range(CONSTANTS.LED_HEIGHT): diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 23722608b..925782c92 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -1,5 +1,6 @@ import pytest import threading +from unittest import mock from ..model import constants as CONSTANTS from ..model.display import Display @@ -109,12 +110,9 @@ def test_show_char(self, value, expected_str): assert Image._Image__same_image(expected, self.display._Display__image) def test_show_char_with_clear(self): - expected = Image(CONSTANTS.BLANK_5X5) image = Image(STR_EXCLAMATION_MARK) self.display.show(image, clear=True) - print(expected._Image__LED) - print(self.display._Display__image._Image__LED) - assert Image._Image__same_image(expected, self.display._Display__image) + assert self.__is_clear() def test_show_iterable(self): expected = Image(STR_A) @@ -126,8 +124,23 @@ def test_show_non_iterable(self): with pytest.raises(TypeError): self.display.show(TestDisplay()) + def test_show_threaded(self): + threading.Thread = mock.Mock() + self.display.show("a", wait=False) + threading.Thread.assert_called_once() + def test_scroll(self): - self.display.scroll("n!") + self.display.scroll("a b") + self.__is_clear() + + def test_scroll_type_error(self): + with pytest.raises(TypeError): + self.display.scroll(["a", 1]) + + def test_scroll_threaded(self): + threading.Thread = mock.Mock() + self.display.scroll("test", wait=False) + threading.Thread.assert_called_once() # Helpers def __is_clear(self): @@ -141,8 +154,3 @@ def __print(self, img): # pytest src/microbit/test/test_display.py --cov-report=html --cov=src/microbit - -# Need tests for -# threaded show -# threaded scroll -# normal scroll From 8a766846f0f5a1c5e5e54b00843ca7a0c559fa3d Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 11:08:28 -0800 Subject: [PATCH 23/38] Removed unused file --- src/microbit/constants.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 src/microbit/constants.py diff --git a/src/microbit/constants.py b/src/microbit/constants.py deleted file mode 100644 index 9e288a098..000000000 --- a/src/microbit/constants.py +++ /dev/null @@ -1,29 +0,0 @@ -LED_WIDTH = 5 -LED_HEIGHT = 5 -MAX_BRIGHTNESS = 9 - -BLANK = [ - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], -] - -BLANK = [ - [0, 9, 0, 9, 0], - [9, 9, 9, 9, 9], - [9, 9, 9, 9, 9], - [0, 9, 9, 9, 0], - [0, 0, 9, 0, 0], -] - - -# Errors -INDEX_ERR = "index out of bounds" -BRIGHTNESS_ERR = "brightness out of bounds" -SAME_SIZE_ERR = "images must be the same size" -UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" -INCORR_IMAGE_SIZE = "image data is incorrect size" -NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" -COPY_ERR_MESSAGE = "please copy() first" From a6d24b9f40a7a43e2bd0307e5ef44c489d7ba1d3 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 11:09:09 -0800 Subject: [PATCH 24/38] Deleted comments --- src/microbit/test/test_display.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 925782c92..2af69fee5 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -146,11 +146,3 @@ def test_scroll_threaded(self): def __is_clear(self): i = Image(CONSTANTS.BLANK_5X5) return Image._Image__same_image(i, self.display._Display__image) - - def __print(self, img): - print("") - for i in range(5): - print(img._Image__LED[i]) - - -# pytest src/microbit/test/test_display.py --cov-report=html --cov=src/microbit From fa53c7d28093a52a4894c8354e436ca30390c01b Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 11:26:28 -0800 Subject: [PATCH 25/38] black formatting --- src/microbit/model/display.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index f1b4555ea..d98bd7e7e 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -205,4 +205,3 @@ def __create_scroll_image(images): ) return scroll_image - From dee99d440ac61f15a3809a36686b4bf4774b68be Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 11:44:22 -0800 Subject: [PATCH 26/38] Updated microbit model to include display --- src/microbit/model/microbit_model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/microbit/model/microbit_model.py b/src/microbit/model/microbit_model.py index f8e2572da..521f8def1 100644 --- a/src/microbit/model/microbit_model.py +++ b/src/microbit/model/microbit_model.py @@ -1,6 +1,7 @@ import time from .button import Button +from .display import Display class MicrobitModel: @@ -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) From 6be25867b59a90a87f5429dc63e26b99574a474f Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 14:33:58 -0800 Subject: [PATCH 27/38] Addressed comments --- src/microbit/model/constants.py | 1 + src/microbit/model/display.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/microbit/model/constants.py b/src/microbit/model/constants.py index 017524025..7f6b40e7d 100644 --- a/src/microbit/model/constants.py +++ b/src/microbit/model/constants.py @@ -98,6 +98,7 @@ # 5x5 Alphabet # Taken from https://raw.githubusercontent.com/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 diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index d98bd7e7e..4fcaeef70 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -1,3 +1,4 @@ +import copy import time import threading @@ -117,20 +118,17 @@ def read_light_level(self): # Helpers def __get_array(self): - return self.__image._Image__LED - - def __print(self): - print("") - for i in range(CONSTANTS.LED_HEIGHT): - print(self._Display__image._Image__LED[i]) + return copy.deepcopy(self.__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) * 5 - representative_bytes = CONSTANTS.ALPHABET[offset : offset + 5] + 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 @@ -145,6 +143,8 @@ def __strip_image(image): 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 = [] From e940be0e66751d024ed7ee1dec6d580e0b79fd09 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Tue, 4 Feb 2020 14:41:10 -0800 Subject: [PATCH 28/38] added comment --- src/microbit/model/display.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 4fcaeef70..a45fb0bc9 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -8,6 +8,7 @@ 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 From e8105f1aa2ee8518d5dd989d069e9d902c41387c Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Wed, 5 Feb 2020 10:45:00 -0800 Subject: [PATCH 29/38] Updated how display on and off functionality work --- src/microbit/model/display.py | 6 +++++- src/microbit/test/test_display.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index a45fb0bc9..a15c09072 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -12,6 +12,7 @@ class Display: def __init__(self): self.__image = Image() self.__on = True + self.__blank_image = Image() def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): if not wait: @@ -119,7 +120,10 @@ def read_light_level(self): # Helpers def __get_array(self): - return copy.deepcopy(self.__image._Image__LED) + if self.is_on(): + return copy.deepcopy(self.__image._Image__LED) + else: + return self.__blank_image._Image__LED @staticmethod def __get_image_from_char(c): diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 2af69fee5..a96a27e71 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -142,6 +142,26 @@ def test_scroll_threaded(self): self.display.scroll("test", wait=False) threading.Thread.assert_called_once() + def test_get_array(self): + self.display.set_pixel(3, 3, 3) + self.display.off() + assert self.display._Display__get_array() == [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + + self.display.on() + assert self.display._Display__get_array() == [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 3, 0], + [0, 0, 0, 0, 0], + ] + # Helpers def __is_clear(self): i = Image(CONSTANTS.BLANK_5X5) From e35a11b22434ea80446d132ca8412b1abe4db5da Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Wed, 5 Feb 2020 17:21:34 -0800 Subject: [PATCH 30/38] incorporated PR feedback --- src/microbit/model/constants.py | 2 + src/microbit/model/display.py | 137 +++++++++++++++++------------- src/microbit/test/test_display.py | 48 +++++------ 3 files changed, 103 insertions(+), 84 deletions(-) diff --git a/src/microbit/model/constants.py b/src/microbit/model/constants.py index 7f6b40e7d..ebe061a18 100644 --- a/src/microbit/model/constants.py +++ b/src/microbit/model/constants.py @@ -101,6 +101,8 @@ # 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 diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index a15c09072..6c88b2214 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -9,88 +9,113 @@ 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.__blank_image = Image() + self.__current_pid = None def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): + # Set current_pid to the thread's identifier + self.__current_pid = threading.get_ident() + if not wait: thread = threading.Thread( target=self.scroll, args=(value, delay, True, loop, monospace) ) thread.start() return - while True: - if isinstance(value, (str, int, float)): - value = str(value) + + 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: - raise TypeError(f"can't convert {type(value)} object to str implicitly") - letters = [] - for c in value: - if monospace: - if c == " ": - letters.append(Image(6, CONSTANTS.LED_HEIGHT)) - else: - letters.append(Display.__get_image_from_char(c)) - letters.append(Image(1, CONSTANTS.LED_HEIGHT)) + if c == " ": + letters.append( + Image(CONSTANTS.WHITESPACE_WIDTH, CONSTANTS.LED_HEIGHT) + ) else: - if c == " ": - letters.append(Image(3, CONSTANTS.LED_HEIGHT)) - else: - letters.append( - Display.__strip_image(Display.__get_image_from_char(c)) + letters.append( + Display.__strip_unlit_columns(Display.__get_image_from_char(c)) + ) + letters.append( + Image( + CONSTANTS.SPACE_BETWEEN_LETTERS_WIDTH, CONSTANTS.LED_HEIGHT, ) - letters.append(Image(1, CONSTANTS.LED_HEIGHT)) - appended_image = Display.__create_scroll_image(letters) + ) + 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.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) + # If show or scroll is called again, there will be a different pid and break + if self.__current_pid != threading.get_ident(): + break time.sleep(delay / 1000) if not loop: break def show(self, value, delay=400, wait=True, loop=False, clear=False): + # Set current_pid to the thread's identifier + self.__current_pid = threading.get_ident() + if not wait: thread = threading.Thread( target=self.show, args=(value, delay, True, loop, clear) ) thread.start() return - while True: - if isinstance(value, Image): - self.__image = value.crop( - 0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT - ) - elif isinstance(value, (str, int, float)): - if isinstance(value, str): - chars = list(value) + + 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: - chars = list(str(value)) + break + use_delay = True - for c in chars: - self.__image = Display.__get_image_from_char(c) - time.sleep(delay / 1000) - else: - # Check if iterable - try: - _ = iter(value) - except TypeError as e: - raise e - - for elem in value: - if isinstance(elem, Image): - self.__image = elem.crop( - 0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT - ) - elif isinstance(elem, str) and len(elem) == 1: - self.__image = Display.__get_image_from_char(elem) - # If elem is not char or image, break without iterating through rest of list - else: - break + while True: + for image in images: + self.__image = image + if use_delay: time.sleep(delay / 1000) + # If show or scroll is called again, there will be a different pid and break + if self.__current_pid != threading.get_ident(): + break if not loop: break if clear: @@ -138,7 +163,7 @@ def __get_image_from_char(c): # Removes columns that are not lit @staticmethod - def __strip_image(image): + def __strip_unlit_columns(image): min_index = CONSTANTS.LED_WIDTH - 1 max_index = 0 for row in image._Image__LED: @@ -184,24 +209,22 @@ def __insert_blank_column(image): @staticmethod def __create_scroll_image(images): blank_5x5_image = Image() - front_image = blank_5x5_image.crop( - 0, 0, CONSTANTS.LED_WIDTH - 1, CONSTANTS.LED_HEIGHT - ) - images.insert(0, front_image) + front_of_scroll_image = Image(4, 5) + images.insert(0, front_of_scroll_image) scroll_image = Image._Image__append_images(images) - end_image = Image() + end_of_scroll_image = Image() # Insert columns of 0s until the ending is a 5x5 blank - end_image.blit( + 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_image, blank_5x5_image): + while not Image._Image__same_image(end_of_scroll_image, blank_5x5_image): Display.__insert_blank_column(scroll_image) - end_image.blit( + end_of_scroll_image.blit( scroll_image, scroll_image.width() - CONSTANTS.LED_WIDTH, 0, diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index a96a27e71..3f2f2f084 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -19,8 +19,8 @@ def setup_method(self): self.display = Display() @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) - def test_get_pixel(self, x, y, brightness): - self.display._Display__image._Image__LED[y][x] = brightness + def test_set_and_get_pixel(self, x, y, brightness): + self.display.set_pixel(x, y, brightness) assert brightness == self.display.get_pixel(x, y) @pytest.mark.parametrize("x, y", [(5, 0), (0, -1), (0, 5)]) @@ -28,11 +28,6 @@ def test_get_pixel_error(self, x, y): with pytest.raises(ValueError, match=CONSTANTS.INDEX_ERR): self.display.get_pixel(x, y) - @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) - def test_set_pixel(self, x, y, brightness): - self.display.set_pixel(x, y, brightness) - assert brightness == self.display._Display__image._Image__LED[y][x] - @pytest.mark.parametrize( "x, y, brightness, err_msg", [ @@ -46,27 +41,18 @@ def test_set_pixel_error(self, x, y, brightness, err_msg): self.display.set_pixel(x, y, brightness) def test_clear(self): - self.display._Display__image._Image__LED[2][3] = 7 - self.display._Display__image._Image__LED[3][4] = 6 - self.display._Display__image._Image__LED[4][4] = 9 + self.display.set_pixel(2, 3, 7) + self.display.set_pixel(3, 4, 6) + self.display.set_pixel(4, 4, 9) assert not self.__is_clear() self.display.clear() assert self.__is_clear() - def test_on(self): - self.display._Display__on = False + def test_on_off(self): self.display.on() - assert self.display._Display__on - - def test_off(self): - self.display._Display__on = True + assert self.display.is_on() self.display.off() - assert False == self.display._Display__on - - @pytest.mark.parametrize("on", [True, False]) - def test_is_on(self, on): - self.display._Display__on = on - assert on == self.display.is_on() + assert not self.display.is_on() def test_show_one_image(self): img = Image() @@ -124,11 +110,6 @@ def test_show_non_iterable(self): with pytest.raises(TypeError): self.display.show(TestDisplay()) - def test_show_threaded(self): - threading.Thread = mock.Mock() - self.display.show("a", wait=False) - threading.Thread.assert_called_once() - def test_scroll(self): self.display.scroll("a b") self.__is_clear() @@ -137,6 +118,12 @@ def test_scroll_type_error(self): with pytest.raises(TypeError): self.display.scroll(["a", 1]) + # Should change these threaded tests to test behaviour in the future + def test_show_threaded(self): + threading.Thread = mock.Mock() + self.display.show("a", wait=False) + threading.Thread.assert_called_once() + def test_scroll_threaded(self): threading.Thread = mock.Mock() self.display.scroll("test", wait=False) @@ -162,6 +149,13 @@ def test_get_array(self): [0, 0, 0, 0, 0], ] + # The second show call should immedaitely stop the first show call. + # Therefore the final result of display should be 6. + def test_async_tests(self): + self.display.show("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA", wait=False) + self.display.show("6") + assert Image._Image__same_image(Image(STR_SIX), self.display._Display__image) + # Helpers def __is_clear(self): i = Image(CONSTANTS.BLANK_5X5) From cd5386f47deae531891c992b28984fefb1ab820f Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 5 Feb 2020 19:56:14 -0800 Subject: [PATCH 31/38] Refactored code --- src/microbit/model/display.py | 14 +++++++------- src/microbit/test/test_display.py | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 6c88b2214..8fa18afdf 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -17,9 +17,6 @@ def __init__(self): self.__current_pid = None def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): - # Set current_pid to the thread's identifier - self.__current_pid = threading.get_ident() - if not wait: thread = threading.Thread( target=self.scroll, args=(value, delay, True, loop, monospace) @@ -27,6 +24,9 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): thread.start() return + # Set current_pid to the thread's identifier + self.__current_pid = threading.get_ident() + if isinstance(value, (str, int, float)): value = str(value) else: @@ -69,9 +69,6 @@ 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): - # Set current_pid to the thread's identifier - self.__current_pid = threading.get_ident() - if not wait: thread = threading.Thread( target=self.show, args=(value, delay, True, loop, clear) @@ -79,6 +76,9 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): thread.start() return + # Set current_pid to the thread's identifier + self.__current_pid = threading.get_ident() + images = [] use_delay = False if isinstance(value, Image): @@ -232,4 +232,4 @@ def __create_scroll_image(images): CONSTANTS.LED_HEIGHT, ) - return scroll_image + return scroll_image \ No newline at end of file diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 3f2f2f084..23f3abdda 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -60,7 +60,7 @@ def test_show_one_image(self): img.set_pixel(0, 1, 9) img.set_pixel(0, 2, 7) img.set_pixel(2, 2, 6) - self.display.show(img) + self.display.show(img, delay=0) assert Image._Image__same_image(img, self.display._Display__image) def test_show_different_size_image(self): @@ -69,7 +69,7 @@ def test_show_different_size_image(self): img.set_pixel(2, 6, 9) # Will not be on display expected = Image(5, 5) expected.set_pixel(1, 1, 9) - self.display.show(img) + self.display.show(img, delay=0) assert Image._Image__same_image(expected, self.display._Display__image) def test_show_smaller_image(self): @@ -77,7 +77,7 @@ def test_show_smaller_image(self): img.set_pixel(1, 1, 9) expected = Image(5, 5) expected.set_pixel(1, 1, 9) - self.display.show(img) + self.display.show(img, delay=0) assert Image._Image__same_image(expected, self.display._Display__image) @pytest.mark.parametrize( @@ -92,18 +92,18 @@ def test_show_smaller_image(self): ) def test_show_char(self, value, expected_str): expected = Image(expected_str) - self.display.show(value) + self.display.show(value, delay=0) assert Image._Image__same_image(expected, self.display._Display__image) def test_show_char_with_clear(self): image = Image(STR_EXCLAMATION_MARK) - self.display.show(image, clear=True) + self.display.show(image, delay=0, clear=True) assert self.__is_clear() def test_show_iterable(self): expected = Image(STR_A) value = [Image(STR_EXCLAMATION_MARK), "A", "ab"] - self.display.show(value) + self.display.show(value, delay=0) assert Image._Image__same_image(expected, self.display._Display__image) def test_show_non_iterable(self): @@ -121,12 +121,12 @@ def test_scroll_type_error(self): # Should change these threaded tests to test behaviour in the future def test_show_threaded(self): threading.Thread = mock.Mock() - self.display.show("a", wait=False) + self.display.show("a", delay=0, wait=False) threading.Thread.assert_called_once() def test_scroll_threaded(self): threading.Thread = mock.Mock() - self.display.scroll("test", wait=False) + self.display.scroll("test", delay=0, wait=False) threading.Thread.assert_called_once() def test_get_array(self): @@ -152,8 +152,8 @@ def test_get_array(self): # The second show call should immedaitely stop the first show call. # Therefore the final result of display should be 6. def test_async_tests(self): - self.display.show("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA", wait=False) - self.display.show("6") + self.display.show("MMMMMMMMMMMMMM", delay=100, wait=False) + self.display.show("6", delay=0) assert Image._Image__same_image(Image(STR_SIX), self.display._Display__image) # Helpers From 972057f6c16bd9825a0718e7ac5738689c013cf9 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 5 Feb 2020 20:12:15 -0800 Subject: [PATCH 32/38] formatted with black --- src/microbit/model/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 8fa18afdf..136c00245 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -232,4 +232,4 @@ def __create_scroll_image(images): CONSTANTS.LED_HEIGHT, ) - return scroll_image \ No newline at end of file + return scroll_image From 164a481e1bc00e98522c21d0a5aebbc033428ec0 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 6 Feb 2020 10:20:42 -0800 Subject: [PATCH 33/38] Lock acquisition added --- src/microbit/model/display.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 6c88b2214..db61e678a 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -14,11 +14,14 @@ def __init__(self): self.__image = Image() self.__on = True self.__blank_image = Image() + self.__lock = threading.Lock() self.__current_pid = None def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): # Set current_pid to the thread's identifier + self.__lock.acquire() self.__current_pid = threading.get_ident() + self.__lock.release() if not wait: thread = threading.Thread( @@ -62,15 +65,20 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) # If show or scroll is called again, there will be a different pid and break + self.__lock.acquire() if self.__current_pid != threading.get_ident(): break + self.__lock.release() + time.sleep(delay / 1000) if not loop: break def show(self, value, delay=400, wait=True, loop=False, clear=False): # Set current_pid to the thread's identifier + self.__lock.acquire() self.__current_pid = threading.get_ident() + self.__lock.release() if not wait: thread = threading.Thread( @@ -114,8 +122,10 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): if use_delay: time.sleep(delay / 1000) # If show or scroll is called again, there will be a different pid and break + self.__lock.acquire() if self.__current_pid != threading.get_ident(): break + self.__lock.release() if not loop: break if clear: From 0a62a0cdf56e30e25aac5ca1d966db02c0e9d05f Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 6 Feb 2020 10:44:50 -0800 Subject: [PATCH 34/38] Added image lock --- src/microbit/model/display.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 16a00166e..856f6ae7e 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -13,9 +13,11 @@ class Display: def __init__(self): self.__image = Image() self.__on = True - self.__blank_image = Image() - self.__lock = threading.Lock() self.__current_pid = None + self.__blank_image = Image() + + self.__image_lock = threading.Lock() + self.__thread_identifier_lock = threading.Lock() def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): if not wait: @@ -26,9 +28,9 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): return # Set current_pid to the thread's identifier - self.__lock.acquire() + self.__thread_identifier_lock.acquire() self.__current_pid = threading.get_ident() - self.__lock.release() + self.__thread_identifier_lock.release() if isinstance(value, (str, int, float)): value = str(value) @@ -60,17 +62,20 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): while True: # Show the scrolled image one square at a time. + self.__image_lock.acquire() for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): self.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) # If show or scroll is called again, there will be a different pid and break - self.__lock.acquire() + self.__thread_identifier_lock.acquire() if self.__current_pid != threading.get_ident(): break - self.__lock.release() + self.__thread_identifier_lock.release() time.sleep(delay / 1000) + self.__image_lock.release() + if not loop: break @@ -83,9 +88,9 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): return # Set current_pid to the thread's identifier - self.__lock.acquire() + self.__thread_identifier_lock.acquire() self.__current_pid = threading.get_ident() - self.__lock.release() + self.__thread_identifier_lock.release() images = [] use_delay = False @@ -117,15 +122,18 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): use_delay = True while True: + self.__image_lock.acquire() for image in images: self.__image = image if use_delay: time.sleep(delay / 1000) # If show or scroll is called again, there will be a different pid and break - self.__lock.acquire() + self.__thread_identifier_lock.acquire() if self.__current_pid != threading.get_ident(): break - self.__lock.release() + self.__thread_identifier_lock.release() + self.__image_lock.release() + if not loop: break if clear: From 975ab1d788a81e40cb28aa6cf23690b1593c1392 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 6 Feb 2020 10:49:16 -0800 Subject: [PATCH 35/38] updated image lock --- src/microbit/model/display.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 856f6ae7e..61cfc0117 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -62,11 +62,13 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): while True: # Show the scrolled image one square at a time. - self.__image_lock.acquire() for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): + self.__image_lock.acquire() self.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) + self.__image_lock.release() + # If show or scroll is called again, there will be a different pid and break self.__thread_identifier_lock.acquire() if self.__current_pid != threading.get_ident(): @@ -74,7 +76,6 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): self.__thread_identifier_lock.release() time.sleep(delay / 1000) - self.__image_lock.release() if not loop: break @@ -122,18 +123,20 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): use_delay = True while True: - self.__image_lock.acquire() for image in images: + self.__image_lock.acquire() self.__image = image - if use_delay: - time.sleep(delay / 1000) + self.__image_lock.release() + # If show or scroll is called again, there will be a different pid and break self.__thread_identifier_lock.acquire() if self.__current_pid != threading.get_ident(): break self.__thread_identifier_lock.release() - self.__image_lock.release() - + + if use_delay: + time.sleep(delay / 1000) + if not loop: break if clear: From 1e502f917a1ad3dd72a11ea542c4a6b292cd4325 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 6 Feb 2020 12:26:48 -0800 Subject: [PATCH 36/38] Updated locks --- src/microbit/model/display.py | 41 ++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 61cfc0117..fd8adbc7f 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -17,7 +17,6 @@ def __init__(self): self.__blank_image = Image() self.__image_lock = threading.Lock() - self.__thread_identifier_lock = threading.Lock() def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): if not wait: @@ -28,9 +27,9 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): return # Set current_pid to the thread's identifier - self.__thread_identifier_lock.acquire() + self.__image_lock.acquire() self.__current_pid = threading.get_ident() - self.__thread_identifier_lock.release() + self.__image_lock.release() if isinstance(value, (str, int, float)): value = str(value) @@ -67,15 +66,14 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): self.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) - self.__image_lock.release() # If show or scroll is called again, there will be a different pid and break - self.__thread_identifier_lock.acquire() if self.__current_pid != threading.get_ident(): + self.__image_lock.release() break - self.__thread_identifier_lock.release() + self.__image_lock.release() - time.sleep(delay / 1000) + Display.sleep_ms(delay) if not loop: break @@ -89,9 +87,9 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): return # Set current_pid to the thread's identifier - self.__thread_identifier_lock.acquire() + self.__image_lock.acquire() self.__current_pid = threading.get_ident() - self.__thread_identifier_lock.release() + self.__image_lock.release() images = [] use_delay = False @@ -126,16 +124,15 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): for image in images: self.__image_lock.acquire() self.__image = image - self.__image_lock.release() # If show or scroll is called again, there will be a different pid and break - self.__thread_identifier_lock.acquire() if self.__current_pid != threading.get_ident(): + self.__image_lock.release() break - self.__thread_identifier_lock.release() + self.__image_lock.release() if use_delay: - time.sleep(delay / 1000) + Display.sleep_ms(delay) if not loop: break @@ -143,13 +140,20 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): self.clear() def get_pixel(self, x, y): - return self.__image.get_pixel(x, y) + self.__image_lock.acquire() + pixel = self.__image.get_pixel(x, y) + self.__image_lock.release() + return pixel def set_pixel(self, x, y, value): + self.__image_lock.acquire() self.__image.set_pixel(x, y, value) + self.__image_lock.release() def clear(self): + self.__image_lock.acquire() self.__image = Image() + self.__image_lock.release() def on(self): self.__on = True @@ -167,7 +171,10 @@ def read_light_level(self): def __get_array(self): if self.is_on(): - return copy.deepcopy(self.__image._Image__LED) + self.__image_lock.acquire() + leds = copy.deepcopy(self.__image._Image__LED) + self.__image_lock.release() + return leds else: return self.__blank_image._Image__LED @@ -254,3 +261,7 @@ def __create_scroll_image(images): ) return scroll_image + + @staticmethod + def sleep_ms(ms): + time.sleep(ms / 1000) From c2ca23ce4c006469f76e01c3b3121799dae64b76 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 6 Feb 2020 12:29:11 -0800 Subject: [PATCH 37/38] Updated locks --- src/microbit/model/display.py | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index fd8adbc7f..95a2f9b63 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -16,7 +16,7 @@ def __init__(self): self.__current_pid = None self.__blank_image = Image() - self.__image_lock = threading.Lock() + self.__lock = threading.Lock() def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): if not wait: @@ -27,9 +27,9 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): return # Set current_pid to the thread's identifier - self.__image_lock.acquire() + self.__lock.acquire() self.__current_pid = threading.get_ident() - self.__image_lock.release() + self.__lock.release() if isinstance(value, (str, int, float)): value = str(value) @@ -62,16 +62,16 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): while True: # Show the scrolled image one square at a time. for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): - self.__image_lock.acquire() + self.__lock.acquire() self.__image.blit( appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) # If show or scroll is called again, there will be a different pid and break if self.__current_pid != threading.get_ident(): - self.__image_lock.release() + self.__lock.release() break - self.__image_lock.release() + self.__lock.release() Display.sleep_ms(delay) @@ -87,9 +87,9 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): return # Set current_pid to the thread's identifier - self.__image_lock.acquire() + self.__lock.acquire() self.__current_pid = threading.get_ident() - self.__image_lock.release() + self.__lock.release() images = [] use_delay = False @@ -122,14 +122,14 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): while True: for image in images: - self.__image_lock.acquire() + self.__lock.acquire() self.__image = image # If show or scroll is called again, there will be a different pid and break if self.__current_pid != threading.get_ident(): - self.__image_lock.release() + self.__lock.release() break - self.__image_lock.release() + self.__lock.release() if use_delay: Display.sleep_ms(delay) @@ -140,20 +140,20 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): self.clear() def get_pixel(self, x, y): - self.__image_lock.acquire() + self.__lock.acquire() pixel = self.__image.get_pixel(x, y) - self.__image_lock.release() + self.__lock.release() return pixel def set_pixel(self, x, y, value): - self.__image_lock.acquire() + self.__lock.acquire() self.__image.set_pixel(x, y, value) - self.__image_lock.release() + self.__lock.release() def clear(self): - self.__image_lock.acquire() + self.__lock.acquire() self.__image = Image() - self.__image_lock.release() + self.__lock.release() def on(self): self.__on = True @@ -171,9 +171,9 @@ def read_light_level(self): def __get_array(self): if self.is_on(): - self.__image_lock.acquire() + self.__lock.acquire() leds = copy.deepcopy(self.__image._Image__LED) - self.__image_lock.release() + self.__lock.release() return leds else: return self.__blank_image._Image__LED From 8fc9b3b2d0281f4cc86ff14d96cd6cd2153b5262 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 6 Feb 2020 13:34:15 -0800 Subject: [PATCH 38/38] Updated locks --- src/microbit/model/display.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 95a2f9b63..82784ba91 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -63,14 +63,15 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): # Show the scrolled image one square at a time. for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): self.__lock.acquire() - self.__image.blit( - appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT - ) # 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) @@ -123,12 +124,13 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): while True: for image in images: self.__lock.acquire() - self.__image = image # 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: