From 25bb8872e933bc959e08041bb1bb4e8d7926136b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 9 Mar 2020 16:34:04 -0700 Subject: [PATCH 01/14] first commit --- package.json | 1 - src/adafruit_circuitplayground/express.py | 12 +-- src/common/telemetry_events.py | 3 + .../test/test_utils.py | 4 +- src/common/utils.py | 13 +++ src/microbit/__init__.py | 17 ++++ src/microbit/__model/compass.py | 88 +++++++++++++++++++ src/microbit/__model/i2c.py | 51 +++++++++++ src/microbit/__model/microbit_model.py | 19 ++++ src/microbit/__model/spi.py | 66 ++++++++++++++ 10 files changed, 267 insertions(+), 7 deletions(-) rename src/{adafruit_circuitplayground => common}/test/test_utils.py (92%) create mode 100644 src/microbit/__model/compass.py create mode 100644 src/microbit/__model/i2c.py create mode 100644 src/microbit/__model/spi.py diff --git a/package.json b/package.json index 59e9b4417..4c18fe24a 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "category": "%deviceSimulatorExpressExtension.commands.common.label%" }, { - "command": "deviceSimulatorExpress.common.openSerialMonitor", "title": "%deviceSimulatorExpressExtension.commands.common.openSerialMonitor%", "category": "%deviceSimulatorExpressExtension.commands.common.label%" diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index 884f4a723..895cbf78d 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -86,7 +86,7 @@ def tapped(self): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_TAPPED) - raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + utils.print_for_unimplemented_functions(Express.tap.__qualname__) @property def red_led(self): @@ -159,7 +159,9 @@ def adjust_touch_threshold(self, adjustement): The CPX Simulator doesn't use capacitive touch threshold. """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_ADJUST_THRESHOLD) - raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + utils.print_for_unimplemented_functions( + Express.adjust_touch_threshold.__qualname__ + ) def shake(self, shake_threshold=30): telemetry_py.send_telemetry(TelemetryEvent.CPX_API_SHAKE) @@ -192,19 +194,19 @@ def play_tone(self, frequency, duration): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_PLAY_TONE) - raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + utils.print_for_unimplemented_functions(Express.play_tone.__qualname__) def start_tone(self, frequency): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_START_TONE) - raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + utils.print_for_unimplemented_functions(Express.start_tone.__qualname__) def stop_tone(self): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_STOP_TONE) - raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) + utils.print_for_unimplemented_functions(Express.stop_tone.__qualname__) def update_state(self, new_state): for event in CONSTANTS.ALL_EXPECTED_INPUT_EVENTS: diff --git a/src/common/telemetry_events.py b/src/common/telemetry_events.py index d150076d0..678659288 100644 --- a/src/common/telemetry_events.py +++ b/src/common/telemetry_events.py @@ -31,3 +31,6 @@ class TelemetryEvent(enum.Enum): MICROBIT_API_IMAGE_OTHER = "MICROBIT.API.IMAGE.OTHER" MICROBIT_API_IMAGE_STATIC = "MICROBIT.API.IMAGE.STATIC" MICROBIT_API_BUTTON = "MICROBIT.API.BUTTON" + MICROBIT_API_COMPASS = "MICROBIT.API.COMPASS" + MICROBIT_API_I2C = "MICROBIT.API.I2C" + MICROBIT_API_SPI = "MICROBIT.API.SPI" diff --git a/src/adafruit_circuitplayground/test/test_utils.py b/src/common/test/test_utils.py similarity index 92% rename from src/adafruit_circuitplayground/test/test_utils.py rename to src/common/test/test_utils.py index 21a8c6296..135d96c38 100644 --- a/src/adafruit_circuitplayground/test/test_utils.py +++ b/src/common/test/test_utils.py @@ -1,8 +1,10 @@ import sys +import types +from io import StringIO from unittest import mock -from .. import constants as CONSTANTS +from common import constants as CONSTANTS from common import utils diff --git a/src/common/utils.py b/src/common/utils.py index 6a0e6953d..b8b6d7de8 100644 --- a/src/common/utils.py +++ b/src/common/utils.py @@ -47,3 +47,16 @@ def escape_if_OSX(file_name): if sys.platform == CONSTANTS.MAC_OS: file_name = file_name.replace(" ", "%20") return file_name + + +def print_for_unimplemented_functions(function_name, one_more_call=False): + # Frame 0 is this function call + # Frame 1 is the call that calls this function, which is a microbit function + # Frame 2 is the call that calls the microbit function, which is in the user's file + # If one_more_call is True, then there is another frame between what was originally supposed to be frame 1 and 2. + frame_no = 2 if not one_more_call else 3 + line_number = sys._getframe(frame_no).f_lineno + user_file_name = sys._getframe(frame_no).f_code.co_filename + print( + f"{function_name} on line {line_number} in {user_file_name} is not implemented in the simulator but it will work on the actual device!" + ) diff --git a/src/microbit/__init__.py b/src/microbit/__init__.py index e444b34f0..478fcff42 100644 --- a/src/microbit/__init__.py +++ b/src/microbit/__init__.py @@ -6,7 +6,24 @@ accelerometer = __mb.accelerometer button_a = __mb.button_a button_b = __mb.button_b +compass = __mb.compass display = __mb.display +i2c = __mb.i2c +spi = __mb.spi + + +def panic(n): + """ + Enter a panic mode. Requires restart. Pass in an arbitrary integer <= 255 to indicate a status + """ + __mb.panic(n) + + +def reset(): + """ + Restart the board. + """ + __mb.reset() def sleep(n): diff --git a/src/microbit/__model/compass.py b/src/microbit/__model/compass.py new file mode 100644 index 000000000..1bd34326d --- /dev/null +++ b/src/microbit/__model/compass.py @@ -0,0 +1,88 @@ +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class Compass: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/compass.html. + def calibrate(self): + """ + This function is not implemented in the simulator. + + Starts the calibration process. When this function is called on the physical device, an instructive message will be scrolled to the user after which they will need to rotate the device in order to draw a circle on the LED display on the actual device. + """ + utils.print_for_unimplemented_functions(Compass.calibrate.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def is_calibrated(self): + """ + This function is not implemented in the simulator. + + Returns ``True`` if the compass has been successfully calibrated, and + returns ``False`` otherwise. + """ + utils.print_for_unimplemented_functions(Compass.is_calibrated.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def clear_calibration(self): + """ + This function is not implemented in the simulator. + + Undoes the calibration, making the compass uncalibrated again. + """ + utils.print_for_unimplemented_functions(Compass.clear_calibration.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_x(self): + """ + This function is not implemented in the simulator. + + Gives the reading of the magnetic field strength on the ``x`` axis in nano + tesla, as a positive or negative integer, depending on the direction of the + field. + """ + utils.print_for_unimplemented_functions(Compass.get_x.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_y(self): + """ + This function is not implemented in the simulator. + + Gives the reading of the magnetic field strength on the ``y`` axis in nano + tesla, as a positive or negative integer, depending on the direction of the + field. + """ + utils.print_for_unimplemented_functions(Compass.get_y.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_z(self): + """ + This function is not implemented in the simulator. + + Gives the reading of the magnetic field strength on the ``z`` axis in nano + tesla, as a positive or negative integer, depending on the direction of the + field. + """ + utils.print_for_unimplemented_functions(Compass.get_z.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def heading(self): + """ + This function is not implemented in the simulator. + + Gives the compass heading, calculated from the above readings, as an + integer in the range from 0 to 360, representing the angle in degrees, + clockwise, with north as 0. + """ + utils.print_for_unimplemented_functions(Compass.heading.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_field_strength(self): + """ + This function is not implemented in the simulator. + + Returns an integer indication of the magnitude of the magnetic field around + the device in nano tesla. + """ + utils.print_for_unimplemented_functions(Compass.get_field_strength.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) diff --git a/src/microbit/__model/i2c.py b/src/microbit/__model/i2c.py new file mode 100644 index 000000000..cf044293e --- /dev/null +++ b/src/microbit/__model/i2c.py @@ -0,0 +1,51 @@ +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class I2c: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/i2c.html. + def init(self, freq=100000, sda="pin20", scl="pin19"): + """ + This function is not implemented in the simulator. + + Re-initialize peripheral with the specified clock frequency ``freq`` on the + specified ``sda`` and ``scl`` pins. + + Warning: + + Changing the I²C pins from defaults will make the accelerometer and + compass stop working, as they are connected internally to those pins. + """ + utils.print_for_unimplemented_functions(I2c.init.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) + + def scan(self): + """ + This function is not implemented in the simulator. + + Scan the bus for devices. Returns a list of 7-bit addresses corresponding + to those devices that responded to the scan. + """ + utils.print_for_unimplemented_functions(I2c.scan.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) + + def read(self, addr, n, repeat=False): + """ + This function is not implemented in the simulator. + + Read ``n`` bytes from the device with 7-bit address ``addr``. If ``repeat`` + is ``True``, no stop bit will be sent. + """ + utils.print_for_unimplemented_functions(I2c.read.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) + + def write(self, addr, buf, repeat=False): + """ + This function is not implemented in the simulator. + + Write bytes from ``buf`` to the device with 7-bit address ``addr``. If + ``repeat`` is ``True``, no stop bit will be sent. + """ + utils.print_for_unimplemented_functions(I2c.write.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) diff --git a/src/microbit/__model/microbit_model.py b/src/microbit/__model/microbit_model.py index e1bbb8971..2b711e04b 100644 --- a/src/microbit/__model/microbit_model.py +++ b/src/microbit/__model/microbit_model.py @@ -1,8 +1,12 @@ import time +from common import utils from .accelerometer import Accelerometer from .button import Button +from .compass import Compass from .display import Display +from .i2c import I2c +from .spi import SPI from . import constants as CONSTANTS @@ -12,7 +16,10 @@ def __init__(self): self.accelerometer = Accelerometer() self.button_a = Button() self.button_b = Button() + self.compass = Compass() self.display = Display() + self.i2c = I2c() + self.spi = SPI() self.__start_time = time.time() self.__temperature = 0 @@ -21,6 +28,18 @@ def __init__(self): "button_b": self.button_b, } + def panic(self, n): + # Due to the shim, there is another call frame. + utils.print_for_unimplemented_functions( + MicrobitModel.panic.__qualname__, one_more_call=True + ) + + def reset(self): + # Due to the shim, there is another call frame. + utils.print_for_unimplemented_functions( + MicrobitModel.reset.__qualname__, one_more_call=True + ) + def sleep(self, n): time.sleep(n / 1000) diff --git a/src/microbit/__model/spi.py b/src/microbit/__model/spi.py new file mode 100644 index 000000000..d614dd2f0 --- /dev/null +++ b/src/microbit/__model/spi.py @@ -0,0 +1,66 @@ +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class SPI: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/spi.html. + def init( + baudrate=1000000, bits=8, mode=0, sclk="pin13", mosi="pin15", miso="pin14" + ): + """ + This function is not implemented in the simulator. + + Initialize SPI communication with the specified parameters on the + specified ``pins``. Note that for correct communication, the parameters + have to be the same on both communicating devices. + + The ``baudrate`` defines the speed of communication. + + The ``bits`` defines the size of bytes being transmitted. Currently only + ``bits=8`` is supported. However, this may change in the future. + + The ``mode`` determines the combination of clock polarity and phase + according to the following convention, with polarity as the high order bit + and phase as the low order bit: + + Polarity (aka CPOL) 0 means that the clock is at logic value 0 when idle + and goes high (logic value 1) when active; polarity 1 means the clock is + at logic value 1 when idle and goes low (logic value 0) when active. Phase + (aka CPHA) 0 means that data is sampled on the leading edge of the clock, + and 1 means on the trailing edge. + + The ``sclk``, ``mosi`` and ``miso`` arguments specify the pins to use for + each type of signal. + """ + utils.print_for_unimplemented_functions(SPI.init.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) + + def read(self, nbytes): + """ + This function is not implemented in the simulator. + + Read at most ``nbytes``. Returns what was read. + """ + utils.print_for_unimplemented_functions(SPI.read.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) + + def write(self, buffer): + """ + This function is not implemented in the simulator. + + Write the ``buffer`` of bytes to the bus. + """ + utils.print_for_unimplemented_functions(SPI.write.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) + + def write_readinto(self, out, in_): + """ + This function is not implemented in the simulator. + + Write the ``out`` buffer to the bus and read any response into the ``in_`` + buffer. The length of the buffers should be the same. The buffers can be + the same object. + """ + utils.print_for_unimplemented_functions(SPI.write_readinto.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) From bd7b405c20ca3d8f58b86e26558cea004207f8e1 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 9 Mar 2020 16:35:54 -0700 Subject: [PATCH 02/14] removed unused imports --- src/common/test/test_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/test/test_utils.py b/src/common/test/test_utils.py index 135d96c38..11e7c8a2e 100644 --- a/src/common/test/test_utils.py +++ b/src/common/test/test_utils.py @@ -1,6 +1,4 @@ import sys -import types -from io import StringIO from unittest import mock From 50c47339839b80a7a2e3966442d152a1f34c0fc7 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 9 Mar 2020 16:48:50 -0700 Subject: [PATCH 03/14] Changed __qualname__ to __name__ to retrieve function names --- src/adafruit_circuitplayground/express.py | 12 ++++++------ src/microbit/__model/compass.py | 16 ++++++++-------- src/microbit/__model/i2c.py | 8 ++++---- src/microbit/__model/microbit_model.py | 4 ++-- src/microbit/__model/spi.py | 8 ++++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index 895cbf78d..86fe8c923 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -86,7 +86,7 @@ def tapped(self): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_TAPPED) - utils.print_for_unimplemented_functions(Express.tap.__qualname__) + utils.print_for_unimplemented_functions("tapped") @property def red_led(self): @@ -154,13 +154,13 @@ def touch_A6(self): def touch_A7(self): return self.__touch(7) - def adjust_touch_threshold(self, adjustement): + def adjust_touch_threshold(self, adjustment): """Not implemented! The CPX Simulator doesn't use capacitive touch threshold. """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_ADJUST_THRESHOLD) utils.print_for_unimplemented_functions( - Express.adjust_touch_threshold.__qualname__ + Express.adjust_touch_threshold.__name__ ) def shake(self, shake_threshold=30): @@ -194,19 +194,19 @@ def play_tone(self, frequency, duration): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_PLAY_TONE) - utils.print_for_unimplemented_functions(Express.play_tone.__qualname__) + utils.print_for_unimplemented_functions(Express.play_tone.__name__) def start_tone(self, frequency): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_START_TONE) - utils.print_for_unimplemented_functions(Express.start_tone.__qualname__) + utils.print_for_unimplemented_functions(Express.start_tone.__name__) def stop_tone(self): """ Not Implemented! """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_STOP_TONE) - utils.print_for_unimplemented_functions(Express.stop_tone.__qualname__) + utils.print_for_unimplemented_functions(Express.stop_tone.__name__) def update_state(self, new_state): for event in CONSTANTS.ALL_EXPECTED_INPUT_EVENTS: diff --git a/src/microbit/__model/compass.py b/src/microbit/__model/compass.py index 1bd34326d..d344ef528 100644 --- a/src/microbit/__model/compass.py +++ b/src/microbit/__model/compass.py @@ -11,7 +11,7 @@ def calibrate(self): Starts the calibration process. When this function is called on the physical device, an instructive message will be scrolled to the user after which they will need to rotate the device in order to draw a circle on the LED display on the actual device. """ - utils.print_for_unimplemented_functions(Compass.calibrate.__qualname__) + utils.print_for_unimplemented_functions(Compass.calibrate.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) def is_calibrated(self): @@ -21,7 +21,7 @@ def is_calibrated(self): Returns ``True`` if the compass has been successfully calibrated, and returns ``False`` otherwise. """ - utils.print_for_unimplemented_functions(Compass.is_calibrated.__qualname__) + utils.print_for_unimplemented_functions(Compass.is_calibrated.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) def clear_calibration(self): @@ -30,7 +30,7 @@ def clear_calibration(self): Undoes the calibration, making the compass uncalibrated again. """ - utils.print_for_unimplemented_functions(Compass.clear_calibration.__qualname__) + utils.print_for_unimplemented_functions(Compass.clear_calibration.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) def get_x(self): @@ -41,7 +41,7 @@ def get_x(self): tesla, as a positive or negative integer, depending on the direction of the field. """ - utils.print_for_unimplemented_functions(Compass.get_x.__qualname__) + utils.print_for_unimplemented_functions(Compass.get_x.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) def get_y(self): @@ -52,7 +52,7 @@ def get_y(self): tesla, as a positive or negative integer, depending on the direction of the field. """ - utils.print_for_unimplemented_functions(Compass.get_y.__qualname__) + utils.print_for_unimplemented_functions(Compass.get_y.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) def get_z(self): @@ -63,7 +63,7 @@ def get_z(self): tesla, as a positive or negative integer, depending on the direction of the field. """ - utils.print_for_unimplemented_functions(Compass.get_z.__qualname__) + utils.print_for_unimplemented_functions(Compass.get_z.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) def heading(self): @@ -74,7 +74,7 @@ def heading(self): integer in the range from 0 to 360, representing the angle in degrees, clockwise, with north as 0. """ - utils.print_for_unimplemented_functions(Compass.heading.__qualname__) + utils.print_for_unimplemented_functions(Compass.heading.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) def get_field_strength(self): @@ -84,5 +84,5 @@ def get_field_strength(self): Returns an integer indication of the magnitude of the magnetic field around the device in nano tesla. """ - utils.print_for_unimplemented_functions(Compass.get_field_strength.__qualname__) + utils.print_for_unimplemented_functions(Compass.get_field_strength.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) diff --git a/src/microbit/__model/i2c.py b/src/microbit/__model/i2c.py index cf044293e..8ca9c29ff 100644 --- a/src/microbit/__model/i2c.py +++ b/src/microbit/__model/i2c.py @@ -17,7 +17,7 @@ def init(self, freq=100000, sda="pin20", scl="pin19"): Changing the I²C pins from defaults will make the accelerometer and compass stop working, as they are connected internally to those pins. """ - utils.print_for_unimplemented_functions(I2c.init.__qualname__) + utils.print_for_unimplemented_functions(I2c.init.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) def scan(self): @@ -27,7 +27,7 @@ def scan(self): Scan the bus for devices. Returns a list of 7-bit addresses corresponding to those devices that responded to the scan. """ - utils.print_for_unimplemented_functions(I2c.scan.__qualname__) + utils.print_for_unimplemented_functions(I2c.scan.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) def read(self, addr, n, repeat=False): @@ -37,7 +37,7 @@ def read(self, addr, n, repeat=False): Read ``n`` bytes from the device with 7-bit address ``addr``. If ``repeat`` is ``True``, no stop bit will be sent. """ - utils.print_for_unimplemented_functions(I2c.read.__qualname__) + utils.print_for_unimplemented_functions(I2c.read.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) def write(self, addr, buf, repeat=False): @@ -47,5 +47,5 @@ def write(self, addr, buf, repeat=False): Write bytes from ``buf`` to the device with 7-bit address ``addr``. If ``repeat`` is ``True``, no stop bit will be sent. """ - utils.print_for_unimplemented_functions(I2c.write.__qualname__) + utils.print_for_unimplemented_functions(I2c.write.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) diff --git a/src/microbit/__model/microbit_model.py b/src/microbit/__model/microbit_model.py index 2b711e04b..79d3f6ae6 100644 --- a/src/microbit/__model/microbit_model.py +++ b/src/microbit/__model/microbit_model.py @@ -31,13 +31,13 @@ def __init__(self): def panic(self, n): # Due to the shim, there is another call frame. utils.print_for_unimplemented_functions( - MicrobitModel.panic.__qualname__, one_more_call=True + MicrobitModel.panic.__name__, one_more_call=True ) def reset(self): # Due to the shim, there is another call frame. utils.print_for_unimplemented_functions( - MicrobitModel.reset.__qualname__, one_more_call=True + MicrobitModel.reset.__name__, one_more_call=True ) def sleep(self, n): diff --git a/src/microbit/__model/spi.py b/src/microbit/__model/spi.py index d614dd2f0..b82b2387a 100644 --- a/src/microbit/__model/spi.py +++ b/src/microbit/__model/spi.py @@ -33,7 +33,7 @@ def init( The ``sclk``, ``mosi`` and ``miso`` arguments specify the pins to use for each type of signal. """ - utils.print_for_unimplemented_functions(SPI.init.__qualname__) + utils.print_for_unimplemented_functions(SPI.init.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) def read(self, nbytes): @@ -42,7 +42,7 @@ def read(self, nbytes): Read at most ``nbytes``. Returns what was read. """ - utils.print_for_unimplemented_functions(SPI.read.__qualname__) + utils.print_for_unimplemented_functions(SPI.read.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) def write(self, buffer): @@ -51,7 +51,7 @@ def write(self, buffer): Write the ``buffer`` of bytes to the bus. """ - utils.print_for_unimplemented_functions(SPI.write.__qualname__) + utils.print_for_unimplemented_functions(SPI.write.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) def write_readinto(self, out, in_): @@ -62,5 +62,5 @@ def write_readinto(self, out, in_): buffer. The length of the buffers should be the same. The buffers can be the same object. """ - utils.print_for_unimplemented_functions(SPI.write_readinto.__qualname__) + utils.print_for_unimplemented_functions(SPI.write_readinto.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) From 8af66f92e18718dfa48e4b524d6a8f05f5af4a62 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 9 Mar 2020 18:09:52 -0700 Subject: [PATCH 04/14] formatted with black --- src/adafruit_circuitplayground/express.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index 86fe8c923..d4af22980 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -159,9 +159,7 @@ def adjust_touch_threshold(self, adjustment): The CPX Simulator doesn't use capacitive touch threshold. """ telemetry_py.send_telemetry(TelemetryEvent.CPX_API_ADJUST_THRESHOLD) - utils.print_for_unimplemented_functions( - Express.adjust_touch_threshold.__name__ - ) + utils.print_for_unimplemented_functions(Express.adjust_touch_threshold.__name__) def shake(self, shake_threshold=30): telemetry_py.send_telemetry(TelemetryEvent.CPX_API_SHAKE) From b07f52606b13a37087fe18e99afd3502edade205 Mon Sep 17 00:00:00 2001 From: Vandy Liu Date: Thu, 12 Mar 2020 17:24:44 -0700 Subject: [PATCH 05/14] restructure everyting --- gulpfile.js | 5 +- src/common/debugger_communication_client.py | 4 +- src/debug_user_code.py | 15 +- .../test => micropython}/__init__.py | 0 src/micropython/audio.py | 2 + src/{ => micropython}/microbit/__init__.py | 0 .../microbit/__model/accelerometer.py | 260 ++-- .../microbit/__model/button.py | 0 .../microbit/__model/compass.py | 176 +-- .../microbit/__model/constants.py | 334 ++--- .../microbit/__model/display.py | 740 +++++------ src/{ => micropython}/microbit/__model/i2c.py | 102 +- .../microbit/__model/image.py | 1078 ++++++++--------- .../microbit/__model/microbit_model.py | 194 +-- .../microbit/__model/producer_property.py | 6 +- src/{ => micropython}/microbit/__model/spi.py | 132 +- src/micropython/microbit/test/__init__.py | 0 .../microbit/test/test_accelerometer.py | 208 ++-- .../microbit/test/test_button.py | 92 +- .../microbit/test/test_display.py | 370 +++--- .../microbit/test/test_image.py | 566 ++++----- .../microbit/test/test_init.py | 90 +- .../microbit/test/test_microbit_model.py | 96 +- src/micropython/music.py | 0 src/micropython/neopixel.py | 0 src/micropython/radio.py | 0 src/micropython/speech.py | 0 src/micropython/utime.py | 0 src/process_user_code.py | 15 +- src/python_constants.py | 4 +- 30 files changed, 2250 insertions(+), 2239 deletions(-) rename src/{microbit/test => micropython}/__init__.py (100%) create mode 100644 src/micropython/audio.py rename src/{ => micropython}/microbit/__init__.py (100%) rename src/{ => micropython}/microbit/__model/accelerometer.py (97%) rename src/{ => micropython}/microbit/__model/button.py (100%) rename src/{ => micropython}/microbit/__model/compass.py (97%) rename src/{ => micropython}/microbit/__model/constants.py (97%) rename src/{ => micropython}/microbit/__model/display.py (97%) rename src/{ => micropython}/microbit/__model/i2c.py (97%) rename src/{ => micropython}/microbit/__model/image.py (96%) rename src/{ => micropython}/microbit/__model/microbit_model.py (96%) rename src/{ => micropython}/microbit/__model/producer_property.py (97%) rename src/{ => micropython}/microbit/__model/spi.py (97%) create mode 100644 src/micropython/microbit/test/__init__.py rename src/{ => micropython}/microbit/test/test_accelerometer.py (97%) rename src/{ => micropython}/microbit/test/test_button.py (97%) rename src/{ => micropython}/microbit/test/test_display.py (97%) rename src/{ => micropython}/microbit/test/test_image.py (97%) rename src/{ => micropython}/microbit/test/test_init.py (96%) rename src/{ => micropython}/microbit/test/test_microbit_model.py (96%) create mode 100644 src/micropython/music.py create mode 100644 src/micropython/neopixel.py create mode 100644 src/micropython/radio.py create mode 100644 src/micropython/speech.py create mode 100644 src/micropython/utime.py diff --git a/gulpfile.js b/gulpfile.js index def76e5c3..685aabb3b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -35,8 +35,9 @@ gulp.task("clean", () => { const pythonToMove = [ "./src/adafruit_circuitplayground/*.*", - "./src/microbit/*.*", - "./src/microbit/!(test)/**/*", + "./src/micropython/*.*", + "./src/micropython/microbit/*.*", + "./src/micropython/microbit/!(test)/**/*", "./src/*.py", "./src/common/*.py", "./src/dev-requirements.txt", diff --git a/src/common/debugger_communication_client.py b/src/common/debugger_communication_client.py index 23035c6f9..49f2381b7 100644 --- a/src/common/debugger_communication_client.py +++ b/src/common/debugger_communication_client.py @@ -14,8 +14,8 @@ from adafruit_circuitplayground.express import cpx from adafruit_circuitplayground.constants import CPX -from microbit.__model.microbit_model import __mb as mb -from microbit.__model.constants import MICROBIT +from micropython.microbit.__model.microbit_model import __mb as mb +from micropython.microbit.__model.constants import MICROBIT device_dict = {CPX: cpx, MICROBIT: mb} diff --git a/src/debug_user_code.py b/src/debug_user_code.py index c7110640f..a22ed1158 100644 --- a/src/debug_user_code.py +++ b/src/debug_user_code.py @@ -11,18 +11,19 @@ # will propagate errors if dependencies aren't sufficient check_python_dependencies.check_for_dependencies() -# Insert absolute path to Adafruit library into sys.path abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) -abs_path_to_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.LIBRARY_NAME) -sys.path.insert(0, abs_path_to_lib) -# Insert absolute path to python libraries into sys.path -abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, abs_path_to_lib) +# Insert absolute path to Adafruit library for CPX into sys.path +abs_path_to_adafruit_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.ADAFRUIT_LIBRARY_NAME) +sys.path.insert(0, abs_path_to_adafruit_lib) + +# Insert absolute path to Micropython libraries for micro:bit into sys.path +abs_path_to_micropython_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.MICROPYTHON_LIBRARY_NAME) +sys.path.insert(0, abs_path_to_micropython_lib) # This import must happen after the sys.path is modified from adafruit_circuitplayground.express import cpx -from microbit.__model.microbit_model import __mb as mb +from micropython.microbit.__model.microbit_model import __mb as mb from common import debugger_communication_client diff --git a/src/microbit/test/__init__.py b/src/micropython/__init__.py similarity index 100% rename from src/microbit/test/__init__.py rename to src/micropython/__init__.py diff --git a/src/micropython/audio.py b/src/micropython/audio.py new file mode 100644 index 000000000..5caa29fab --- /dev/null +++ b/src/micropython/audio.py @@ -0,0 +1,2 @@ +def play(): + print("audio.play") \ No newline at end of file diff --git a/src/microbit/__init__.py b/src/micropython/microbit/__init__.py similarity index 100% rename from src/microbit/__init__.py rename to src/micropython/microbit/__init__.py diff --git a/src/microbit/__model/accelerometer.py b/src/micropython/microbit/__model/accelerometer.py similarity index 97% rename from src/microbit/__model/accelerometer.py rename to src/micropython/microbit/__model/accelerometer.py index 6360ce982..c70abd540 100644 --- a/src/microbit/__model/accelerometer.py +++ b/src/micropython/microbit/__model/accelerometer.py @@ -1,130 +1,130 @@ -from . import constants as CONSTANTS -from common.telemetry import telemetry_py -from common.telemetry_events import TelemetryEvent - - -class Accelerometer: - # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/accelerometer.html. - def __init__(self): - self.__x = 0 - self.__y = 0 - self.__z = 0 - self.__current_gesture = "" - self.__prev_gestures = set() - self.__gestures = [] - - def get_x(self): - """ - Get the acceleration measurement in the ``x`` axis, as a positive or - negative integer, depending on the direction. The measurement is given in - milli-g. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) - return self.__x - - def get_y(self): - """ - Get the acceleration measurement in the ``y`` axis, as a positive or - negative integer, depending on the direction. The measurement is given in - milli-g. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) - return self.__y - - def get_z(self): - """ - Get the acceleration measurement in the ``z`` axis, as a positive or - negative integer, depending on the direction. The measurement is given in - milli-g. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) - return self.__z - - def get_values(self): - """ - Get the acceleration measurements in all axes at once, as a three-element - tuple of integers ordered as X, Y, Z. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) - return (self.__x, self.__y, self.__z) - - def current_gesture(self): - """ - Return the name of the current gesture. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) - self.__add_current_gesture_to_gesture_lists() - return self.__current_gesture - - def is_gesture(self, name): - """ - Return True or False to indicate if the named gesture is currently active. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) - self.__add_current_gesture_to_gesture_lists() - if name not in CONSTANTS.GESTURES: - raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) - return name == self.__current_gesture - - def was_gesture(self, name): - """ - Return True or False to indicate if the named gesture was active since the - last [was_gesture] call. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) - self.__add_current_gesture_to_gesture_lists() - if name not in CONSTANTS.GESTURES: - raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) - was_gesture = name in self.__prev_gestures - self.__prev_gestures.clear() - return was_gesture - - def get_gestures(self): - """ - Return a tuple of the gesture history. The most recent is listed last. - Also clears the gesture history before returning. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) - self.__add_current_gesture_to_gesture_lists() - gestures = tuple(self.__gestures) - self.__gestures.clear() - return gestures - - # Helpers and Hidden Functions - - def __get_accel(self, axis): - if axis == "x": - return self.get_x() - elif axis == "y": - return self.get_y() - elif axis == "z": - return self.get_z() - - def __set_accel(self, axis, accel): - if accel < CONSTANTS.MIN_ACCELERATION or accel > CONSTANTS.MAX_ACCELERATION: - raise ValueError(CONSTANTS.INVALID_ACCEL_ERR) - if axis == "x": - self.__x = accel - elif axis == "y": - self.__y = accel - elif axis == "z": - self.__z = accel - - def __set_gesture(self, gesture): - if gesture in CONSTANTS.GESTURES: - self.__current_gesture = gesture - elif gesture == "": - self.__current_gesture = "" - else: - raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) - - def __add_current_gesture_to_gesture_lists(self): - if self.__current_gesture in CONSTANTS.GESTURES: - self.__gestures.append(self.__current_gesture) - self.__prev_gestures.add(self.__current_gesture) - - def __update(self, axis, accel): - if accel is not None: - previous_accel = self.__get_accel(axis) - if accel != previous_accel: - self.__set_accel(axis, accel) +from . import constants as CONSTANTS +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class Accelerometer: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/accelerometer.html. + def __init__(self): + self.__x = 0 + self.__y = 0 + self.__z = 0 + self.__current_gesture = "" + self.__prev_gestures = set() + self.__gestures = [] + + def get_x(self): + """ + Get the acceleration measurement in the ``x`` axis, as a positive or + negative integer, depending on the direction. The measurement is given in + milli-g. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) + return self.__x + + def get_y(self): + """ + Get the acceleration measurement in the ``y`` axis, as a positive or + negative integer, depending on the direction. The measurement is given in + milli-g. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) + return self.__y + + def get_z(self): + """ + Get the acceleration measurement in the ``z`` axis, as a positive or + negative integer, depending on the direction. The measurement is given in + milli-g. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) + return self.__z + + def get_values(self): + """ + Get the acceleration measurements in all axes at once, as a three-element + tuple of integers ordered as X, Y, Z. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) + return (self.__x, self.__y, self.__z) + + def current_gesture(self): + """ + Return the name of the current gesture. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) + self.__add_current_gesture_to_gesture_lists() + return self.__current_gesture + + def is_gesture(self, name): + """ + Return True or False to indicate if the named gesture is currently active. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) + self.__add_current_gesture_to_gesture_lists() + if name not in CONSTANTS.GESTURES: + raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) + return name == self.__current_gesture + + def was_gesture(self, name): + """ + Return True or False to indicate if the named gesture was active since the + last [was_gesture] call. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) + self.__add_current_gesture_to_gesture_lists() + if name not in CONSTANTS.GESTURES: + raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) + was_gesture = name in self.__prev_gestures + self.__prev_gestures.clear() + return was_gesture + + def get_gestures(self): + """ + Return a tuple of the gesture history. The most recent is listed last. + Also clears the gesture history before returning. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) + self.__add_current_gesture_to_gesture_lists() + gestures = tuple(self.__gestures) + self.__gestures.clear() + return gestures + + # Helpers and Hidden Functions + + def __get_accel(self, axis): + if axis == "x": + return self.get_x() + elif axis == "y": + return self.get_y() + elif axis == "z": + return self.get_z() + + def __set_accel(self, axis, accel): + if accel < CONSTANTS.MIN_ACCELERATION or accel > CONSTANTS.MAX_ACCELERATION: + raise ValueError(CONSTANTS.INVALID_ACCEL_ERR) + if axis == "x": + self.__x = accel + elif axis == "y": + self.__y = accel + elif axis == "z": + self.__z = accel + + def __set_gesture(self, gesture): + if gesture in CONSTANTS.GESTURES: + self.__current_gesture = gesture + elif gesture == "": + self.__current_gesture = "" + else: + raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) + + def __add_current_gesture_to_gesture_lists(self): + if self.__current_gesture in CONSTANTS.GESTURES: + self.__gestures.append(self.__current_gesture) + self.__prev_gestures.add(self.__current_gesture) + + def __update(self, axis, accel): + if accel is not None: + previous_accel = self.__get_accel(axis) + if accel != previous_accel: + self.__set_accel(axis, accel) diff --git a/src/microbit/__model/button.py b/src/micropython/microbit/__model/button.py similarity index 100% rename from src/microbit/__model/button.py rename to src/micropython/microbit/__model/button.py diff --git a/src/microbit/__model/compass.py b/src/micropython/microbit/__model/compass.py similarity index 97% rename from src/microbit/__model/compass.py rename to src/micropython/microbit/__model/compass.py index d344ef528..56eb43911 100644 --- a/src/microbit/__model/compass.py +++ b/src/micropython/microbit/__model/compass.py @@ -1,88 +1,88 @@ -from common import utils -from common.telemetry import telemetry_py -from common.telemetry_events import TelemetryEvent - - -class Compass: - # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/compass.html. - def calibrate(self): - """ - This function is not implemented in the simulator. - - Starts the calibration process. When this function is called on the physical device, an instructive message will be scrolled to the user after which they will need to rotate the device in order to draw a circle on the LED display on the actual device. - """ - utils.print_for_unimplemented_functions(Compass.calibrate.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) - - def is_calibrated(self): - """ - This function is not implemented in the simulator. - - Returns ``True`` if the compass has been successfully calibrated, and - returns ``False`` otherwise. - """ - utils.print_for_unimplemented_functions(Compass.is_calibrated.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) - - def clear_calibration(self): - """ - This function is not implemented in the simulator. - - Undoes the calibration, making the compass uncalibrated again. - """ - utils.print_for_unimplemented_functions(Compass.clear_calibration.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) - - def get_x(self): - """ - This function is not implemented in the simulator. - - Gives the reading of the magnetic field strength on the ``x`` axis in nano - tesla, as a positive or negative integer, depending on the direction of the - field. - """ - utils.print_for_unimplemented_functions(Compass.get_x.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) - - def get_y(self): - """ - This function is not implemented in the simulator. - - Gives the reading of the magnetic field strength on the ``y`` axis in nano - tesla, as a positive or negative integer, depending on the direction of the - field. - """ - utils.print_for_unimplemented_functions(Compass.get_y.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) - - def get_z(self): - """ - This function is not implemented in the simulator. - - Gives the reading of the magnetic field strength on the ``z`` axis in nano - tesla, as a positive or negative integer, depending on the direction of the - field. - """ - utils.print_for_unimplemented_functions(Compass.get_z.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) - - def heading(self): - """ - This function is not implemented in the simulator. - - Gives the compass heading, calculated from the above readings, as an - integer in the range from 0 to 360, representing the angle in degrees, - clockwise, with north as 0. - """ - utils.print_for_unimplemented_functions(Compass.heading.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) - - def get_field_strength(self): - """ - This function is not implemented in the simulator. - - Returns an integer indication of the magnitude of the magnetic field around - the device in nano tesla. - """ - utils.print_for_unimplemented_functions(Compass.get_field_strength.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class Compass: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/compass.html. + def calibrate(self): + """ + This function is not implemented in the simulator. + + Starts the calibration process. When this function is called on the physical device, an instructive message will be scrolled to the user after which they will need to rotate the device in order to draw a circle on the LED display on the actual device. + """ + utils.print_for_unimplemented_functions(Compass.calibrate.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def is_calibrated(self): + """ + This function is not implemented in the simulator. + + Returns ``True`` if the compass has been successfully calibrated, and + returns ``False`` otherwise. + """ + utils.print_for_unimplemented_functions(Compass.is_calibrated.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def clear_calibration(self): + """ + This function is not implemented in the simulator. + + Undoes the calibration, making the compass uncalibrated again. + """ + utils.print_for_unimplemented_functions(Compass.clear_calibration.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_x(self): + """ + This function is not implemented in the simulator. + + Gives the reading of the magnetic field strength on the ``x`` axis in nano + tesla, as a positive or negative integer, depending on the direction of the + field. + """ + utils.print_for_unimplemented_functions(Compass.get_x.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_y(self): + """ + This function is not implemented in the simulator. + + Gives the reading of the magnetic field strength on the ``y`` axis in nano + tesla, as a positive or negative integer, depending on the direction of the + field. + """ + utils.print_for_unimplemented_functions(Compass.get_y.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_z(self): + """ + This function is not implemented in the simulator. + + Gives the reading of the magnetic field strength on the ``z`` axis in nano + tesla, as a positive or negative integer, depending on the direction of the + field. + """ + utils.print_for_unimplemented_functions(Compass.get_z.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def heading(self): + """ + This function is not implemented in the simulator. + + Gives the compass heading, calculated from the above readings, as an + integer in the range from 0 to 360, representing the angle in degrees, + clockwise, with north as 0. + """ + utils.print_for_unimplemented_functions(Compass.heading.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) + + def get_field_strength(self): + """ + This function is not implemented in the simulator. + + Returns an integer indication of the magnitude of the magnetic field around + the device in nano tesla. + """ + utils.print_for_unimplemented_functions(Compass.get_field_strength.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_COMPASS) diff --git a/src/microbit/__model/constants.py b/src/micropython/microbit/__model/constants.py similarity index 97% rename from src/microbit/__model/constants.py rename to src/micropython/microbit/__model/constants.py index 141f7fcde..096099ec9 100644 --- a/src/microbit/__model/constants.py +++ b/src/micropython/microbit/__model/constants.py @@ -1,167 +1,167 @@ -MICROBIT = "micro:bit" - -# string arguments for constructor -BLANK_5X5 = "00000:00000:00000:00000:00000:" - -# pre-defined image patterns -IMAGE_PATTERNS = { - "HEART": "09090:99999:99999:09990:00900:", - "HEART_SMALL": "00000:09090:09990:00900:00000:", - "HAPPY": "00000:09090:00000:90009:09990:", - "SMILE": "00000:00000:00000:90009:09990:", - "SAD": "00000:09090:00000:09990:90009:", - "CONFUSED": "00000:09090:00000:09090:90909:", - "ANGRY": "90009:09090:00000:99999:90909:", - "ASLEEP": "00000:99099:00000:09990:00000:", - "SURPRISED": "09090:00000:00900:09090:00900:", - "SILLY": "90009:00000:99999:00909:00999:", - "FABULOUS": "99999:99099:00000:09090:09990:", - "MEH": "09090:00000:00090:00900:09000:", - "YES": "00000:00009:00090:90900:09000:", - "NO": "90009:09090:00900:09090:90009:", - "CLOCK12": "00900:00900:00900:00000:00000:", - "CLOCK11": "09000:09000:00900:00000:00000:", - "CLOCK10": "00000:99000:00900:00000:00000:", - "CLOCK9": "00000:00000:99900:00000:00000:", - "CLOCK8": "00000:00000:00900:99000:00000:", - "CLOCK7": "00000:00000:00900:09000:09000:", - "CLOCK6": "00000:00000:00900:00900:00900:", - "CLOCK5": "00000:00000:00900:00090:00090:", - "CLOCK4": "00000:00000:00900:00099:00000:", - "CLOCK3": "00000:00000:00999:00000:00000:", - "CLOCK2": "00000:00099:00900:00000:00000:", - "CLOCK1": "00090:00090:00900:00000:00000:", - "ARROW_N": "00900:09990:90909:00900:00900:", - "ARROW_NE": "00999:00099:00909:09000:90000:", - "ARROW_E": "00900:00090:99999:00090:00900:", - "ARROW_SE": "90000:09000:00909:00099:00999:", - "ARROW_S": "00900:00900:90909:09990:00900:", - "ARROW_SW": "00009:00090:90900:99000:99900:", - "ARROW_W": "00900:09000:99999:09000:00900:", - "ARROW_NW": "99900:99000:90900:00090:00009:", - "TRIANGLE": "00000:00900:09090:99999:00000:", - "TRIANGLE_LEFT": "90000:99000:90900:90090:99999:", - "CHESSBOARD": "09090:90909:09090:90909:09090:", - "DIAMOND": "00900:09090:90009:09090:00900:", - "DIAMOND_SMALL": "00000:00900:09090:00900:00000:", - "SQUARE": "99999:90009:90009:90009:99999:", - "SQUARE_SMALL": "00000:09990:09090:09990:00000:", - "RABBIT": "90900:90900:99990:99090:99990:", - "COW": "90009:90009:99999:09990:00900:", - "MUSIC_CROTCHET": "00900:00900:00900:99900:99900:", - "MUSIC_QUAVER": "00900:00990:00909:99900:99900:", - "MUSIC_QUAVERS": "09999:09009:09009:99099:99099:", - "PITCHFORK": "90909:90909:99999:00900:00900:", - "XMAS": "00900:09990:00900:09990:99999:", - "PACMAN": "09999:99090:99900:99990:09999:", - "TARGET": "00900:09990:99099:09990:00900:", - "TSHIRT": "99099:99999:09990:09990:09990:", - "ROLLERSKATE": "00099:00099:99999:99999:09090:", - "DUCK": "09900:99900:09999:09990:00000:", - "HOUSE": "00900:09990:99999:09990:09090:", - "TORTOISE": "00000:09990:99999:09090:00000:", - "BUTTERFLY": "99099:99999:00900:99999:99099:", - "STICKFIGURE": "00900:99999:00900:09090:90009:", - "GHOST": "99999:90909:99999:99999:90909:", - "SWORD": "00900:00900:00900:09990:00900:", - "GIRAFFE": "99000:09000:09000:09990:09090:", - "SKULL": "09990:90909:99999:09990:09990:", - "UMBRELLA": "09990:99999:00900:90900:09900:", - "SNAKE": "99000:99099:09090:09990:00000:", -} - -IMAGE_TUPLE_LOOKUP = { - "ALL_CLOCKS": [ - "CLOCK12", - "CLOCK11", - "CLOCK10", - "CLOCK9", - "CLOCK8", - "CLOCK7", - "CLOCK6", - "CLOCK5", - "CLOCK4", - "CLOCK3", - "CLOCK2", - "CLOCK1", - ], - "ALL_ARROWS": [ - "ARROW_N", - "ARROW_NE", - "ARROW_E", - "ARROW_SE", - "ARROW_S", - "ARROW_SW", - "ARROW_W", - "ARROW_NW", - ], -} - -# 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 -SPACE_BETWEEN_LETTERS_WIDTH = 1 -WHITESPACE_WIDTH = 3 - -# numerical LED values -LED_HEIGHT = 5 -LED_WIDTH = 5 -BRIGHTNESS_MIN = 0 -BRIGHTNESS_MAX = 9 - -# sensor max/min values -MAX_TEMPERATURE = 125 -MIN_TEMPERATURE = -55 -MAX_LIGHT_LEVEL = 255 -MIN_LIGHT_LEVEL = 0 -MAX_ACCELERATION = 1023 -MIN_ACCELERATION = -1023 - -GESTURES = set( - [ - "up", - "down", - "left", - "right", - "face up", - "face down", - "freefall", - "3g", - "6g", - "8g", - "shake", - ] -) - -# error messages -BRIGHTNESS_ERR = "brightness out of bounds" -COPY_ERR_MESSAGE = "please call copy function first" -INCORR_IMAGE_SIZE = "image data is incorrect size" -INDEX_ERR = "index out of bounds" -NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" -UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" -SAME_SIZE_ERR = "images must be the same size" -INVALID_GESTURE_ERR = "invalid gesture" -INVALID_ACCEL_ERR = "invalid acceleration" -INVALID_LIGHT_LEVEL_ERR = "invalid light level" -INVALID_TEMPERATURE_ERR = "invalid temperature" - -TIME_DELAY = 0.03 - -EXPECTED_INPUT_BUTTONS = [ - "button_a", - "button_b", -] - -EXPECTED_INPUT_ACCEL = { - "motion_x": "x", - "motion_y": "y", - "motion_z": "z", -} - -EXPECTED_INPUT_LIGHT = "light" - -EXPECTED_INPUT_TEMP = "temperature" +MICROBIT = "micro:bit" + +# string arguments for constructor +BLANK_5X5 = "00000:00000:00000:00000:00000:" + +# pre-defined image patterns +IMAGE_PATTERNS = { + "HEART": "09090:99999:99999:09990:00900:", + "HEART_SMALL": "00000:09090:09990:00900:00000:", + "HAPPY": "00000:09090:00000:90009:09990:", + "SMILE": "00000:00000:00000:90009:09990:", + "SAD": "00000:09090:00000:09990:90009:", + "CONFUSED": "00000:09090:00000:09090:90909:", + "ANGRY": "90009:09090:00000:99999:90909:", + "ASLEEP": "00000:99099:00000:09990:00000:", + "SURPRISED": "09090:00000:00900:09090:00900:", + "SILLY": "90009:00000:99999:00909:00999:", + "FABULOUS": "99999:99099:00000:09090:09990:", + "MEH": "09090:00000:00090:00900:09000:", + "YES": "00000:00009:00090:90900:09000:", + "NO": "90009:09090:00900:09090:90009:", + "CLOCK12": "00900:00900:00900:00000:00000:", + "CLOCK11": "09000:09000:00900:00000:00000:", + "CLOCK10": "00000:99000:00900:00000:00000:", + "CLOCK9": "00000:00000:99900:00000:00000:", + "CLOCK8": "00000:00000:00900:99000:00000:", + "CLOCK7": "00000:00000:00900:09000:09000:", + "CLOCK6": "00000:00000:00900:00900:00900:", + "CLOCK5": "00000:00000:00900:00090:00090:", + "CLOCK4": "00000:00000:00900:00099:00000:", + "CLOCK3": "00000:00000:00999:00000:00000:", + "CLOCK2": "00000:00099:00900:00000:00000:", + "CLOCK1": "00090:00090:00900:00000:00000:", + "ARROW_N": "00900:09990:90909:00900:00900:", + "ARROW_NE": "00999:00099:00909:09000:90000:", + "ARROW_E": "00900:00090:99999:00090:00900:", + "ARROW_SE": "90000:09000:00909:00099:00999:", + "ARROW_S": "00900:00900:90909:09990:00900:", + "ARROW_SW": "00009:00090:90900:99000:99900:", + "ARROW_W": "00900:09000:99999:09000:00900:", + "ARROW_NW": "99900:99000:90900:00090:00009:", + "TRIANGLE": "00000:00900:09090:99999:00000:", + "TRIANGLE_LEFT": "90000:99000:90900:90090:99999:", + "CHESSBOARD": "09090:90909:09090:90909:09090:", + "DIAMOND": "00900:09090:90009:09090:00900:", + "DIAMOND_SMALL": "00000:00900:09090:00900:00000:", + "SQUARE": "99999:90009:90009:90009:99999:", + "SQUARE_SMALL": "00000:09990:09090:09990:00000:", + "RABBIT": "90900:90900:99990:99090:99990:", + "COW": "90009:90009:99999:09990:00900:", + "MUSIC_CROTCHET": "00900:00900:00900:99900:99900:", + "MUSIC_QUAVER": "00900:00990:00909:99900:99900:", + "MUSIC_QUAVERS": "09999:09009:09009:99099:99099:", + "PITCHFORK": "90909:90909:99999:00900:00900:", + "XMAS": "00900:09990:00900:09990:99999:", + "PACMAN": "09999:99090:99900:99990:09999:", + "TARGET": "00900:09990:99099:09990:00900:", + "TSHIRT": "99099:99999:09990:09990:09990:", + "ROLLERSKATE": "00099:00099:99999:99999:09090:", + "DUCK": "09900:99900:09999:09990:00000:", + "HOUSE": "00900:09990:99999:09990:09090:", + "TORTOISE": "00000:09990:99999:09090:00000:", + "BUTTERFLY": "99099:99999:00900:99999:99099:", + "STICKFIGURE": "00900:99999:00900:09090:90009:", + "GHOST": "99999:90909:99999:99999:90909:", + "SWORD": "00900:00900:00900:09990:00900:", + "GIRAFFE": "99000:09000:09000:09990:09090:", + "SKULL": "09990:90909:99999:09990:09990:", + "UMBRELLA": "09990:99999:00900:90900:09900:", + "SNAKE": "99000:99099:09090:09990:00000:", +} + +IMAGE_TUPLE_LOOKUP = { + "ALL_CLOCKS": [ + "CLOCK12", + "CLOCK11", + "CLOCK10", + "CLOCK9", + "CLOCK8", + "CLOCK7", + "CLOCK6", + "CLOCK5", + "CLOCK4", + "CLOCK3", + "CLOCK2", + "CLOCK1", + ], + "ALL_ARROWS": [ + "ARROW_N", + "ARROW_NE", + "ARROW_E", + "ARROW_SE", + "ARROW_S", + "ARROW_SW", + "ARROW_W", + "ARROW_NW", + ], +} + +# 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 +SPACE_BETWEEN_LETTERS_WIDTH = 1 +WHITESPACE_WIDTH = 3 + +# numerical LED values +LED_HEIGHT = 5 +LED_WIDTH = 5 +BRIGHTNESS_MIN = 0 +BRIGHTNESS_MAX = 9 + +# sensor max/min values +MAX_TEMPERATURE = 125 +MIN_TEMPERATURE = -55 +MAX_LIGHT_LEVEL = 255 +MIN_LIGHT_LEVEL = 0 +MAX_ACCELERATION = 1023 +MIN_ACCELERATION = -1023 + +GESTURES = set( + [ + "up", + "down", + "left", + "right", + "face up", + "face down", + "freefall", + "3g", + "6g", + "8g", + "shake", + ] +) + +# error messages +BRIGHTNESS_ERR = "brightness out of bounds" +COPY_ERR_MESSAGE = "please call copy function first" +INCORR_IMAGE_SIZE = "image data is incorrect size" +INDEX_ERR = "index out of bounds" +NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" +UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" +SAME_SIZE_ERR = "images must be the same size" +INVALID_GESTURE_ERR = "invalid gesture" +INVALID_ACCEL_ERR = "invalid acceleration" +INVALID_LIGHT_LEVEL_ERR = "invalid light level" +INVALID_TEMPERATURE_ERR = "invalid temperature" + +TIME_DELAY = 0.03 + +EXPECTED_INPUT_BUTTONS = [ + "button_a", + "button_b", +] + +EXPECTED_INPUT_ACCEL = { + "motion_x": "x", + "motion_y": "y", + "motion_z": "z", +} + +EXPECTED_INPUT_LIGHT = "light" + +EXPECTED_INPUT_TEMP = "temperature" diff --git a/src/microbit/__model/display.py b/src/micropython/microbit/__model/display.py similarity index 97% rename from src/microbit/__model/display.py rename to src/micropython/microbit/__model/display.py index eee651064..1075f2c56 100644 --- a/src/microbit/__model/display.py +++ b/src/micropython/microbit/__model/display.py @@ -1,370 +1,370 @@ -import copy -import time -import threading -import common - -from common import utils -from common.telemetry import telemetry_py -from common.telemetry_events import TelemetryEvent -from . import constants as CONSTANTS -from .image import Image - - -class Display: - # The implementation based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/display.html. - - def __init__(self): - self.__image = Image() - self.__on = True - self.__light_level = 0 - self.__blank_image = Image() - - self.__current_pid = None - self.__lock = threading.Lock() - self.__debug_mode = False - - def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): - """ - Scrolls ``value`` horizontally on the display. If ``value`` is an integer or float it is - first converted to a string using ``str()``. The ``delay`` parameter controls how fast - the text is scrolling. - - If ``wait`` is ``True``, this function will block until the animation is - finished, otherwise the animation will happen in the background. - - If ``loop`` is ``True``, the animation will repeat forever. - - If ``monospace`` is ``True``, the characters will all take up 5 pixel-columns - in width, otherwise there will be exactly 1 blank pixel-column between each - character as they scroll. - - Note that the ``wait``, ``loop`` and ``monospace`` arguments must be specified - using their keyword. - """ - if not wait: - thread = threading.Thread( - target=self.scroll, args=(value, delay, True, loop, monospace) - ) - thread.start() - return - - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_SCROLL) - - # Set current_pid to the thread's identifier - self.__lock.acquire() - self.__current_pid = threading.get_ident() - self.__lock.release() - - if isinstance(value, (str, int, float)): - value = str(value) - else: - raise TypeError(f"can't convert {type(value)} object to str implicitly") - - letters = [] - for c in value: - if monospace: - letters.append(Display.__get_image_from_char(c)) - letters.append( - Image(CONSTANTS.SPACE_BETWEEN_LETTERS_WIDTH, CONSTANTS.LED_HEIGHT) - ) - else: - if c == " ": - letters.append( - Image(CONSTANTS.WHITESPACE_WIDTH, CONSTANTS.LED_HEIGHT) - ) - else: - letters.append( - Display.__strip_unlit_columns(Display.__get_image_from_char(c)) - ) - letters.append( - Image( - CONSTANTS.SPACE_BETWEEN_LETTERS_WIDTH, CONSTANTS.LED_HEIGHT, - ) - ) - appended_image = Display.__create_scroll_image(letters) - - while True: - # Show the scrolled image one square at a time. - for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): - self.__lock.acquire() - - # If show or scroll is called again, there will be a different pid and break - if self.__current_pid != threading.get_ident(): - self.__lock.release() - break - - self.__image.blit( - appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT - ) - self.__lock.release() - self.__update_client() - - Display.sleep_ms(delay) - - if not loop: - break - - def show(self, value, delay=400, wait=True, loop=False, clear=False): - """ - Display the ``image``. - - If ``value`` is a string, float or integer, display letters/digits in sequence. - Otherwise, if ``value`` is an iterable sequence of images, display these images in sequence. - Each letter, digit or image is shown with ``delay`` milliseconds between them. - - If ``wait`` is ``True``, this function will block until the animation is - finished, otherwise the animation will happen in the background. - - If ``loop`` is ``True``, the animation will repeat forever. - - If ``clear`` is ``True``, the display will be cleared after the iterable has finished. - - Note that the ``wait``, ``loop`` and ``clear`` arguments must be specified - using their keyword. - """ - if not wait: - thread = threading.Thread( - target=self.show, args=(value, delay, True, loop, clear) - ) - thread.start() - return - - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_SHOW) - - # Set current_pid to the thread's identifier - self.__lock.acquire() - self.__current_pid = threading.get_ident() - self.__lock.release() - - images = [] - use_delay = False - if isinstance(value, Image): - images.append(value.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT)) - elif isinstance(value, (str, int, float)): - chars = list(str(value)) - for c in chars: - images.append(Display.__get_image_from_char(c)) - if len(chars) > 1: - use_delay = True - else: - # Check if iterable - try: - _ = iter(value) - except TypeError as e: - raise e - - for elem in value: - if isinstance(elem, Image): - images.append( - elem.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT) - ) - elif isinstance(elem, str) and len(elem) == 1: - images.append(Display.__get_image_from_char(elem)) - # If elem is not char or image, break without iterating through rest of list - else: - break - use_delay = True - - while True: - for image in images: - self.__lock.acquire() - - # If show or scroll is called again, there will be a different pid and break - if self.__current_pid != threading.get_ident(): - self.__lock.release() - break - - self.__image = image - self.__lock.release() - self.__update_client() - - if use_delay: - Display.sleep_ms(delay) - - if not loop: - break - if clear: - self.clear() - - def get_pixel(self, x, y): - """ - Return the brightness of the LED at column ``x`` and row ``y`` as an - integer between 0 (off) and 9 (bright). - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) - self.__lock.acquire() - pixel = self.__image.get_pixel(x, y) - self.__lock.release() - return pixel - - def set_pixel(self, x, y, value): - """ - Set the brightness of the LED at column ``x`` and row ``y`` to ``value``, - which has to be an integer between 0 and 9. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) - self.__lock.acquire() - self.__image.set_pixel(x, y, value) - self.__lock.release() - self.__update_client() - - def clear(self): - """ - Set the brightness of all LEDs to 0 (off). - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) - self.__lock.acquire() - self.__image = Image() - self.__lock.release() - self.__update_client() - - def on(self): - """ - Use on() to turn on the display. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) - self.__on = True - - def off(self): - """ - Use off() to turn off the display. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) - self.__on = False - - def is_on(self): - """ - Returns ``True`` if the display is on, otherwise returns ``False``. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) - return self.__on - - def read_light_level(self): - """ - Use the display's LEDs in reverse-bias mode to sense the amount of light - falling on the display. Returns an integer between 0 and 255 representing - the light level, with larger meaning more light. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_LIGHT_LEVEL) - return self.__light_level - - def __set_light_level(self, level): - if level < CONSTANTS.MIN_LIGHT_LEVEL or level > CONSTANTS.MAX_LIGHT_LEVEL: - raise ValueError(CONSTANTS.INVALID_LIGHT_LEVEL_ERR) - else: - self.__light_level = level - - # Helpers - - def __get_array(self): - self.__lock.acquire() - if self.is_on(): - leds = copy.deepcopy(self.__image._Image__LED) - else: - leds = self.__blank_image._Image__LED - self.__lock.release() - return leds - - @staticmethod - def __get_image_from_char(c): - # If c is not between the ASCII alphabet we cover, make it a question mark - if ord(c) < CONSTANTS.ASCII_START or ord(c) > CONSTANTS.ASCII_END: - c = "?" - offset = (ord(c) - CONSTANTS.ASCII_START) * CONSTANTS.LED_WIDTH - representative_bytes = CONSTANTS.ALPHABET[ - offset : offset + CONSTANTS.LED_HEIGHT - ] - return Image(Display.__convert_bytearray_to_image_str(representative_bytes)) - - # Removes columns that are not lit - @staticmethod - def __strip_unlit_columns(image): - min_index = CONSTANTS.LED_WIDTH - 1 - max_index = 0 - for row in image._Image__LED: - for index, bit in enumerate(row): - if bit > 0: - min_index = min(min_index, index) - max_index = max(max_index, index) - return image.crop(min_index, 0, max_index - min_index + 1, CONSTANTS.LED_HEIGHT) - - # This method is different from Image's __bytes_to_array. - # This one requires a conversion from binary of the ALPHABET constant to an image. - @staticmethod - def __convert_bytearray_to_image_str(byte_array): - arr = [] - for b in byte_array: - # Convert byte to binary - b_as_bits = str(bin(b))[2:] - sub_arr = [] - while len(sub_arr) < 5: - # Iterate throught bits - # If there is a 1 at b, then the pixel at column b is lit - for bit in b_as_bits[::-1]: - if len(sub_arr) < 5: - sub_arr.insert(0, int(bit) * CONSTANTS.BRIGHTNESS_MAX) - else: - break - # Add 0s to the front until the list is 5 long - while len(sub_arr) < 5: - sub_arr.insert(0, 0) - arr.append(sub_arr) - image_str = "" - for row in arr: - for elem in row: - image_str += str(elem) - image_str += ":" - return image_str - - @staticmethod - def __insert_blank_column(image): - for row in image._Image__LED: - row.append(0) - - @staticmethod - def __create_scroll_image(images): - blank_5x5_image = Image() - front_of_scroll_image = Image(4, 5) - images.insert(0, front_of_scroll_image) - - scroll_image = Image._Image__append_images(images) - end_of_scroll_image = Image() - # Insert columns of 0s until the ending is a 5x5 blank - end_of_scroll_image.blit( - scroll_image, - scroll_image.width() - CONSTANTS.LED_WIDTH, - 0, - CONSTANTS.LED_WIDTH, - CONSTANTS.LED_HEIGHT, - ) - while not Image._Image__same_image(end_of_scroll_image, blank_5x5_image): - Display.__insert_blank_column(scroll_image) - end_of_scroll_image.blit( - scroll_image, - scroll_image.width() - CONSTANTS.LED_WIDTH, - 0, - CONSTANTS.LED_WIDTH, - CONSTANTS.LED_HEIGHT, - ) - - return scroll_image - - def __update_client(self): - sendable_json = {"leds": self.__get_array()} - - if self.__debug_mode: - common.debugger_communication_client.debug_send_to_simulator( - sendable_json, CONSTANTS.MICROBIT - ) - else: - common.utils.send_to_simulator(sendable_json, CONSTANTS.MICROBIT) - - def __update_light_level(self, new_light_level): - if new_light_level is not None: - previous_light_level = self.read_light_level() - if new_light_level != previous_light_level: - self.__set_light_level(new_light_level) - - @staticmethod - def sleep_ms(ms): - time.sleep(ms / 1000) +import copy +import time +import threading +import common + +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent +from . import constants as CONSTANTS +from .image import Image + + +class Display: + # The implementation based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/display.html. + + def __init__(self): + self.__image = Image() + self.__on = True + self.__light_level = 0 + self.__blank_image = Image() + + self.__current_pid = None + self.__lock = threading.Lock() + self.__debug_mode = False + + def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): + """ + Scrolls ``value`` horizontally on the display. If ``value`` is an integer or float it is + first converted to a string using ``str()``. The ``delay`` parameter controls how fast + the text is scrolling. + + If ``wait`` is ``True``, this function will block until the animation is + finished, otherwise the animation will happen in the background. + + If ``loop`` is ``True``, the animation will repeat forever. + + If ``monospace`` is ``True``, the characters will all take up 5 pixel-columns + in width, otherwise there will be exactly 1 blank pixel-column between each + character as they scroll. + + Note that the ``wait``, ``loop`` and ``monospace`` arguments must be specified + using their keyword. + """ + if not wait: + thread = threading.Thread( + target=self.scroll, args=(value, delay, True, loop, monospace) + ) + thread.start() + return + + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_SCROLL) + + # Set current_pid to the thread's identifier + self.__lock.acquire() + self.__current_pid = threading.get_ident() + self.__lock.release() + + if isinstance(value, (str, int, float)): + value = str(value) + else: + raise TypeError(f"can't convert {type(value)} object to str implicitly") + + letters = [] + for c in value: + if monospace: + letters.append(Display.__get_image_from_char(c)) + letters.append( + Image(CONSTANTS.SPACE_BETWEEN_LETTERS_WIDTH, CONSTANTS.LED_HEIGHT) + ) + else: + if c == " ": + letters.append( + Image(CONSTANTS.WHITESPACE_WIDTH, CONSTANTS.LED_HEIGHT) + ) + else: + letters.append( + Display.__strip_unlit_columns(Display.__get_image_from_char(c)) + ) + letters.append( + Image( + CONSTANTS.SPACE_BETWEEN_LETTERS_WIDTH, CONSTANTS.LED_HEIGHT, + ) + ) + appended_image = Display.__create_scroll_image(letters) + + while True: + # Show the scrolled image one square at a time. + for x in range(appended_image.width() - CONSTANTS.LED_WIDTH + 1): + self.__lock.acquire() + + # If show or scroll is called again, there will be a different pid and break + if self.__current_pid != threading.get_ident(): + self.__lock.release() + break + + self.__image.blit( + appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT + ) + self.__lock.release() + self.__update_client() + + Display.sleep_ms(delay) + + if not loop: + break + + def show(self, value, delay=400, wait=True, loop=False, clear=False): + """ + Display the ``image``. + + If ``value`` is a string, float or integer, display letters/digits in sequence. + Otherwise, if ``value`` is an iterable sequence of images, display these images in sequence. + Each letter, digit or image is shown with ``delay`` milliseconds between them. + + If ``wait`` is ``True``, this function will block until the animation is + finished, otherwise the animation will happen in the background. + + If ``loop`` is ``True``, the animation will repeat forever. + + If ``clear`` is ``True``, the display will be cleared after the iterable has finished. + + Note that the ``wait``, ``loop`` and ``clear`` arguments must be specified + using their keyword. + """ + if not wait: + thread = threading.Thread( + target=self.show, args=(value, delay, True, loop, clear) + ) + thread.start() + return + + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_SHOW) + + # Set current_pid to the thread's identifier + self.__lock.acquire() + self.__current_pid = threading.get_ident() + self.__lock.release() + + images = [] + use_delay = False + if isinstance(value, Image): + images.append(value.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT)) + elif isinstance(value, (str, int, float)): + chars = list(str(value)) + for c in chars: + images.append(Display.__get_image_from_char(c)) + if len(chars) > 1: + use_delay = True + else: + # Check if iterable + try: + _ = iter(value) + except TypeError as e: + raise e + + for elem in value: + if isinstance(elem, Image): + images.append( + elem.crop(0, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT) + ) + elif isinstance(elem, str) and len(elem) == 1: + images.append(Display.__get_image_from_char(elem)) + # If elem is not char or image, break without iterating through rest of list + else: + break + use_delay = True + + while True: + for image in images: + self.__lock.acquire() + + # If show or scroll is called again, there will be a different pid and break + if self.__current_pid != threading.get_ident(): + self.__lock.release() + break + + self.__image = image + self.__lock.release() + self.__update_client() + + if use_delay: + Display.sleep_ms(delay) + + if not loop: + break + if clear: + self.clear() + + def get_pixel(self, x, y): + """ + Return the brightness of the LED at column ``x`` and row ``y`` as an + integer between 0 (off) and 9 (bright). + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) + self.__lock.acquire() + pixel = self.__image.get_pixel(x, y) + self.__lock.release() + return pixel + + def set_pixel(self, x, y, value): + """ + Set the brightness of the LED at column ``x`` and row ``y`` to ``value``, + which has to be an integer between 0 and 9. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) + self.__lock.acquire() + self.__image.set_pixel(x, y, value) + self.__lock.release() + self.__update_client() + + def clear(self): + """ + Set the brightness of all LEDs to 0 (off). + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) + self.__lock.acquire() + self.__image = Image() + self.__lock.release() + self.__update_client() + + def on(self): + """ + Use on() to turn on the display. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) + self.__on = True + + def off(self): + """ + Use off() to turn off the display. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) + self.__on = False + + def is_on(self): + """ + Returns ``True`` if the display is on, otherwise returns ``False``. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) + return self.__on + + def read_light_level(self): + """ + Use the display's LEDs in reverse-bias mode to sense the amount of light + falling on the display. Returns an integer between 0 and 255 representing + the light level, with larger meaning more light. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_LIGHT_LEVEL) + return self.__light_level + + def __set_light_level(self, level): + if level < CONSTANTS.MIN_LIGHT_LEVEL or level > CONSTANTS.MAX_LIGHT_LEVEL: + raise ValueError(CONSTANTS.INVALID_LIGHT_LEVEL_ERR) + else: + self.__light_level = level + + # Helpers + + def __get_array(self): + self.__lock.acquire() + if self.is_on(): + leds = copy.deepcopy(self.__image._Image__LED) + else: + leds = self.__blank_image._Image__LED + self.__lock.release() + return leds + + @staticmethod + def __get_image_from_char(c): + # If c is not between the ASCII alphabet we cover, make it a question mark + if ord(c) < CONSTANTS.ASCII_START or ord(c) > CONSTANTS.ASCII_END: + c = "?" + offset = (ord(c) - CONSTANTS.ASCII_START) * CONSTANTS.LED_WIDTH + representative_bytes = CONSTANTS.ALPHABET[ + offset : offset + CONSTANTS.LED_HEIGHT + ] + return Image(Display.__convert_bytearray_to_image_str(representative_bytes)) + + # Removes columns that are not lit + @staticmethod + def __strip_unlit_columns(image): + min_index = CONSTANTS.LED_WIDTH - 1 + max_index = 0 + for row in image._Image__LED: + for index, bit in enumerate(row): + if bit > 0: + min_index = min(min_index, index) + max_index = max(max_index, index) + return image.crop(min_index, 0, max_index - min_index + 1, CONSTANTS.LED_HEIGHT) + + # This method is different from Image's __bytes_to_array. + # This one requires a conversion from binary of the ALPHABET constant to an image. + @staticmethod + def __convert_bytearray_to_image_str(byte_array): + arr = [] + for b in byte_array: + # Convert byte to binary + b_as_bits = str(bin(b))[2:] + sub_arr = [] + while len(sub_arr) < 5: + # Iterate throught bits + # If there is a 1 at b, then the pixel at column b is lit + for bit in b_as_bits[::-1]: + if len(sub_arr) < 5: + sub_arr.insert(0, int(bit) * CONSTANTS.BRIGHTNESS_MAX) + else: + break + # Add 0s to the front until the list is 5 long + while len(sub_arr) < 5: + sub_arr.insert(0, 0) + arr.append(sub_arr) + image_str = "" + for row in arr: + for elem in row: + image_str += str(elem) + image_str += ":" + return image_str + + @staticmethod + def __insert_blank_column(image): + for row in image._Image__LED: + row.append(0) + + @staticmethod + def __create_scroll_image(images): + blank_5x5_image = Image() + front_of_scroll_image = Image(4, 5) + images.insert(0, front_of_scroll_image) + + scroll_image = Image._Image__append_images(images) + end_of_scroll_image = Image() + # Insert columns of 0s until the ending is a 5x5 blank + end_of_scroll_image.blit( + scroll_image, + scroll_image.width() - CONSTANTS.LED_WIDTH, + 0, + CONSTANTS.LED_WIDTH, + CONSTANTS.LED_HEIGHT, + ) + while not Image._Image__same_image(end_of_scroll_image, blank_5x5_image): + Display.__insert_blank_column(scroll_image) + end_of_scroll_image.blit( + scroll_image, + scroll_image.width() - CONSTANTS.LED_WIDTH, + 0, + CONSTANTS.LED_WIDTH, + CONSTANTS.LED_HEIGHT, + ) + + return scroll_image + + def __update_client(self): + sendable_json = {"leds": self.__get_array()} + + if self.__debug_mode: + common.debugger_communication_client.debug_send_to_simulator( + sendable_json, CONSTANTS.MICROBIT + ) + else: + common.utils.send_to_simulator(sendable_json, CONSTANTS.MICROBIT) + + def __update_light_level(self, new_light_level): + if new_light_level is not None: + previous_light_level = self.read_light_level() + if new_light_level != previous_light_level: + self.__set_light_level(new_light_level) + + @staticmethod + def sleep_ms(ms): + time.sleep(ms / 1000) diff --git a/src/microbit/__model/i2c.py b/src/micropython/microbit/__model/i2c.py similarity index 97% rename from src/microbit/__model/i2c.py rename to src/micropython/microbit/__model/i2c.py index 8ca9c29ff..64739cd64 100644 --- a/src/microbit/__model/i2c.py +++ b/src/micropython/microbit/__model/i2c.py @@ -1,51 +1,51 @@ -from common import utils -from common.telemetry import telemetry_py -from common.telemetry_events import TelemetryEvent - - -class I2c: - # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/i2c.html. - def init(self, freq=100000, sda="pin20", scl="pin19"): - """ - This function is not implemented in the simulator. - - Re-initialize peripheral with the specified clock frequency ``freq`` on the - specified ``sda`` and ``scl`` pins. - - Warning: - - Changing the I²C pins from defaults will make the accelerometer and - compass stop working, as they are connected internally to those pins. - """ - utils.print_for_unimplemented_functions(I2c.init.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) - - def scan(self): - """ - This function is not implemented in the simulator. - - Scan the bus for devices. Returns a list of 7-bit addresses corresponding - to those devices that responded to the scan. - """ - utils.print_for_unimplemented_functions(I2c.scan.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) - - def read(self, addr, n, repeat=False): - """ - This function is not implemented in the simulator. - - Read ``n`` bytes from the device with 7-bit address ``addr``. If ``repeat`` - is ``True``, no stop bit will be sent. - """ - utils.print_for_unimplemented_functions(I2c.read.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) - - def write(self, addr, buf, repeat=False): - """ - This function is not implemented in the simulator. - - Write bytes from ``buf`` to the device with 7-bit address ``addr``. If - ``repeat`` is ``True``, no stop bit will be sent. - """ - utils.print_for_unimplemented_functions(I2c.write.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class I2c: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/i2c.html. + def init(self, freq=100000, sda="pin20", scl="pin19"): + """ + This function is not implemented in the simulator. + + Re-initialize peripheral with the specified clock frequency ``freq`` on the + specified ``sda`` and ``scl`` pins. + + Warning: + + Changing the I²C pins from defaults will make the accelerometer and + compass stop working, as they are connected internally to those pins. + """ + utils.print_for_unimplemented_functions(I2c.init.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) + + def scan(self): + """ + This function is not implemented in the simulator. + + Scan the bus for devices. Returns a list of 7-bit addresses corresponding + to those devices that responded to the scan. + """ + utils.print_for_unimplemented_functions(I2c.scan.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) + + def read(self, addr, n, repeat=False): + """ + This function is not implemented in the simulator. + + Read ``n`` bytes from the device with 7-bit address ``addr``. If ``repeat`` + is ``True``, no stop bit will be sent. + """ + utils.print_for_unimplemented_functions(I2c.read.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) + + def write(self, addr, buf, repeat=False): + """ + This function is not implemented in the simulator. + + Write bytes from ``buf`` to the device with 7-bit address ``addr``. If + ``repeat`` is ``True``, no stop bit will be sent. + """ + utils.print_for_unimplemented_functions(I2c.write.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) diff --git a/src/microbit/__model/image.py b/src/micropython/microbit/__model/image.py similarity index 96% rename from src/microbit/__model/image.py rename to src/micropython/microbit/__model/image.py index 391e17e1e..a525ea6e1 100644 --- a/src/microbit/__model/image.py +++ b/src/micropython/microbit/__model/image.py @@ -1,539 +1,539 @@ -from . import constants as CONSTANTS -from .producer_property import ProducerProperty -from common.telemetry import telemetry_py -from common.telemetry_events import TelemetryEvent - - -class Image: - """ - If ``string`` is used, it has to consist of digits 0-9 arranged into - lines, describing the image, for example:: - - image = Image("90009:" - "09090:" - "00900:" - "09090:" - "90009") - - will create a 5×5 image of an X. The end of a line is indicated by a colon. - It's also possible to use a newline (\\n) to indicate the end of a line - like this:: - - image = Image("90009\\n" - "09090\\n" - "00900\\n" - "09090\\n" - "90009") - - The other form creates an empty image with ``width`` columns and - ``height`` rows. Optionally ``buffer`` can be an array of - ``width``×``height`` integers in range 0-9 to initialize the image:: - - Image(2, 2, b'\x08\x08\x08\x08') - - or:: - - Image(2, 2, bytearray([9,9,9,9])) - - Will create a 2 x 2 pixel image at full brightness. - - .. note:: - - Keyword arguments cannot be passed to ``buffer``. - """ - - # Attributes assigned (to functions) later; - # having this here helps the pylint. - HEART = None - HEART_SMALL = None - HAPPY = None - SMILE = None - SAD = None - CONFUSED = None - ANGRY = None - ASLEEP = None - SURPRISED = None - SILLY = None - FABULOUS = None - MEH = None - YES = None - NO = None - CLOCK12 = None - CLOCK11 = None - CLOCK10 = None - CLOCK9 = None - CLOCK8 = None - CLOCK7 = None - CLOCK6 = None - CLOCK5 = None - CLOCK4 = None - CLOCK3 = None - CLOCK2 = None - CLOCK1 = None - ARROW_N = None - ARROW_NE = None - ARROW_E = None - ARROW_SE = None - ARROW_S = None - ARROW_SW = None - ARROW_W = None - ARROW_NW = None - TRIANGLE = None - TRIANGLE_LEFT = None - CHESSBOARD = None - DIAMOND = None - DIAMOND_SMALL = None - SQUARE = None - SQUARE_SMALL = None - RABBIT = None - COW = None - MUSIC_CROTCHET = None - MUSIC_QUAVER = None - MUSIC_QUAVERS = None - PITCHFORK = None - XMAS = None - PACMAN = None - TARGET = None - TSHIRT = None - ROLLERSKATE = None - DUCK = None - HOUSE = None - TORTOISE = None - BUTTERFLY = None - STICKFIGURE = None - GHOST = None - SWORD = None - GIRAFFE = None - SKULL = None - UMBRELLA = None - SNAKE = None - ALL_CLOCKS = None - ALL_ARROWS = None - - # implementing image model as described here: - # https://microbit-micropython.readthedocs.io/en/v1.0.1/image.html - - def __init__(self, *args, **kwargs): - # Depending on the number of arguments - # in constructor, it treat args differently. - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_CREATION) - if len(args) == 0: - # default constructor - self.__LED = self.__string_to_square_array(CONSTANTS.BLANK_5X5) - elif len(args) == 1: - pattern = args[0] - if isinstance(pattern, str): - self.__LED = self.__string_to_square_array(pattern) - else: - raise TypeError("Image(s) takes a string") - else: - - width = args[0] - height = args[1] - - if width < 0 or height < 0: - # This is not in original, but ideally, - # image should fail non-silently - raise ValueError(CONSTANTS.INDEX_ERR) - - if len(args) == 3: - # This option is for potential third bytearray arguments - byte_arr = args[2] - self.__LED = self.__bytes_to_array(width, height, byte_arr) - else: - self.__LED = self.__create_leds(width, height) - self.read_only = False - - def width(self): - """ - Return the number of columns in the image. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - if len(self.__LED) > 0: - return len(self.__LED[0]) - else: - return 0 - - def height(self): - """ - Return the numbers of rows in the image. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - return len(self.__LED) - - def set_pixel(self, x, y, value): - """ - Set the brightness of the pixel at column ``x`` and row ``y`` to the - ``value``, which has to be between 0 (dark) and 9 (bright). - - This method will raise an exception when called on any of the built-in - read-only images, like ``Image.HEART``. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - if self.read_only: - raise TypeError(CONSTANTS.COPY_ERR_MESSAGE) - elif 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 - - def get_pixel(self, x, y): - """ - Return the brightness of pixel at column ``x`` and row ``y`` as an - integer between 0 and 9. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - if self.__valid_pos(x, y): - return self.__LED[y][x] - else: - raise ValueError(CONSTANTS.INDEX_ERR) - - def shift_up(self, n): - """ - Return a new image created by shifting the picture up by ``n`` rows. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - return self.__shift_vertical(-n) - - def shift_down(self, n): - """ - Return a new image created by shifting the picture down by ``n`` rows. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - return self.__shift_vertical(n) - - def shift_right(self, n): - """ - Return a new image created by shifting the picture right by ``n`` - columns. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - return self.__shift_horizontal(n) - - def shift_left(self, n): - """ - Return a new image created by shifting the picture left by ``n`` - columns. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - return self.__shift_horizontal(-n) - - def crop(self, x, y, w, h): - """ - Return a new image by cropping the picture to a width of ``w`` and a - height of ``h``, starting with the pixel at column ``x`` and row ``y``. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - res = Image(w, h) - res.blit(self, x, y, w, h) - return res - - def copy(self): - """ - Return an exact copy of the image. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - return Image(self.__create_string()) - - # This inverts the brightness of each LED. - # ie: Pixel that is at brightness 4 would become brightness 5 - # and pixel that is at brightness 9 would become brightness 0. - def invert(self): - """ - Return a new image by inverting the brightness of the pixels in the - source image. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - for y in range(self.height()): - for x in range(self.width()): - self.set_pixel(x, y, CONSTANTS.BRIGHTNESS_MAX - self.get_pixel(x, y)) - - # This fills all LEDs with same brightness. - def fill(self, value): - """ - Set the brightness of all the pixels in the image to the - ``value``, which has to be between 0 (dark) and 9 (bright). - - This method will raise an exception when called on any of the built-in - read-only images, like ``Image.HEART``. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - for y in range(self.height()): - for x in range(self.width()): - self.set_pixel(x, y, value) - - # This transposes a certain area (w x h) on src onto the current image. - def blit(self, src, x, y, w, h, xdest=0, ydest=0): - """ - Copy the rectangle defined by ``x``, ``y``, ``w``, ``h`` from the image ``src`` into - this image at ``xdest``, ``ydest``. - Areas in the source rectangle, but outside the source image are treated as having a value of 0. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - if not src.__valid_pos(x, y): - raise ValueError(CONSTANTS.INDEX_ERR) - - for count_y in range(h): - for count_x in range(w): - if self.__valid_pos(xdest + count_x, ydest + count_y): - if src.__valid_pos(x + count_x, y + count_y): - transfer_pixel = src.get_pixel(x + count_x, y + count_y) - else: - transfer_pixel = 0 - self.set_pixel(xdest + count_x, ydest + count_y, transfer_pixel) - - # This adds two images (if other object is not an image, throws error). - # The images must be the same size. - def __add__(self, other): - """ - Create a new image by adding the brightness values from the two images for each pixel. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - if not isinstance(other, Image): - raise TypeError( - CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(self)}', '{type(other)}'" - ) - elif not (other.height() == self.height() and other.width() == self.width()): - raise ValueError(CONSTANTS.SAME_SIZE_ERR) - else: - res = Image(self.width(), self.height()) - - for y in range(self.height()): - for x in range(self.width()): - sum_value = other.get_pixel(x, y) + self.get_pixel(x, y) - display_result = min(CONSTANTS.BRIGHTNESS_MAX, sum_value) - res.set_pixel(x, y, display_result) - - return res - - # This multiplies image by number (if other factor is not a number, it throws an error). - def __mul__(self, other): - """ - Create a new image by multiplying the brightness of each pixel by n. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - try: - float_val = float(other) - except TypeError: - raise TypeError(f"can't convert {type(other)} to float") - - res = Image(self.width(), self.height()) - - for y in range(self.height()): - for x in range(self.width()): - product = self.get_pixel(x, y) * float_val - res.set_pixel(x, y, min(CONSTANTS.BRIGHTNESS_MAX, product)) - - return res - - def __repr__(self): - """ - Get a compact string representation of the image. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - 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): - """ - Get a readable string representation of the image. - """ - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) - 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 - # width w and height h - def __create_leds(self, w, h): - arr = [] - for _ in range(h): - sub_arr = [] - for _ in range(w): - sub_arr.append(0) - arr.append(sub_arr) - - return arr - - # This turns byte array to 2D array for LED field. - def __bytes_to_array(self, width, height, 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 index != 0: - arr.append(sub_arr) - sub_arr = [] - - sub_arr.append(elem) - - arr.append(sub_arr) - return arr - - # This converts string (with different rows separated by ":") - # to 2d array arrangement. - def __string_to_square_array(self, pattern): - initial_array, max_subarray_len = self.__string_directly_to_array(pattern) - - # Fill in empty spaces in w x h matrix. - for arr_y in initial_array: - num_extra_spaces = max_subarray_len - len(arr_y) - for _ in range(num_extra_spaces): - arr_y.append(0) - - return initial_array - - def __string_directly_to_array(self, pattern): - # The result may have spaces in the 2D array - # and may uneven sub-array lengths - arr = [] - sub_arr = [] - - max_subarray_len = 0 - - for elem in pattern: - if elem == ":" or elem == "\n": - if len(sub_arr) > max_subarray_len: - max_subarray_len = len(sub_arr) - arr.append(sub_arr) - sub_arr = [] - else: - sub_arr.append(int(elem)) - - if ( - len(pattern) > 0 - and not str(pattern)[-1] == ":" - and not str(pattern)[-1] == "\n" - and len(sub_arr) != 0 - ): - if len(sub_arr) > max_subarray_len: - max_subarray_len = len(sub_arr) - arr.append(sub_arr) - - return arr, max_subarray_len - - def __valid_brightness(self, value): - return value >= CONSTANTS.BRIGHTNESS_MIN and value <= CONSTANTS.BRIGHTNESS_MAX - - def __valid_pos(self, x, y): - return x >= 0 and x < self.width() and y >= 0 and y < self.height() - - def __shift_vertical(self, n): - res = Image(self.width(), self.height()) - - if n > 0: - # down - res.blit(self, 0, 0, self.width(), self.height() - n, 0, n) - else: - # up - if self.__valid_pos(0, abs(n)): - res.blit(self, 0, abs(n), self.width(), self.height() - abs(n), 0, 0) - - 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 - if self.__valid_pos(abs(n), 0): - res.blit(self, abs(n), 0, self.width() - abs(n), self.height(), 0, 0) - - return res - - def __create_string(self): - ret_str = "" - for index_y in range(self.height()): - ret_str += self.__row_to_str(index_y) - return ret_str - - def __row_to_str(self, y): - new_str = "" - for x in range(self.width()): - new_str += str(self.get_pixel(x, y)) - - new_str += ":" - - return new_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 - - @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 -# that return a new read-only Image -def create_const_func(func_name): - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_STATIC) - - def func(*args): - const_instance = Image(CONSTANTS.IMAGE_PATTERNS[func_name]) - const_instance.read_only = True - return const_instance - - func.__name__ = func_name - return ProducerProperty(func) - - -# for attributes like Image.ALL_CLOCKS -# that return tuples -def create_const_list_func(func_name): - def func(*args): - collection_names = CONSTANTS.IMAGE_TUPLE_LOOKUP[func_name] - ret_list = [] - for image_name in collection_names: - const_instance = Image(CONSTANTS.IMAGE_PATTERNS[image_name]) - const_instance.read_only = True - ret_list.append(const_instance) - - return tuple(ret_list) - - func.__name__ = func_name - return ProducerProperty(func) - - -for name in CONSTANTS.IMAGE_PATTERNS.keys(): - setattr(Image, name, create_const_func(name)) - -for name in CONSTANTS.IMAGE_TUPLE_LOOKUP.keys(): - setattr(Image, name, create_const_list_func(name)) +from . import constants as CONSTANTS +from .producer_property import ProducerProperty +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class Image: + """ + If ``string`` is used, it has to consist of digits 0-9 arranged into + lines, describing the image, for example:: + + image = Image("90009:" + "09090:" + "00900:" + "09090:" + "90009") + + will create a 5×5 image of an X. The end of a line is indicated by a colon. + It's also possible to use a newline (\\n) to indicate the end of a line + like this:: + + image = Image("90009\\n" + "09090\\n" + "00900\\n" + "09090\\n" + "90009") + + The other form creates an empty image with ``width`` columns and + ``height`` rows. Optionally ``buffer`` can be an array of + ``width``×``height`` integers in range 0-9 to initialize the image:: + + Image(2, 2, b'\x08\x08\x08\x08') + + or:: + + Image(2, 2, bytearray([9,9,9,9])) + + Will create a 2 x 2 pixel image at full brightness. + + .. note:: + + Keyword arguments cannot be passed to ``buffer``. + """ + + # Attributes assigned (to functions) later; + # having this here helps the pylint. + HEART = None + HEART_SMALL = None + HAPPY = None + SMILE = None + SAD = None + CONFUSED = None + ANGRY = None + ASLEEP = None + SURPRISED = None + SILLY = None + FABULOUS = None + MEH = None + YES = None + NO = None + CLOCK12 = None + CLOCK11 = None + CLOCK10 = None + CLOCK9 = None + CLOCK8 = None + CLOCK7 = None + CLOCK6 = None + CLOCK5 = None + CLOCK4 = None + CLOCK3 = None + CLOCK2 = None + CLOCK1 = None + ARROW_N = None + ARROW_NE = None + ARROW_E = None + ARROW_SE = None + ARROW_S = None + ARROW_SW = None + ARROW_W = None + ARROW_NW = None + TRIANGLE = None + TRIANGLE_LEFT = None + CHESSBOARD = None + DIAMOND = None + DIAMOND_SMALL = None + SQUARE = None + SQUARE_SMALL = None + RABBIT = None + COW = None + MUSIC_CROTCHET = None + MUSIC_QUAVER = None + MUSIC_QUAVERS = None + PITCHFORK = None + XMAS = None + PACMAN = None + TARGET = None + TSHIRT = None + ROLLERSKATE = None + DUCK = None + HOUSE = None + TORTOISE = None + BUTTERFLY = None + STICKFIGURE = None + GHOST = None + SWORD = None + GIRAFFE = None + SKULL = None + UMBRELLA = None + SNAKE = None + ALL_CLOCKS = None + ALL_ARROWS = None + + # implementing image model as described here: + # https://microbit-micropython.readthedocs.io/en/v1.0.1/image.html + + def __init__(self, *args, **kwargs): + # Depending on the number of arguments + # in constructor, it treat args differently. + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_CREATION) + if len(args) == 0: + # default constructor + self.__LED = self.__string_to_square_array(CONSTANTS.BLANK_5X5) + elif len(args) == 1: + pattern = args[0] + if isinstance(pattern, str): + self.__LED = self.__string_to_square_array(pattern) + else: + raise TypeError("Image(s) takes a string") + else: + + width = args[0] + height = args[1] + + if width < 0 or height < 0: + # This is not in original, but ideally, + # image should fail non-silently + raise ValueError(CONSTANTS.INDEX_ERR) + + if len(args) == 3: + # This option is for potential third bytearray arguments + byte_arr = args[2] + self.__LED = self.__bytes_to_array(width, height, byte_arr) + else: + self.__LED = self.__create_leds(width, height) + self.read_only = False + + def width(self): + """ + Return the number of columns in the image. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + if len(self.__LED) > 0: + return len(self.__LED[0]) + else: + return 0 + + def height(self): + """ + Return the numbers of rows in the image. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + return len(self.__LED) + + def set_pixel(self, x, y, value): + """ + Set the brightness of the pixel at column ``x`` and row ``y`` to the + ``value``, which has to be between 0 (dark) and 9 (bright). + + This method will raise an exception when called on any of the built-in + read-only images, like ``Image.HEART``. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + if self.read_only: + raise TypeError(CONSTANTS.COPY_ERR_MESSAGE) + elif 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 + + def get_pixel(self, x, y): + """ + Return the brightness of pixel at column ``x`` and row ``y`` as an + integer between 0 and 9. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + if self.__valid_pos(x, y): + return self.__LED[y][x] + else: + raise ValueError(CONSTANTS.INDEX_ERR) + + def shift_up(self, n): + """ + Return a new image created by shifting the picture up by ``n`` rows. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + return self.__shift_vertical(-n) + + def shift_down(self, n): + """ + Return a new image created by shifting the picture down by ``n`` rows. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + return self.__shift_vertical(n) + + def shift_right(self, n): + """ + Return a new image created by shifting the picture right by ``n`` + columns. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + return self.__shift_horizontal(n) + + def shift_left(self, n): + """ + Return a new image created by shifting the picture left by ``n`` + columns. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + return self.__shift_horizontal(-n) + + def crop(self, x, y, w, h): + """ + Return a new image by cropping the picture to a width of ``w`` and a + height of ``h``, starting with the pixel at column ``x`` and row ``y``. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + res = Image(w, h) + res.blit(self, x, y, w, h) + return res + + def copy(self): + """ + Return an exact copy of the image. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + return Image(self.__create_string()) + + # This inverts the brightness of each LED. + # ie: Pixel that is at brightness 4 would become brightness 5 + # and pixel that is at brightness 9 would become brightness 0. + def invert(self): + """ + Return a new image by inverting the brightness of the pixels in the + source image. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + for y in range(self.height()): + for x in range(self.width()): + self.set_pixel(x, y, CONSTANTS.BRIGHTNESS_MAX - self.get_pixel(x, y)) + + # This fills all LEDs with same brightness. + def fill(self, value): + """ + Set the brightness of all the pixels in the image to the + ``value``, which has to be between 0 (dark) and 9 (bright). + + This method will raise an exception when called on any of the built-in + read-only images, like ``Image.HEART``. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + for y in range(self.height()): + for x in range(self.width()): + self.set_pixel(x, y, value) + + # This transposes a certain area (w x h) on src onto the current image. + def blit(self, src, x, y, w, h, xdest=0, ydest=0): + """ + Copy the rectangle defined by ``x``, ``y``, ``w``, ``h`` from the image ``src`` into + this image at ``xdest``, ``ydest``. + Areas in the source rectangle, but outside the source image are treated as having a value of 0. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + if not src.__valid_pos(x, y): + raise ValueError(CONSTANTS.INDEX_ERR) + + for count_y in range(h): + for count_x in range(w): + if self.__valid_pos(xdest + count_x, ydest + count_y): + if src.__valid_pos(x + count_x, y + count_y): + transfer_pixel = src.get_pixel(x + count_x, y + count_y) + else: + transfer_pixel = 0 + self.set_pixel(xdest + count_x, ydest + count_y, transfer_pixel) + + # This adds two images (if other object is not an image, throws error). + # The images must be the same size. + def __add__(self, other): + """ + Create a new image by adding the brightness values from the two images for each pixel. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + if not isinstance(other, Image): + raise TypeError( + CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(self)}', '{type(other)}'" + ) + elif not (other.height() == self.height() and other.width() == self.width()): + raise ValueError(CONSTANTS.SAME_SIZE_ERR) + else: + res = Image(self.width(), self.height()) + + for y in range(self.height()): + for x in range(self.width()): + sum_value = other.get_pixel(x, y) + self.get_pixel(x, y) + display_result = min(CONSTANTS.BRIGHTNESS_MAX, sum_value) + res.set_pixel(x, y, display_result) + + return res + + # This multiplies image by number (if other factor is not a number, it throws an error). + def __mul__(self, other): + """ + Create a new image by multiplying the brightness of each pixel by n. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + try: + float_val = float(other) + except TypeError: + raise TypeError(f"can't convert {type(other)} to float") + + res = Image(self.width(), self.height()) + + for y in range(self.height()): + for x in range(self.width()): + product = self.get_pixel(x, y) * float_val + res.set_pixel(x, y, min(CONSTANTS.BRIGHTNESS_MAX, product)) + + return res + + def __repr__(self): + """ + Get a compact string representation of the image. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + 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): + """ + Get a readable string representation of the image. + """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) + 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 + # width w and height h + def __create_leds(self, w, h): + arr = [] + for _ in range(h): + sub_arr = [] + for _ in range(w): + sub_arr.append(0) + arr.append(sub_arr) + + return arr + + # This turns byte array to 2D array for LED field. + def __bytes_to_array(self, width, height, 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 index != 0: + arr.append(sub_arr) + sub_arr = [] + + sub_arr.append(elem) + + arr.append(sub_arr) + return arr + + # This converts string (with different rows separated by ":") + # to 2d array arrangement. + def __string_to_square_array(self, pattern): + initial_array, max_subarray_len = self.__string_directly_to_array(pattern) + + # Fill in empty spaces in w x h matrix. + for arr_y in initial_array: + num_extra_spaces = max_subarray_len - len(arr_y) + for _ in range(num_extra_spaces): + arr_y.append(0) + + return initial_array + + def __string_directly_to_array(self, pattern): + # The result may have spaces in the 2D array + # and may uneven sub-array lengths + arr = [] + sub_arr = [] + + max_subarray_len = 0 + + for elem in pattern: + if elem == ":" or elem == "\n": + if len(sub_arr) > max_subarray_len: + max_subarray_len = len(sub_arr) + arr.append(sub_arr) + sub_arr = [] + else: + sub_arr.append(int(elem)) + + if ( + len(pattern) > 0 + and not str(pattern)[-1] == ":" + and not str(pattern)[-1] == "\n" + and len(sub_arr) != 0 + ): + if len(sub_arr) > max_subarray_len: + max_subarray_len = len(sub_arr) + arr.append(sub_arr) + + return arr, max_subarray_len + + def __valid_brightness(self, value): + return value >= CONSTANTS.BRIGHTNESS_MIN and value <= CONSTANTS.BRIGHTNESS_MAX + + def __valid_pos(self, x, y): + return x >= 0 and x < self.width() and y >= 0 and y < self.height() + + def __shift_vertical(self, n): + res = Image(self.width(), self.height()) + + if n > 0: + # down + res.blit(self, 0, 0, self.width(), self.height() - n, 0, n) + else: + # up + if self.__valid_pos(0, abs(n)): + res.blit(self, 0, abs(n), self.width(), self.height() - abs(n), 0, 0) + + 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 + if self.__valid_pos(abs(n), 0): + res.blit(self, abs(n), 0, self.width() - abs(n), self.height(), 0, 0) + + return res + + def __create_string(self): + ret_str = "" + for index_y in range(self.height()): + ret_str += self.__row_to_str(index_y) + return ret_str + + def __row_to_str(self, y): + new_str = "" + for x in range(self.width()): + new_str += str(self.get_pixel(x, y)) + + new_str += ":" + + return new_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 + + @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 +# that return a new read-only Image +def create_const_func(func_name): + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_STATIC) + + def func(*args): + const_instance = Image(CONSTANTS.IMAGE_PATTERNS[func_name]) + const_instance.read_only = True + return const_instance + + func.__name__ = func_name + return ProducerProperty(func) + + +# for attributes like Image.ALL_CLOCKS +# that return tuples +def create_const_list_func(func_name): + def func(*args): + collection_names = CONSTANTS.IMAGE_TUPLE_LOOKUP[func_name] + ret_list = [] + for image_name in collection_names: + const_instance = Image(CONSTANTS.IMAGE_PATTERNS[image_name]) + const_instance.read_only = True + ret_list.append(const_instance) + + return tuple(ret_list) + + func.__name__ = func_name + return ProducerProperty(func) + + +for name in CONSTANTS.IMAGE_PATTERNS.keys(): + setattr(Image, name, create_const_func(name)) + +for name in CONSTANTS.IMAGE_TUPLE_LOOKUP.keys(): + setattr(Image, name, create_const_list_func(name)) diff --git a/src/microbit/__model/microbit_model.py b/src/micropython/microbit/__model/microbit_model.py similarity index 96% rename from src/microbit/__model/microbit_model.py rename to src/micropython/microbit/__model/microbit_model.py index 79d3f6ae6..638563e8c 100644 --- a/src/microbit/__model/microbit_model.py +++ b/src/micropython/microbit/__model/microbit_model.py @@ -1,97 +1,97 @@ -import time - -from common import utils -from .accelerometer import Accelerometer -from .button import Button -from .compass import Compass -from .display import Display -from .i2c import I2c -from .spi import SPI -from . import constants as CONSTANTS - - -class MicrobitModel: - def __init__(self): - # State in the Python process - self.accelerometer = Accelerometer() - self.button_a = Button() - self.button_b = Button() - self.compass = Compass() - self.display = Display() - self.i2c = I2c() - self.spi = SPI() - - self.__start_time = time.time() - self.__temperature = 0 - self.__microbit_button_dict = { - "button_a": self.button_a, - "button_b": self.button_b, - } - - def panic(self, n): - # Due to the shim, there is another call frame. - utils.print_for_unimplemented_functions( - MicrobitModel.panic.__name__, one_more_call=True - ) - - def reset(self): - # Due to the shim, there is another call frame. - utils.print_for_unimplemented_functions( - MicrobitModel.reset.__name__, one_more_call=True - ) - - def sleep(self, n): - time.sleep(n / 1000) - - def running_time(self): - print(f"time. time: {time.time()}") - return time.time() - self.__start_time - - def temperature(self): - return self.__temperature - - def __set_temperature(self, temperature): - if ( - temperature < CONSTANTS.MIN_TEMPERATURE - or temperature > CONSTANTS.MAX_TEMPERATURE - ): - raise ValueError(CONSTANTS.INVALID_TEMPERATURE_ERR) - else: - self.__temperature = temperature - - def update_state(self, new_state): - self.__update_buttons(new_state) - self.__update_motion(new_state) - self.__update_light(new_state) - self.__update_temp(new_state) - - # helpers - def __update_buttons(self, new_state): - # get button pushes - for button_name in CONSTANTS.EXPECTED_INPUT_BUTTONS: - button = self.__microbit_button_dict[button_name] - button._Button__update(new_state.get(button_name)) - - def __update_motion(self, new_state): - # set motion_x, motion_y, motion_z - for name, direction in CONSTANTS.EXPECTED_INPUT_ACCEL.items(): - self.accelerometer._Accelerometer__update(direction, new_state.get(name)) - - def __update_light(self, new_state): - # set light level - new_light_level = new_state.get(CONSTANTS.EXPECTED_INPUT_LIGHT) - self.display._Display__update_light_level(new_light_level) - - def __update_temp(self, new_state): - # set temperature - new_temp = new_state.get(CONSTANTS.EXPECTED_INPUT_TEMP) - if new_temp is not None: - previous_temp = self.temperature() - if new_temp != previous_temp: - self._MicrobitModel__set_temperature(new_temp) - - def __set_debug_mode(self, mode): - self.display._Display__debug_mode = mode - - -__mb = MicrobitModel() +import time + +from common import utils +from .accelerometer import Accelerometer +from .button import Button +from .compass import Compass +from .display import Display +from .i2c import I2c +from .spi import SPI +from . import constants as CONSTANTS + + +class MicrobitModel: + def __init__(self): + # State in the Python process + self.accelerometer = Accelerometer() + self.button_a = Button() + self.button_b = Button() + self.compass = Compass() + self.display = Display() + self.i2c = I2c() + self.spi = SPI() + + self.__start_time = time.time() + self.__temperature = 0 + self.__microbit_button_dict = { + "button_a": self.button_a, + "button_b": self.button_b, + } + + def panic(self, n): + # Due to the shim, there is another call frame. + utils.print_for_unimplemented_functions( + MicrobitModel.panic.__name__, one_more_call=True + ) + + def reset(self): + # Due to the shim, there is another call frame. + utils.print_for_unimplemented_functions( + MicrobitModel.reset.__name__, one_more_call=True + ) + + def sleep(self, n): + time.sleep(n / 1000) + + def running_time(self): + print(f"time. time: {time.time()}") + return time.time() - self.__start_time + + def temperature(self): + return self.__temperature + + def __set_temperature(self, temperature): + if ( + temperature < CONSTANTS.MIN_TEMPERATURE + or temperature > CONSTANTS.MAX_TEMPERATURE + ): + raise ValueError(CONSTANTS.INVALID_TEMPERATURE_ERR) + else: + self.__temperature = temperature + + def update_state(self, new_state): + self.__update_buttons(new_state) + self.__update_motion(new_state) + self.__update_light(new_state) + self.__update_temp(new_state) + + # helpers + def __update_buttons(self, new_state): + # get button pushes + for button_name in CONSTANTS.EXPECTED_INPUT_BUTTONS: + button = self.__microbit_button_dict[button_name] + button._Button__update(new_state.get(button_name)) + + def __update_motion(self, new_state): + # set motion_x, motion_y, motion_z + for name, direction in CONSTANTS.EXPECTED_INPUT_ACCEL.items(): + self.accelerometer._Accelerometer__update(direction, new_state.get(name)) + + def __update_light(self, new_state): + # set light level + new_light_level = new_state.get(CONSTANTS.EXPECTED_INPUT_LIGHT) + self.display._Display__update_light_level(new_light_level) + + def __update_temp(self, new_state): + # set temperature + new_temp = new_state.get(CONSTANTS.EXPECTED_INPUT_TEMP) + if new_temp is not None: + previous_temp = self.temperature() + if new_temp != previous_temp: + self._MicrobitModel__set_temperature(new_temp) + + def __set_debug_mode(self, mode): + self.display._Display__debug_mode = mode + + +__mb = MicrobitModel() diff --git a/src/microbit/__model/producer_property.py b/src/micropython/microbit/__model/producer_property.py similarity index 97% rename from src/microbit/__model/producer_property.py rename to src/micropython/microbit/__model/producer_property.py index 6a6ed593a..f67f23261 100644 --- a/src/microbit/__model/producer_property.py +++ b/src/micropython/microbit/__model/producer_property.py @@ -1,3 +1,3 @@ -class ProducerProperty(property): - def __get__(self, cls, owner): - return classmethod(self.fget).__get__(cls, owner)() +class ProducerProperty(property): + def __get__(self, cls, owner): + return classmethod(self.fget).__get__(cls, owner)() diff --git a/src/microbit/__model/spi.py b/src/micropython/microbit/__model/spi.py similarity index 97% rename from src/microbit/__model/spi.py rename to src/micropython/microbit/__model/spi.py index b82b2387a..836e3859c 100644 --- a/src/microbit/__model/spi.py +++ b/src/micropython/microbit/__model/spi.py @@ -1,66 +1,66 @@ -from common import utils -from common.telemetry import telemetry_py -from common.telemetry_events import TelemetryEvent - - -class SPI: - # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/spi.html. - def init( - baudrate=1000000, bits=8, mode=0, sclk="pin13", mosi="pin15", miso="pin14" - ): - """ - This function is not implemented in the simulator. - - Initialize SPI communication with the specified parameters on the - specified ``pins``. Note that for correct communication, the parameters - have to be the same on both communicating devices. - - The ``baudrate`` defines the speed of communication. - - The ``bits`` defines the size of bytes being transmitted. Currently only - ``bits=8`` is supported. However, this may change in the future. - - The ``mode`` determines the combination of clock polarity and phase - according to the following convention, with polarity as the high order bit - and phase as the low order bit: - - Polarity (aka CPOL) 0 means that the clock is at logic value 0 when idle - and goes high (logic value 1) when active; polarity 1 means the clock is - at logic value 1 when idle and goes low (logic value 0) when active. Phase - (aka CPHA) 0 means that data is sampled on the leading edge of the clock, - and 1 means on the trailing edge. - - The ``sclk``, ``mosi`` and ``miso`` arguments specify the pins to use for - each type of signal. - """ - utils.print_for_unimplemented_functions(SPI.init.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) - - def read(self, nbytes): - """ - This function is not implemented in the simulator. - - Read at most ``nbytes``. Returns what was read. - """ - utils.print_for_unimplemented_functions(SPI.read.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) - - def write(self, buffer): - """ - This function is not implemented in the simulator. - - Write the ``buffer`` of bytes to the bus. - """ - utils.print_for_unimplemented_functions(SPI.write.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) - - def write_readinto(self, out, in_): - """ - This function is not implemented in the simulator. - - Write the ``out`` buffer to the bus and read any response into the ``in_`` - buffer. The length of the buffers should be the same. The buffers can be - the same object. - """ - utils.print_for_unimplemented_functions(SPI.write_readinto.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +class SPI: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/spi.html. + def init( + baudrate=1000000, bits=8, mode=0, sclk="pin13", mosi="pin15", miso="pin14" + ): + """ + This function is not implemented in the simulator. + + Initialize SPI communication with the specified parameters on the + specified ``pins``. Note that for correct communication, the parameters + have to be the same on both communicating devices. + + The ``baudrate`` defines the speed of communication. + + The ``bits`` defines the size of bytes being transmitted. Currently only + ``bits=8`` is supported. However, this may change in the future. + + The ``mode`` determines the combination of clock polarity and phase + according to the following convention, with polarity as the high order bit + and phase as the low order bit: + + Polarity (aka CPOL) 0 means that the clock is at logic value 0 when idle + and goes high (logic value 1) when active; polarity 1 means the clock is + at logic value 1 when idle and goes low (logic value 0) when active. Phase + (aka CPHA) 0 means that data is sampled on the leading edge of the clock, + and 1 means on the trailing edge. + + The ``sclk``, ``mosi`` and ``miso`` arguments specify the pins to use for + each type of signal. + """ + utils.print_for_unimplemented_functions(SPI.init.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) + + def read(self, nbytes): + """ + This function is not implemented in the simulator. + + Read at most ``nbytes``. Returns what was read. + """ + utils.print_for_unimplemented_functions(SPI.read.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) + + def write(self, buffer): + """ + This function is not implemented in the simulator. + + Write the ``buffer`` of bytes to the bus. + """ + utils.print_for_unimplemented_functions(SPI.write.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) + + def write_readinto(self, out, in_): + """ + This function is not implemented in the simulator. + + Write the ``out`` buffer to the bus and read any response into the ``in_`` + buffer. The length of the buffers should be the same. The buffers can be + the same object. + """ + utils.print_for_unimplemented_functions(SPI.write_readinto.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) diff --git a/src/micropython/microbit/test/__init__.py b/src/micropython/microbit/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/microbit/test/test_accelerometer.py b/src/micropython/microbit/test/test_accelerometer.py similarity index 97% rename from src/microbit/test/test_accelerometer.py rename to src/micropython/microbit/test/test_accelerometer.py index 0e654727c..241f63df1 100644 --- a/src/microbit/test/test_accelerometer.py +++ b/src/micropython/microbit/test/test_accelerometer.py @@ -1,104 +1,104 @@ -import pytest -from unittest import mock - -from ..__model.accelerometer import Accelerometer -from ..__model import constants as CONSTANTS - - -class TestAccelerometer(object): - def setup_method(self): - self.accelerometer = Accelerometer() - - @pytest.mark.parametrize( - "accel", - [ - CONSTANTS.MIN_ACCELERATION, - CONSTANTS.MIN_ACCELERATION + 1, - 100, - CONSTANTS.MAX_ACCELERATION - 1, - CONSTANTS.MAX_ACCELERATION, - ], - ) - def test_x_y_z(self, accel): - self.accelerometer._Accelerometer__set_accel("x", accel) - assert accel == self.accelerometer.get_x() - self.accelerometer._Accelerometer__set_accel("y", accel) - assert accel == self.accelerometer.get_y() - self.accelerometer._Accelerometer__set_accel("z", accel) - assert accel == self.accelerometer.get_z() - - @pytest.mark.parametrize("axis", ["x", "y", "z"]) - def test_x_y_z_invalid_accel(self, axis): - with pytest.raises(ValueError): - self.accelerometer._Accelerometer__set_accel( - axis, CONSTANTS.MAX_ACCELERATION + 1 - ) - with pytest.raises(ValueError): - self.accelerometer._Accelerometer__set_accel( - axis, CONSTANTS.MIN_ACCELERATION - 1 - ) - - @pytest.mark.parametrize( - "accels", - [ - (23, 25, 26), - (204, 234, -534), - (CONSTANTS.MIN_ACCELERATION + 10, 234, CONSTANTS.MAX_ACCELERATION), - ], - ) - def test_get_values(self, accels): - self.accelerometer._Accelerometer__set_accel("x", accels[0]) - self.accelerometer._Accelerometer__set_accel("y", accels[1]) - self.accelerometer._Accelerometer__set_accel("z", accels[2]) - assert accels == self.accelerometer.get_values() - - @pytest.mark.parametrize("gesture", ["up", "face down", "freefall", "8g"]) - def test_current_gesture(self, gesture): - self.accelerometer._Accelerometer__set_gesture(gesture) - assert gesture == self.accelerometer.current_gesture() - - @pytest.mark.parametrize("gesture", ["up", "face down", "freefall", "8g"]) - def test_is_gesture(self, gesture): - self.accelerometer._Accelerometer__set_gesture(gesture) - assert self.accelerometer.is_gesture(gesture) - for g in CONSTANTS.GESTURES: - if g != gesture: - assert not self.accelerometer.is_gesture(g) - - def test_is_gesture_error(self): - with pytest.raises(ValueError): - self.accelerometer.is_gesture("sideways") - - def test_was_gesture(self): - mock_gesture_up = "up" - mock_gesture_down = "down" - - assert not self.accelerometer.was_gesture(mock_gesture_up) - self.accelerometer._Accelerometer__set_gesture(mock_gesture_up) - self.accelerometer.current_gesture() # Call is needed for gesture detection so it can be added to the lists. - self.accelerometer._Accelerometer__set_gesture("") - assert self.accelerometer.was_gesture(mock_gesture_up) - assert not self.accelerometer.was_gesture(mock_gesture_up) - - def test_was_gesture_error(self): - with pytest.raises(ValueError): - self.accelerometer.was_gesture("sideways") - - def test_get_gestures(self): - mock_gesture_up = "up" - mock_gesture_down = "down" - mock_gesture_freefall = "freefall" - - self.accelerometer._Accelerometer__set_gesture(mock_gesture_up) - self.accelerometer.current_gesture() # Call is needed for gesture detection so it can be added to the lists. - self.accelerometer._Accelerometer__set_gesture(mock_gesture_down) - self.accelerometer.current_gesture() - self.accelerometer._Accelerometer__set_gesture(mock_gesture_freefall) - self.accelerometer.current_gesture() - self.accelerometer._Accelerometer__set_gesture("") - assert ( - mock_gesture_up, - mock_gesture_down, - mock_gesture_freefall, - ) == self.accelerometer.get_gestures() - assert () == self.accelerometer.get_gestures() +import pytest +from unittest import mock + +from ..__model.accelerometer import Accelerometer +from ..__model import constants as CONSTANTS + + +class TestAccelerometer(object): + def setup_method(self): + self.accelerometer = Accelerometer() + + @pytest.mark.parametrize( + "accel", + [ + CONSTANTS.MIN_ACCELERATION, + CONSTANTS.MIN_ACCELERATION + 1, + 100, + CONSTANTS.MAX_ACCELERATION - 1, + CONSTANTS.MAX_ACCELERATION, + ], + ) + def test_x_y_z(self, accel): + self.accelerometer._Accelerometer__set_accel("x", accel) + assert accel == self.accelerometer.get_x() + self.accelerometer._Accelerometer__set_accel("y", accel) + assert accel == self.accelerometer.get_y() + self.accelerometer._Accelerometer__set_accel("z", accel) + assert accel == self.accelerometer.get_z() + + @pytest.mark.parametrize("axis", ["x", "y", "z"]) + def test_x_y_z_invalid_accel(self, axis): + with pytest.raises(ValueError): + self.accelerometer._Accelerometer__set_accel( + axis, CONSTANTS.MAX_ACCELERATION + 1 + ) + with pytest.raises(ValueError): + self.accelerometer._Accelerometer__set_accel( + axis, CONSTANTS.MIN_ACCELERATION - 1 + ) + + @pytest.mark.parametrize( + "accels", + [ + (23, 25, 26), + (204, 234, -534), + (CONSTANTS.MIN_ACCELERATION + 10, 234, CONSTANTS.MAX_ACCELERATION), + ], + ) + def test_get_values(self, accels): + self.accelerometer._Accelerometer__set_accel("x", accels[0]) + self.accelerometer._Accelerometer__set_accel("y", accels[1]) + self.accelerometer._Accelerometer__set_accel("z", accels[2]) + assert accels == self.accelerometer.get_values() + + @pytest.mark.parametrize("gesture", ["up", "face down", "freefall", "8g"]) + def test_current_gesture(self, gesture): + self.accelerometer._Accelerometer__set_gesture(gesture) + assert gesture == self.accelerometer.current_gesture() + + @pytest.mark.parametrize("gesture", ["up", "face down", "freefall", "8g"]) + def test_is_gesture(self, gesture): + self.accelerometer._Accelerometer__set_gesture(gesture) + assert self.accelerometer.is_gesture(gesture) + for g in CONSTANTS.GESTURES: + if g != gesture: + assert not self.accelerometer.is_gesture(g) + + def test_is_gesture_error(self): + with pytest.raises(ValueError): + self.accelerometer.is_gesture("sideways") + + def test_was_gesture(self): + mock_gesture_up = "up" + mock_gesture_down = "down" + + assert not self.accelerometer.was_gesture(mock_gesture_up) + self.accelerometer._Accelerometer__set_gesture(mock_gesture_up) + self.accelerometer.current_gesture() # Call is needed for gesture detection so it can be added to the lists. + self.accelerometer._Accelerometer__set_gesture("") + assert self.accelerometer.was_gesture(mock_gesture_up) + assert not self.accelerometer.was_gesture(mock_gesture_up) + + def test_was_gesture_error(self): + with pytest.raises(ValueError): + self.accelerometer.was_gesture("sideways") + + def test_get_gestures(self): + mock_gesture_up = "up" + mock_gesture_down = "down" + mock_gesture_freefall = "freefall" + + self.accelerometer._Accelerometer__set_gesture(mock_gesture_up) + self.accelerometer.current_gesture() # Call is needed for gesture detection so it can be added to the lists. + self.accelerometer._Accelerometer__set_gesture(mock_gesture_down) + self.accelerometer.current_gesture() + self.accelerometer._Accelerometer__set_gesture(mock_gesture_freefall) + self.accelerometer.current_gesture() + self.accelerometer._Accelerometer__set_gesture("") + assert ( + mock_gesture_up, + mock_gesture_down, + mock_gesture_freefall, + ) == self.accelerometer.get_gestures() + assert () == self.accelerometer.get_gestures() diff --git a/src/microbit/test/test_button.py b/src/micropython/microbit/test/test_button.py similarity index 97% rename from src/microbit/test/test_button.py rename to src/micropython/microbit/test/test_button.py index 9e0b3cbcb..31e9d6720 100644 --- a/src/microbit/test/test_button.py +++ b/src/micropython/microbit/test/test_button.py @@ -1,46 +1,46 @@ -import pytest -from ..__model.button import Button - - -class TestButton(object): - def setup_method(self): - self.button = Button() - - def test_press_down(self): - self.button._Button__press_down() - assert self.button._Button__presses == 1 - assert self.button._Button__pressed - assert self.button._Button__prev_pressed - self.button._Button__press_down() - assert self.button._Button__presses == 2 - assert self.button._Button__pressed - assert self.button._Button__prev_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 - - def test_is_pressed(self): - assert not self.button.is_pressed() - self.button._Button__press_down() - assert self.button.is_pressed() - - def test_was_pressed(self): - assert not self.button.was_pressed() - self.button._Button__press_down() - self.button._Button__release() - assert self.button.was_pressed() - # Button resets __prev_pressed after was_pressed() is called. - assert not self.button.was_pressed() - - @pytest.mark.parametrize("presses", [1, 2, 4]) - def test_get_presses(self, presses): - assert 0 == self.button.get_presses() - for i in range(presses): - self.button._Button__press_down() - self.button._Button__release() - assert presses == self.button.get_presses() - # Presses is reset to 0 after get_presses() is called. - assert 0 == self.button.get_presses() +import pytest +from ..__model.button import Button + + +class TestButton(object): + def setup_method(self): + self.button = Button() + + def test_press_down(self): + self.button._Button__press_down() + assert self.button._Button__presses == 1 + assert self.button._Button__pressed + assert self.button._Button__prev_pressed + self.button._Button__press_down() + assert self.button._Button__presses == 2 + assert self.button._Button__pressed + assert self.button._Button__prev_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 + + def test_is_pressed(self): + assert not self.button.is_pressed() + self.button._Button__press_down() + assert self.button.is_pressed() + + def test_was_pressed(self): + assert not self.button.was_pressed() + self.button._Button__press_down() + self.button._Button__release() + assert self.button.was_pressed() + # Button resets __prev_pressed after was_pressed() is called. + assert not self.button.was_pressed() + + @pytest.mark.parametrize("presses", [1, 2, 4]) + def test_get_presses(self, presses): + assert 0 == self.button.get_presses() + for i in range(presses): + self.button._Button__press_down() + self.button._Button__release() + assert presses == self.button.get_presses() + # Presses is reset to 0 after get_presses() is called. + assert 0 == self.button.get_presses() diff --git a/src/microbit/test/test_display.py b/src/micropython/microbit/test/test_display.py similarity index 97% rename from src/microbit/test/test_display.py rename to src/micropython/microbit/test/test_display.py index 06cc6609e..1055c8870 100644 --- a/src/microbit/test/test_display.py +++ b/src/micropython/microbit/test/test_display.py @@ -1,185 +1,185 @@ -import pytest -import threading -from unittest import mock -from common import utils - -from ..__model import constants as CONSTANTS -from ..__model.display import Display -from ..__model.image import Image - - -STR_A = "09900:90090:99990:90090:90090" -STR_QUESTION_MARK = "09990:90009:00990:00000:00900" -STR_EXCLAMATION_MARK = "09000:09000:09000:00000:09000:" -STR_SIX = "00090:00900:09990:90009:09990" - - -class TestDisplay(object): - def setup_method(self): - self.display = Display() - utils.send_to_simulator = mock.Mock() - - @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) - 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)]) - 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, 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.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_off(self): - self.display.on() - assert self.display.is_on() - self.display.off() - assert not self.display.is_on() - - def test_show_one_image(self): - 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, delay=0) - assert Image._Image__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, delay=0) - assert Image._Image__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, delay=0) - assert Image._Image__same_image(expected, self.display._Display__image) - - @pytest.mark.parametrize( - "value, expected_str", - [ - ("!", STR_EXCLAMATION_MARK), - ("A", STR_A), - (" ", CONSTANTS.BLANK_5X5), - (6, STR_SIX), - ("\x7F", STR_QUESTION_MARK), # Character is out of our ASCII range - ], - ) - def test_show_char(self, value, expected_str): - expected = Image(expected_str) - 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, 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, delay=0) - assert Image._Image__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("a b") - self.__is_clear() - - 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", delay=0, wait=False) - threading.Thread.assert_called_once() - - def test_scroll_threaded(self): - threading.Thread = mock.Mock() - self.display.scroll("test", delay=0, 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], - ] - - # 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("MMMMMMMMMMMMMM", delay=100, wait=False) - self.display.show("6", delay=0) - assert Image._Image__same_image(Image(STR_SIX), self.display._Display__image) - - @pytest.mark.parametrize( - "light_level", - [ - CONSTANTS.MIN_LIGHT_LEVEL, - CONSTANTS.MIN_LIGHT_LEVEL + 1, - 100, - CONSTANTS.MAX_LIGHT_LEVEL - 1, - CONSTANTS.MAX_LIGHT_LEVEL, - ], - ) - def test_light_level(self, light_level): - self.display._Display__set_light_level(light_level) - assert light_level == self.display.read_light_level() - - @pytest.mark.parametrize( - "invalid_light_level", - [CONSTANTS.MIN_LIGHT_LEVEL - 1, CONSTANTS.MAX_LIGHT_LEVEL + 1], - ) - def test_invalid_light_level(self, invalid_light_level): - with pytest.raises(ValueError): - self.display._Display__set_light_level(invalid_light_level) - - # Helpers - def __is_clear(self): - i = Image(CONSTANTS.BLANK_5X5) - return Image._Image__same_image(i, self.display._Display__image) +import pytest +import threading +from unittest import mock +from common import utils + +from ..__model import constants as CONSTANTS +from ..__model.display import Display +from ..__model.image import Image + + +STR_A = "09900:90090:99990:90090:90090" +STR_QUESTION_MARK = "09990:90009:00990:00000:00900" +STR_EXCLAMATION_MARK = "09000:09000:09000:00000:09000:" +STR_SIX = "00090:00900:09990:90009:09990" + + +class TestDisplay(object): + def setup_method(self): + self.display = Display() + utils.send_to_simulator = mock.Mock() + + @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) + 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)]) + 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, 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.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_off(self): + self.display.on() + assert self.display.is_on() + self.display.off() + assert not self.display.is_on() + + def test_show_one_image(self): + 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, delay=0) + assert Image._Image__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, delay=0) + assert Image._Image__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, delay=0) + assert Image._Image__same_image(expected, self.display._Display__image) + + @pytest.mark.parametrize( + "value, expected_str", + [ + ("!", STR_EXCLAMATION_MARK), + ("A", STR_A), + (" ", CONSTANTS.BLANK_5X5), + (6, STR_SIX), + ("\x7F", STR_QUESTION_MARK), # Character is out of our ASCII range + ], + ) + def test_show_char(self, value, expected_str): + expected = Image(expected_str) + 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, 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, delay=0) + assert Image._Image__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("a b") + self.__is_clear() + + 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", delay=0, wait=False) + threading.Thread.assert_called_once() + + def test_scroll_threaded(self): + threading.Thread = mock.Mock() + self.display.scroll("test", delay=0, 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], + ] + + # 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("MMMMMMMMMMMMMM", delay=100, wait=False) + self.display.show("6", delay=0) + assert Image._Image__same_image(Image(STR_SIX), self.display._Display__image) + + @pytest.mark.parametrize( + "light_level", + [ + CONSTANTS.MIN_LIGHT_LEVEL, + CONSTANTS.MIN_LIGHT_LEVEL + 1, + 100, + CONSTANTS.MAX_LIGHT_LEVEL - 1, + CONSTANTS.MAX_LIGHT_LEVEL, + ], + ) + def test_light_level(self, light_level): + self.display._Display__set_light_level(light_level) + assert light_level == self.display.read_light_level() + + @pytest.mark.parametrize( + "invalid_light_level", + [CONSTANTS.MIN_LIGHT_LEVEL - 1, CONSTANTS.MAX_LIGHT_LEVEL + 1], + ) + def test_invalid_light_level(self, invalid_light_level): + with pytest.raises(ValueError): + self.display._Display__set_light_level(invalid_light_level) + + # Helpers + def __is_clear(self): + i = Image(CONSTANTS.BLANK_5X5) + return Image._Image__same_image(i, self.display._Display__image) diff --git a/src/microbit/test/test_image.py b/src/micropython/microbit/test/test_image.py similarity index 97% rename from src/microbit/test/test_image.py rename to src/micropython/microbit/test/test_image.py index c4d2731d6..5ea150230 100644 --- a/src/microbit/test/test_image.py +++ b/src/micropython/microbit/test/test_image.py @@ -1,283 +1,283 @@ -import pytest -from ..__model.image import Image - -from ..__model import constants as CONSTANTS - - -class TestImage(object): - def setup_method(self): - self.image = Image() - self.image_heart = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) - - @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) - def test_get_set_pixel(self, x, y, brightness): - self.image.set_pixel(x, y, brightness) - assert brightness == self.image.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.image.get_pixel(x, y) - - @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) - - @pytest.mark.parametrize( - "image, height, width", - [ - (Image(), 5, 5), - (Image(3, 3), 3, 3), - (Image(""), 0, 0), - (Image("00:00000"), 2, 5), - (Image("0000:0000"), 2, 4), - ], - ) - def test_width_and_height(self, image, height, width): - print(str(image)) - assert image.height() == height - assert image.width() == width - - @pytest.mark.parametrize( - "x, y, w, h, x_dest, y_dest, actual", - [ - (0, 0, 3, 2, 2, 1, Image("00000:00090:00999:00000:00000:")), - (0, 0, 3, 3, 8, 8, Image("00000:00000:00000:00000:00000:")), - (3, 0, 3, 3, 0, 0, Image("90000:99000:99000:00000:00000:")), - (3, 0, 7, 7, 0, 0, Image("90000:99000:99000:90000:00000:")), - ], - ) - def test_blit_heart(self, x, y, w, h, x_dest, y_dest, actual): - result = Image() - result.blit(self.image_heart, x, y, w, h, x_dest, y_dest) - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "x, y, w, h, x_dest, y_dest, actual", - [ - (1, 1, 2, 4, 3, 3, Image("09090:99999:99999:09999:00999:")), - (0, 0, 3, 3, 8, 8, Image(CONSTANTS.IMAGE_PATTERNS["HEART"])), - (0, 0, 7, 7, 0, 0, Image(CONSTANTS.IMAGE_PATTERNS["HEART"])), - (3, 0, 7, 7, 0, 0, Image("90000:99000:99000:90000:00000:")), - ], - ) - def test_blit_heart_nonblank(self, x, y, w, h, x_dest, y_dest, actual): - result = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) - src = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) - result.blit(src, x, y, w, h, x_dest, y_dest) - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "x, y, w, h, x_dest, y_dest", [(5, 6, 2, 4, 3, 3), (5, 0, 3, 3, 8, 8)] - ) - def test_blit_heart_valueerror(self, x, y, w, h, x_dest, y_dest): - result = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) - with pytest.raises(ValueError, match=CONSTANTS.INDEX_ERR): - result.blit(self.image_heart, x, y, w, h, x_dest, y_dest) - - @pytest.mark.parametrize( - "image1, image2", [(Image(2, 2, bytearray([4, 4, 4, 4])), Image("44:44"))] - ) - def test_constructor_bytearray(self, image1, image2): - assert image1._Image__LED == image2._Image__LED - - @pytest.mark.parametrize( - "x, y, w, h, actual", [(1, 1, 2, 4, Image("99:99:99:09:"))] - ) - def test_crop_heart(self, x, y, w, h, actual): - result = self.image_heart.crop(1, 1, 2, 4) - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, actual", [(Image("99:99:99:00:"), Image("22:22:22:22:"))] - ) - def test_fill(self, target, actual): - target.fill(2) - assert target._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, actual", [(Image("012:345:678:900:"), Image("987:654:321:099:"))] - ) - def test_invert(self, target, actual): - target.invert() - assert target._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, value, actual", - [ - (Image("012:345:678:900:"), 1, Image("001:034:067:090:")), - (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), - (Image("012:345:678:900:"), -1, Image("120:450:780:000:")), - ], - ) - def test_shift_right(self, target, value, actual): - result = target.shift_right(value) - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, value, actual", - [ - (Image("012:345:678:900:"), 2, Image("200:500:800:000:")), - (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), - (Image("012:345:678:900:"), -2, Image("000:003:006:009:")), - ], - ) - def test_shift_left(self, target, value, actual): - result = target.shift_left(value) - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, value, actual", - [ - (Image("012:345:678:900:"), 2, Image("678:900:000:000:")), - (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), - (Image("012:345:678:900:"), -2, Image("000:000:012:345:")), - ], - ) - def test_shift_up(self, target, value, actual): - result = target.shift_up(value) - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, value, actual", - [ - (Image("012:345:678:900:"), 1, Image("000:012:345:678")), - (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), - (Image("012:345:678:900:"), -1, Image("345:678:900:000:")), - ], - ) - def test_shift_down(self, target, value, actual): - result = target.shift_down(value) - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize("target", [(Image("012:345:678:900:"))]) - def test_copy(self, target): - result = target.copy() - assert result._Image__LED == target._Image__LED - - @pytest.mark.parametrize( - "target, multiplier, actual", - [ - (Image("012:345:678:900:"), 2, Image("024:689:999:900:")), - (Image("012:345:678:900:"), 0, Image("000:000:000:000:")), - ], - ) - def test_multiply(self, target, multiplier, actual): - result = target * multiplier - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, multiplier", - [ - (Image("012:345:678:900:"), []), - (Image("012:345:678:900:"), Image("000:000:000:000:")), - ], - ) - def test_multiply_error(self, target, multiplier): - - with pytest.raises( - TypeError, match=f"can't convert {type(multiplier)} to float" - ): - target * multiplier - - @pytest.mark.parametrize( - "target, value, actual", - [ - ( - Image("012:345:678:900:"), - Image("024:689:999:900:"), - Image("036:999:999:900:"), - ), - ( - Image("999:999:999:000:"), - Image("999:999:999:000:"), - Image("999:999:999:000:"), - ), - ], - ) - def test_add(self, target, value, actual): - result = target + value - assert result._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "target, value, err_message", - [ - ( - Image("012:345:678:900:"), - 2, - CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(Image())}', '{type(2)}'", - ), - ( - Image("012:345:678:900:"), - [], - CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(Image())}', '{type([])}'", - ), - ], - ) - def test_add_typeerror(self, target, value, err_message): - with pytest.raises(TypeError, match=err_message): - target + value - - @pytest.mark.parametrize( - "target, value", [(Image(2, 3), Image(3, 3)), (Image(2, 1), Image(0, 0))] - ) - def test_add_valueerror(self, target, value): - with pytest.raises(ValueError, match=CONSTANTS.SAME_SIZE_ERR): - target + value - - @pytest.mark.parametrize( - "initial, actual", - [ - (Image("0:000:00:0000:"), Image("0000:0000:0000:0000:")), - (Image("12125:1212:12:1:"), Image("12125:12120:12000:10000:")), - ], - ) - def test_uneven_strings(self, initial, actual): - assert initial._Image__LED == actual._Image__LED - - @pytest.mark.parametrize( - "image, repr_actual, str_actual", - [ - ( - Image("05150:05050:05050:99999:09990:"), - "Image('05150:05050:05050:99999:09990:')", - "Image('\n 05150:\n 05050:\n 05050:\n 99999:\n 09990:\n')", - ), - (Image(""), "Image('')", "Image('\n')"), - ( - Image("00000:00000:00000:00000:00000:"), - "Image('00000:00000:00000:00000:00000:')", - "Image('\n 00000:\n 00000:\n 00000:\n 00000:\n 00000:\n')", - ), - ( - Image("00:00:00:00:"), - "Image('00:00:00:00:')", - "Image('\n 00:\n 00:\n 00:\n 00:\n')", - ), - ], - ) - def test_str(self, image, repr_actual, str_actual): - repr_output = repr(image) - str_output = str(image) - assert repr_actual == repr_output - assert str_actual == str_output - - @pytest.mark.parametrize( - "const, actual", - [ - (Image.SNAKE, Image(CONSTANTS.IMAGE_PATTERNS["SNAKE"])), - (Image.PITCHFORK, Image(CONSTANTS.IMAGE_PATTERNS["PITCHFORK"])), - ], - ) - def test_image_constants(self, const, actual): - assert const._Image__LED == actual._Image__LED - with pytest.raises(TypeError, match=CONSTANTS.COPY_ERR_MESSAGE): - const.set_pixel(0, 0, 5) +import pytest +from ..__model.image import Image + +from ..__model import constants as CONSTANTS + + +class TestImage(object): + def setup_method(self): + self.image = Image() + self.image_heart = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) + + @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) + def test_get_set_pixel(self, x, y, brightness): + self.image.set_pixel(x, y, brightness) + assert brightness == self.image.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.image.get_pixel(x, y) + + @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) + + @pytest.mark.parametrize( + "image, height, width", + [ + (Image(), 5, 5), + (Image(3, 3), 3, 3), + (Image(""), 0, 0), + (Image("00:00000"), 2, 5), + (Image("0000:0000"), 2, 4), + ], + ) + def test_width_and_height(self, image, height, width): + print(str(image)) + assert image.height() == height + assert image.width() == width + + @pytest.mark.parametrize( + "x, y, w, h, x_dest, y_dest, actual", + [ + (0, 0, 3, 2, 2, 1, Image("00000:00090:00999:00000:00000:")), + (0, 0, 3, 3, 8, 8, Image("00000:00000:00000:00000:00000:")), + (3, 0, 3, 3, 0, 0, Image("90000:99000:99000:00000:00000:")), + (3, 0, 7, 7, 0, 0, Image("90000:99000:99000:90000:00000:")), + ], + ) + def test_blit_heart(self, x, y, w, h, x_dest, y_dest, actual): + result = Image() + result.blit(self.image_heart, x, y, w, h, x_dest, y_dest) + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "x, y, w, h, x_dest, y_dest, actual", + [ + (1, 1, 2, 4, 3, 3, Image("09090:99999:99999:09999:00999:")), + (0, 0, 3, 3, 8, 8, Image(CONSTANTS.IMAGE_PATTERNS["HEART"])), + (0, 0, 7, 7, 0, 0, Image(CONSTANTS.IMAGE_PATTERNS["HEART"])), + (3, 0, 7, 7, 0, 0, Image("90000:99000:99000:90000:00000:")), + ], + ) + def test_blit_heart_nonblank(self, x, y, w, h, x_dest, y_dest, actual): + result = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) + src = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) + result.blit(src, x, y, w, h, x_dest, y_dest) + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "x, y, w, h, x_dest, y_dest", [(5, 6, 2, 4, 3, 3), (5, 0, 3, 3, 8, 8)] + ) + def test_blit_heart_valueerror(self, x, y, w, h, x_dest, y_dest): + result = Image(CONSTANTS.IMAGE_PATTERNS["HEART"]) + with pytest.raises(ValueError, match=CONSTANTS.INDEX_ERR): + result.blit(self.image_heart, x, y, w, h, x_dest, y_dest) + + @pytest.mark.parametrize( + "image1, image2", [(Image(2, 2, bytearray([4, 4, 4, 4])), Image("44:44"))] + ) + def test_constructor_bytearray(self, image1, image2): + assert image1._Image__LED == image2._Image__LED + + @pytest.mark.parametrize( + "x, y, w, h, actual", [(1, 1, 2, 4, Image("99:99:99:09:"))] + ) + def test_crop_heart(self, x, y, w, h, actual): + result = self.image_heart.crop(1, 1, 2, 4) + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, actual", [(Image("99:99:99:00:"), Image("22:22:22:22:"))] + ) + def test_fill(self, target, actual): + target.fill(2) + assert target._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, actual", [(Image("012:345:678:900:"), Image("987:654:321:099:"))] + ) + def test_invert(self, target, actual): + target.invert() + assert target._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, value, actual", + [ + (Image("012:345:678:900:"), 1, Image("001:034:067:090:")), + (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), + (Image("012:345:678:900:"), -1, Image("120:450:780:000:")), + ], + ) + def test_shift_right(self, target, value, actual): + result = target.shift_right(value) + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, value, actual", + [ + (Image("012:345:678:900:"), 2, Image("200:500:800:000:")), + (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), + (Image("012:345:678:900:"), -2, Image("000:003:006:009:")), + ], + ) + def test_shift_left(self, target, value, actual): + result = target.shift_left(value) + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, value, actual", + [ + (Image("012:345:678:900:"), 2, Image("678:900:000:000:")), + (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), + (Image("012:345:678:900:"), -2, Image("000:000:012:345:")), + ], + ) + def test_shift_up(self, target, value, actual): + result = target.shift_up(value) + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, value, actual", + [ + (Image("012:345:678:900:"), 1, Image("000:012:345:678")), + (Image("012:345:678:900:"), 6, Image("000:000:000:000:")), + (Image("012:345:678:900:"), -1, Image("345:678:900:000:")), + ], + ) + def test_shift_down(self, target, value, actual): + result = target.shift_down(value) + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize("target", [(Image("012:345:678:900:"))]) + def test_copy(self, target): + result = target.copy() + assert result._Image__LED == target._Image__LED + + @pytest.mark.parametrize( + "target, multiplier, actual", + [ + (Image("012:345:678:900:"), 2, Image("024:689:999:900:")), + (Image("012:345:678:900:"), 0, Image("000:000:000:000:")), + ], + ) + def test_multiply(self, target, multiplier, actual): + result = target * multiplier + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, multiplier", + [ + (Image("012:345:678:900:"), []), + (Image("012:345:678:900:"), Image("000:000:000:000:")), + ], + ) + def test_multiply_error(self, target, multiplier): + + with pytest.raises( + TypeError, match=f"can't convert {type(multiplier)} to float" + ): + target * multiplier + + @pytest.mark.parametrize( + "target, value, actual", + [ + ( + Image("012:345:678:900:"), + Image("024:689:999:900:"), + Image("036:999:999:900:"), + ), + ( + Image("999:999:999:000:"), + Image("999:999:999:000:"), + Image("999:999:999:000:"), + ), + ], + ) + def test_add(self, target, value, actual): + result = target + value + assert result._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "target, value, err_message", + [ + ( + Image("012:345:678:900:"), + 2, + CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(Image())}', '{type(2)}'", + ), + ( + Image("012:345:678:900:"), + [], + CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(Image())}', '{type([])}'", + ), + ], + ) + def test_add_typeerror(self, target, value, err_message): + with pytest.raises(TypeError, match=err_message): + target + value + + @pytest.mark.parametrize( + "target, value", [(Image(2, 3), Image(3, 3)), (Image(2, 1), Image(0, 0))] + ) + def test_add_valueerror(self, target, value): + with pytest.raises(ValueError, match=CONSTANTS.SAME_SIZE_ERR): + target + value + + @pytest.mark.parametrize( + "initial, actual", + [ + (Image("0:000:00:0000:"), Image("0000:0000:0000:0000:")), + (Image("12125:1212:12:1:"), Image("12125:12120:12000:10000:")), + ], + ) + def test_uneven_strings(self, initial, actual): + assert initial._Image__LED == actual._Image__LED + + @pytest.mark.parametrize( + "image, repr_actual, str_actual", + [ + ( + Image("05150:05050:05050:99999:09990:"), + "Image('05150:05050:05050:99999:09990:')", + "Image('\n 05150:\n 05050:\n 05050:\n 99999:\n 09990:\n')", + ), + (Image(""), "Image('')", "Image('\n')"), + ( + Image("00000:00000:00000:00000:00000:"), + "Image('00000:00000:00000:00000:00000:')", + "Image('\n 00000:\n 00000:\n 00000:\n 00000:\n 00000:\n')", + ), + ( + Image("00:00:00:00:"), + "Image('00:00:00:00:')", + "Image('\n 00:\n 00:\n 00:\n 00:\n')", + ), + ], + ) + def test_str(self, image, repr_actual, str_actual): + repr_output = repr(image) + str_output = str(image) + assert repr_actual == repr_output + assert str_actual == str_output + + @pytest.mark.parametrize( + "const, actual", + [ + (Image.SNAKE, Image(CONSTANTS.IMAGE_PATTERNS["SNAKE"])), + (Image.PITCHFORK, Image(CONSTANTS.IMAGE_PATTERNS["PITCHFORK"])), + ], + ) + def test_image_constants(self, const, actual): + assert const._Image__LED == actual._Image__LED + with pytest.raises(TypeError, match=CONSTANTS.COPY_ERR_MESSAGE): + const.set_pixel(0, 0, 5) diff --git a/src/microbit/test/test_init.py b/src/micropython/microbit/test/test_init.py similarity index 96% rename from src/microbit/test/test_init.py rename to src/micropython/microbit/test/test_init.py index c6882ac75..aebbc805a 100644 --- a/src/microbit/test/test_init.py +++ b/src/micropython/microbit/test/test_init.py @@ -1,45 +1,45 @@ -import time - -import pytest -from unittest import mock - -from .. import * -from ..__model.microbit_model import MicrobitModel - -# tests methods in __init__.py - - -class TestShim(object): - def test_sleep(self): - # Save pointer to function about to be mocked - real_function = MicrobitModel.sleep - - milliseconds = 100 - MicrobitModel.sleep = mock.Mock() - sleep(milliseconds) - MicrobitModel.sleep.assert_called_with(milliseconds) - - # Restore original function - MicrobitModel.sleep = real_function - - def test_running_time(self): - # Save pointer to function about to be mocked - real_function = MicrobitModel.running_time - - MicrobitModel.running_time = mock.Mock() - running_time() - MicrobitModel.running_time.assert_called_once() - - # Restore original function - MicrobitModel.running_time = real_function - - def test_temperature(self): - # Save pointer to function about to be mocked - real_function = MicrobitModel.temperature - - MicrobitModel.temperature = mock.Mock() - temperature() - MicrobitModel.temperature.asser_called_once() - - # Restore original function - MicrobitModel.temperature = real_function +import time + +import pytest +from unittest import mock + +from .. import * +from ..__model.microbit_model import MicrobitModel + +# tests methods in __init__.py + + +class TestShim(object): + def test_sleep(self): + # Save pointer to function about to be mocked + real_function = MicrobitModel.sleep + + milliseconds = 100 + MicrobitModel.sleep = mock.Mock() + sleep(milliseconds) + MicrobitModel.sleep.assert_called_with(milliseconds) + + # Restore original function + MicrobitModel.sleep = real_function + + def test_running_time(self): + # Save pointer to function about to be mocked + real_function = MicrobitModel.running_time + + MicrobitModel.running_time = mock.Mock() + running_time() + MicrobitModel.running_time.assert_called_once() + + # Restore original function + MicrobitModel.running_time = real_function + + def test_temperature(self): + # Save pointer to function about to be mocked + real_function = MicrobitModel.temperature + + MicrobitModel.temperature = mock.Mock() + temperature() + MicrobitModel.temperature.asser_called_once() + + # Restore original function + MicrobitModel.temperature = real_function diff --git a/src/microbit/test/test_microbit_model.py b/src/micropython/microbit/test/test_microbit_model.py similarity index 96% rename from src/microbit/test/test_microbit_model.py rename to src/micropython/microbit/test/test_microbit_model.py index 1577bf24a..4e06c6c36 100644 --- a/src/microbit/test/test_microbit_model.py +++ b/src/micropython/microbit/test/test_microbit_model.py @@ -1,48 +1,48 @@ -import time - -import pytest -from unittest import mock -from ..__model import constants as CONSTANTS -from ..__model.microbit_model import MicrobitModel - - -class TestMicrobitModel(object): - def setup_method(self): - self.__mb = MicrobitModel() - - @pytest.mark.parametrize("value", [9, 30, 1999]) - def test_sleep(self, value): - time.sleep = mock.Mock() - self.__mb.sleep(value) - time.sleep.assert_called_with(value / 1000) - - def test_running_time(self): - mock_start_time = 10 - mock_end_time = 300 - self.__mb._MicrobitModel__start_time = mock_start_time - time.time = mock.MagicMock(return_value=mock_end_time) - assert mock_end_time - mock_start_time == pytest.approx( - self.__mb.running_time() - ) - - @pytest.mark.parametrize( - "temperature", - [ - CONSTANTS.MIN_TEMPERATURE, - CONSTANTS.MIN_TEMPERATURE + 1, - 0, - CONSTANTS.MAX_TEMPERATURE - 1, - CONSTANTS.MAX_TEMPERATURE, - ], - ) - def test_temperature(self, temperature): - self.__mb._MicrobitModel__set_temperature(temperature) - assert temperature == self.__mb.temperature() - - @pytest.mark.parametrize( - "invalid_temperature", - [CONSTANTS.MIN_TEMPERATURE - 1, CONSTANTS.MAX_TEMPERATURE + 1], - ) - def test_invalid_temperature(self, invalid_temperature): - with pytest.raises(ValueError): - self.__mb._MicrobitModel__set_temperature(invalid_temperature) +import time + +import pytest +from unittest import mock +from ..__model import constants as CONSTANTS +from ..__model.microbit_model import MicrobitModel + + +class TestMicrobitModel(object): + def setup_method(self): + self.__mb = MicrobitModel() + + @pytest.mark.parametrize("value", [9, 30, 1999]) + def test_sleep(self, value): + time.sleep = mock.Mock() + self.__mb.sleep(value) + time.sleep.assert_called_with(value / 1000) + + def test_running_time(self): + mock_start_time = 10 + mock_end_time = 300 + self.__mb._MicrobitModel__start_time = mock_start_time + time.time = mock.MagicMock(return_value=mock_end_time) + assert mock_end_time - mock_start_time == pytest.approx( + self.__mb.running_time() + ) + + @pytest.mark.parametrize( + "temperature", + [ + CONSTANTS.MIN_TEMPERATURE, + CONSTANTS.MIN_TEMPERATURE + 1, + 0, + CONSTANTS.MAX_TEMPERATURE - 1, + CONSTANTS.MAX_TEMPERATURE, + ], + ) + def test_temperature(self, temperature): + self.__mb._MicrobitModel__set_temperature(temperature) + assert temperature == self.__mb.temperature() + + @pytest.mark.parametrize( + "invalid_temperature", + [CONSTANTS.MIN_TEMPERATURE - 1, CONSTANTS.MAX_TEMPERATURE + 1], + ) + def test_invalid_temperature(self, invalid_temperature): + with pytest.raises(ValueError): + self.__mb._MicrobitModel__set_temperature(invalid_temperature) diff --git a/src/micropython/music.py b/src/micropython/music.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/micropython/neopixel.py b/src/micropython/neopixel.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/micropython/radio.py b/src/micropython/radio.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/micropython/speech.py b/src/micropython/speech.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/micropython/utime.py b/src/micropython/utime.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/process_user_code.py b/src/process_user_code.py index 4201acdd1..935188e25 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -21,10 +21,15 @@ user_stdout = io.StringIO() sys.stdout = user_stdout -# Insert absolute path to Adafruit library into sys.path abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) -abs_path_to_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.LIBRARY_NAME) -sys.path.insert(0, abs_path_to_lib) + +# Insert absolute path to Adafruit library for CPX into sys.path +abs_path_to_adafruit_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.ADAFRUIT_LIBRARY_NAME) +sys.path.insert(0, abs_path_to_adafruit_lib) + +# Insert absolute path to Micropython libraries for micro:bit into sys.path +abs_path_to_micropython_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.MICROPYTHON_LIBRARY_NAME) +sys.path.insert(0, abs_path_to_micropython_lib) # This import must happen after the sys.path is modified from common.telemetry import telemetry_py @@ -32,8 +37,8 @@ from adafruit_circuitplayground.express import cpx from adafruit_circuitplayground.constants import CPX -from microbit.__model.microbit_model import __mb as mb -from microbit.__model.constants import MICROBIT +from micropython.microbit.__model.microbit_model import __mb as mb +from micropython.microbit.__model.constants import MICROBIT # Handle User Inputs Thread diff --git a/src/python_constants.py b/src/python_constants.py index a5f55bc8c..197c071a2 100644 --- a/src/python_constants.py +++ b/src/python_constants.py @@ -16,7 +16,9 @@ ERROR_TRACEBACK = "\n\tTraceback of code execution : \n" ERROR_NO_FILE = "Error : No file was passed to the process to execute.\n" -LIBRARY_NAME = "adafruit_circuitplayground" +ADAFRUIT_LIBRARY_NAME = "adafruit_circuitplayground" +MICROPYTHON_LIBRARY_NAME = "micropython" + LINUX_OS = "linux" MAC_OS = "darwin" From c4a8162d925a2b663c9f4ee9b811ac308f9cae68 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Mar 2020 01:47:06 -0700 Subject: [PATCH 06/14] Added all unimplemented modules correctly --- src/common/telemetry_events.py | 6 + src/micropython/audio.py | 37 +++++- src/micropython/microbit/__model/i2c.py | 2 +- src/micropython/microbit/__model/spi.py | 2 +- src/micropython/music.py | 108 +++++++++++++++ src/micropython/neopixel.py | 30 +++++ src/micropython/radio.py | 168 ++++++++++++++++++++++++ src/micropython/speech.py | 55 ++++++++ src/micropython/utime.py | 125 ++++++++++++++++++ 9 files changed, 529 insertions(+), 4 deletions(-) diff --git a/src/common/telemetry_events.py b/src/common/telemetry_events.py index 678659288..92e81c546 100644 --- a/src/common/telemetry_events.py +++ b/src/common/telemetry_events.py @@ -34,3 +34,9 @@ class TelemetryEvent(enum.Enum): MICROBIT_API_COMPASS = "MICROBIT.API.COMPASS" MICROBIT_API_I2C = "MICROBIT.API.I2C" MICROBIT_API_SPI = "MICROBIT.API.SPI" + MICROBIT_API_AUDIO = "MICROBIT.API.AUDIO" + MICROBIT_API_MUSIC = "MICROBIT.API.MUSIC" + MICROBIT_API_NEOPIXEL = "MICROBIT.API.NEOPIXEL" + MICROBIT_API_RADIO = "MICROBIT.API.RADIO" + MICROBIT_API_SPEECH = "MICROBIT.API.SPEECH" + MICROBIT_API_UTIME = "MICROBIT.API.UTIME" \ No newline at end of file diff --git a/src/micropython/audio.py b/src/micropython/audio.py index 5caa29fab..50f2870e5 100644 --- a/src/micropython/audio.py +++ b/src/micropython/audio.py @@ -1,2 +1,35 @@ -def play(): - print("audio.play") \ No newline at end of file +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + +def play(source, wait=True, pin="pin0", return_pin=None): + """ + This function is not implemented in the simulator. + + Play the source to completion. + + ``source`` is an iterable, each element of which must be an ``AudioFrame``. + + If ``wait`` is ``True``, this function will block until the source is exhausted. + + ``pin`` specifies which pin the speaker is connected to. + + ``return_pin`` specifies a differential pin to connect to the speaker + instead of ground. + """ + utils.print_for_unimplemented_functions(play.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_AUDIO) + + +class AudioFrame: + """ + This class is not implemented in the simulator. + + An ``AudioFrame`` object is a list of 32 samples each of which is a signed byte + (whole number between -128 and 127). + + It takes just over 4 ms to play a single frame. + """ + def __init__(self): + utils.print_for_unimplemented_functions(AudioFrame.__init__.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_AUDIO) diff --git a/src/micropython/microbit/__model/i2c.py b/src/micropython/microbit/__model/i2c.py index 64739cd64..2c83868d2 100644 --- a/src/micropython/microbit/__model/i2c.py +++ b/src/micropython/microbit/__model/i2c.py @@ -17,7 +17,7 @@ def init(self, freq=100000, sda="pin20", scl="pin19"): Changing the I²C pins from defaults will make the accelerometer and compass stop working, as they are connected internally to those pins. """ - utils.print_for_unimplemented_functions(I2c.init.__name__) + utils.print_for_unimplemented_functions(I2c.init.__qualname__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) def scan(self): diff --git a/src/micropython/microbit/__model/spi.py b/src/micropython/microbit/__model/spi.py index 836e3859c..d455e30b0 100644 --- a/src/micropython/microbit/__model/spi.py +++ b/src/micropython/microbit/__model/spi.py @@ -33,7 +33,7 @@ def init( The ``sclk``, ``mosi`` and ``miso`` arguments specify the pins to use for each type of signal. """ - utils.print_for_unimplemented_functions(SPI.init.__name__) + utils.print_for_unimplemented_functions(SPI.init.__qualname__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) def read(self, nbytes): diff --git a/src/micropython/music.py b/src/micropython/music.py index e69de29bb..7b76d636b 100644 --- a/src/micropython/music.py +++ b/src/micropython/music.py @@ -0,0 +1,108 @@ +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + +def set_tempo(ticks=4, bpm=120): + """ + This function is not implemented in the simulator. + + Sets the approximate tempo for playback. + + A number of ticks (expressed as an integer) constitute a beat. Each beat is to be played at a certain frequency per minute (expressed as the more familiar BPM - beats per minute - also as an integer). + + Suggested default values allow the following useful behaviour: + + * ``music.set_tempo()`` - reset the tempo to default of ticks = 4, bpm = 120 + * ``music.set_tempo(ticks=8)`` - change the "definition" of a beat + * ``music.set_tempo(bpm=180)`` - just change the tempo + + To work out the length of a tick in milliseconds is very simple arithmetic: ``60000/bpm/ticks_per_beat`` . For the default values that's ``60000/120/4 = 125 milliseconds`` or ``1 beat = 500 milliseconds``. + """ + utils.print_for_unimplemented_functions(set_tempo.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) + +def get_tempo(): + """ + This function is not implemented in the simulator. + + Gets the current tempo as a tuple of integers: ``(ticks, bpm)``. + """ + utils.print_for_unimplemented_functions(get_tempo.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) + + + +def play(music, pin="microbit.pin0", wait=True, loop=False): + """ + This function is not implemented in the simulator. + + Plays ``music`` containing the musical DSL defined above. + + If ``music`` is a string it is expected to be a single note such as, + ``'c1:4'``. + + If ``music`` is specified as a list of notes (as defined in the section on + the musical DSL, above) then they are played one after the other to perform + a melody. + + In both cases, the ``duration`` and ``octave`` values are reset to + their defaults before the music (whatever it may be) is played. + + An optional argument to specify the output pin can be used to override the + default of ``microbit.pin0``. + + If ``wait`` is set to ``True``, this function is blocking. + + If ``loop`` is set to ``True``, the tune repeats until ``stop`` is called + (see below) or the blocking call is interrupted. + """ + utils.print_for_unimplemented_functions(play.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) + + +def pitch(frequency, duration=-1, pin="microbit.pin0", wait=True): + """ + This function is not implemented in the simulator. + + Plays a pitch at the integer frequency given for the specified number of + milliseconds. For example, if the frequency is set to 440 and the length to + 1000 then we hear a standard concert A for one second. + + Note that you can only play one pitch on one pin at any one time. + + If ``wait`` is set to ``True``, this function is blocking. + + If ``duration`` is negative the pitch is played continuously until either the + blocking call is interrupted or, in the case of a background call, a new + frequency is set or ``stop`` is called (see below). + """ + utils.print_for_unimplemented_functions(pitch.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) + + +def stop(pin="microbit.pin0"): + """ + This function is not implemented in the simulator. + + Stops all music playback on a given pin, eg. ``music.stop(pin1)``. + If no pin is given, eg. ``music.stop()`` pin0 is assumed. + """ + utils.print_for_unimplemented_functions(stop.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) + + + +def reset(): + """ + This function is not implemented in the simulator. + + Resets the state of the following attributes in the following way: + + * ``ticks = 4`` + * ``bpm = 120`` + * ``duration = 4`` + * ``octave = 4`` + """ + utils.print_for_unimplemented_functions(reset.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) + \ No newline at end of file diff --git a/src/micropython/neopixel.py b/src/micropython/neopixel.py index e69de29bb..6aaf4f0ce 100644 --- a/src/micropython/neopixel.py +++ b/src/micropython/neopixel.py @@ -0,0 +1,30 @@ +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + +class NeoPixel: + """ + This class is not implemented in the simulator. + + Initialise a new strip of ``n`` number of neopixel LEDs controlled via pin + ``pin``. Each pixel is addressed by a position (starting from 0). Neopixels + are given RGB (red, green, blue) values between 0-255 as a tuple. For + example, ``(255,255,255)`` is white. + """ + def __init__(self, pin, n): + utils.print_for_unimplemented_functions(NeoPixel.__init__.__qualname__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_NEOPIXEL) + + def clear(self): + """ + Clear all the pixels. + """ + utils.print_for_unimplemented_functions(NeoPixel.clear.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_NEOPIXEL) + + def show(self): + """ + Show the pixels. Must be called for any updates to become visible. + """ + utils.print_for_unimplemented_functions(NeoPixel.show.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_NEOPIXEL) \ No newline at end of file diff --git a/src/micropython/radio.py b/src/micropython/radio.py index e69de29bb..18e0e6d2e 100644 --- a/src/micropython/radio.py +++ b/src/micropython/radio.py @@ -0,0 +1,168 @@ +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + +RATE_250KBIT = "" + +RATE_1MBIT = "" + +RATE_2MBIT = "" + +def on(): + """ + This function is not implemented in the simulator. + + Turns the radio on. This needs to be explicitly called since the radio + draws power and takes up memory that you may otherwise need. + """ + utils.print_for_unimplemented_functions(on.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def off(): + """ + This function is not implemented in the simulator. + + Turns off the radio, thus saving power and memory + """ + utils.print_for_unimplemented_functions(off.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def config(**kwargs): + """ + This function is not implemented in the simulator. + + Configures various keyword based settings relating to the radio. The + available settings and their sensible default values are listed below. + + The ``length`` (default=32) defines the maximum length, in bytes, of a + message sent via the radio. It can be up to 251 bytes long (254 - 3 bytes + for S0, LENGTH and S1 preamble). + + The ``queue`` (default=3) specifies the number of messages that can be + stored on the incoming message queue. If there are no spaces left on the + queue for incoming messages, then the incoming message is dropped. + + The ``channel`` (default=7) can be an integer value from 0 to 83 + (inclusive) that defines an arbitrary "channel" to which the radio is + tuned. Messages will be sent via this channel and only messages received + via this channel will be put onto the incoming message queue. Each step is + 1MHz wide, based at 2400MHz. + + The ``power`` (default=6) is an integer value from 0 to 7 (inclusive) to + indicate the strength of signal used when broadcasting a message. The + higher the value the stronger the signal, but the more power is consumed + by the device. The numbering translates to positions in the following list + of dBm (decibel milliwatt) values: -30, -20, -16, -12, -8, -4, 0, 4. + + The ``address`` (default=0x75626974) is an arbitrary name, expressed as a + 32-bit address, that's used to filter incoming packets at the hardware + level, keeping only those that match the address you set. The default used + by other micro:bit related platforms is the default setting used here. + + The ``group`` (default=0) is an 8-bit value (0-255) used with the + ``address`` when filtering messages. Conceptually, "address" is like a + house/office address and "group" is like the person at that address to + which you want to send your message. + + The ``data_rate`` (default=radio.RATE_1MBIT) indicates the speed at which + data throughput takes place. Can be one of the following contants defined + in the ``radio`` module : ``RATE_250KBIT``, ``RATE_1MBIT`` or + ``RATE_2MBIT``. + + If ``config`` is not called then the defaults described above are assumed. + """ + utils.print_for_unimplemented_functions(config.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def reset(): + """ + This function is not implemented in the simulator. + + Reset the settings to their default values (as listed in the documentation + for the ``config`` function above). + """ + utils.print_for_unimplemented_functions(reset.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def send_bytes(message): + """ + This function is not implemented in the simulator. + + Sends a message containing bytes. + """ + utils.print_for_unimplemented_functions(send_bytes.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def receive_bytes(): + """ + This function is not implemented in the simulator. + + Receive the next incoming message on the message queue. Returns ``None`` if + there are no pending messages. Messages are returned as bytes. + """ + utils.print_for_unimplemented_functions(receive_bytes.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def receive_bytes_into(buffer): + """ + This function is not implemented in the simulator. + + Receive the next incoming message on the message queue. Copies the message + into ``buffer``, trimming the end of the message if necessary. + Returns ``None`` if there are no pending messages, otherwise it returns the length + of the message (which might be more than the length of the buffer). + """ + utils.print_for_unimplemented_functions(receive_bytes_into.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def send(message): + """ + This function is not implemented in the simulator. + + Sends a message containing bytes. + """ + utils.print_for_unimplemented_functions(send.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def receive(): + """ + This function is not implemented in the simulator. + + Works in exactly the same way as ``receive_bytes`` but returns + whatever was sent. + + Currently, it's equivalent to ``str(receive_bytes(), 'utf8')`` but with a + check that the the first three bytes are ``b'\x01\x00\x01'`` (to make it + compatible with other platforms that may target the micro:bit). It strips + the prepended bytes before converting to a string. + + A ``ValueError`` exception is raised if conversion to string fails. + """ + utils.print_for_unimplemented_functions(receive.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + +def receive_full(): + """ + This function is not implemented in the simulator. + + Returns a tuple containing three values representing the next incoming + message on the message queue. If there are no pending messages then + ``None`` is returned. + + The three values in the tuple represent: + + * the next incoming message on the message queue as bytes. + * the RSSI (signal strength): a value between 0 (strongest) and -255 (weakest) as measured in dBm. + * a microsecond timestamp: the value returned by ``time.ticks_us()`` when the message was received. + + For example:: + + details = radio.receive_full() + if details: + msg, rssi, timestamp = details + + This function is useful for providing information needed for triangulation + and/or triliteration with other micro:bit devices. + """ + utils.print_for_unimplemented_functions(receive_full.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) diff --git a/src/micropython/speech.py b/src/micropython/speech.py index e69de29bb..5ae4ce449 100644 --- a/src/micropython/speech.py +++ b/src/micropython/speech.py @@ -0,0 +1,55 @@ +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + +def translate(words): + """ + This function is not implemented in the simulator. + + Given English words in the string ``words``, return a string containing + a best guess at the appropriate phonemes to pronounce. The output is + generated from this + `text to phoneme translation table `_. + + This function should be used to generate a first approximation of phonemes + that can be further hand-edited to improve accuracy, inflection and + emphasis. + """ + utils.print_for_unimplemented_functions(translate.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) + +def pronounce(phonemes, pitch=64, speed=72, mouth=128, throat=128): + """ + This function is not implemented in the simulator. + + Pronounce the phonemes in the string ``phonemes``. See below for details of + how to use phonemes to finely control the output of the speech synthesiser. + Override the optional pitch, speed, mouth and throat settings to change the + timbre (quality) of the voice. + """ + utils.print_for_unimplemented_functions(pronounce.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) + +def say(words, pitch=64, speed=72, mouth=128, throat=128): + """ + This function is not implemented in the simulator. + + Say the English words in the string ``words``. The result is semi-accurate + for English. Override the optional pitch, speed, mouth and throat + settings to change the timbre (quality) of the voice. This is a short-hand + equivalent of: ``speech.pronounce(speech.translate(words))`` + """ + utils.print_for_unimplemented_functions(say.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) + +def sing(phonemes, pitch=64, speed=72, mouth=128, throat=128): + """ + This function is not implemented in the simulator. + + Sing the phonemes contained in the string ``phonemes``. Changing the pitch + and duration of the note is described below. Override the optional pitch, + speed, mouth and throat settings to change the timbre (quality) of the + voice. + """ + utils.print_for_unimplemented_functions(sing.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) \ No newline at end of file diff --git a/src/micropython/utime.py b/src/micropython/utime.py index e69de29bb..27423f705 100644 --- a/src/micropython/utime.py +++ b/src/micropython/utime.py @@ -0,0 +1,125 @@ +import time + +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + +def sleep(seconds): + """ + Sleep for the given number of seconds. You can use a floating-point number + to sleep for a fractional number of seconds, or use the + :func:`utime.sleep_ms()` and :func:`utime.sleep_us()` functions. + """ + time.sleep(seconds) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + +def sleep_ms(ms): + """ + Delay for given number of milliseconds, should be positive or 0. + """ + time.sleep(ms / 1000) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + +def sleep_us(us): + """ + Delay for given number of microseconds, should be positive or 0. + """ + time.sleep(us / 1000000) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + + +def ticks_ms(): + """ + This function is not implemented in the simulator. + + Returns an increasing millisecond counter with an arbitrary reference point, + that wraps around after some value. + """ + utils.print_for_unimplemented_functions(ticks_ms.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + +def ticks_us(): + """ + This function is not implemented in the simulator. + + Just like :func:`utime.ticks_ms()` above, but in microseconds. + """ + utils.print_for_unimplemented_functions(ticks_us.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + +def ticks_add(ticks, delta): + """ + This function is not implemented in the simulator. + + Offset ticks value by a given number, which can be either positive or + negative. Given a ticks value, this function allows to calculate ticks + value delta ticks before or after it, following modular-arithmetic + definition of tick values. + + Example: + + .. code-block:: python + + # Find out what ticks value there was 100ms ago + print(ticks_add(time.ticks_ms(), -100)) + + # Calculate deadline for operation and test for it + deadline = ticks_add(time.ticks_ms(), 200) + while ticks_diff(deadline, time.ticks_ms()) > 0: + do_a_little_of_something() + + # Find out TICKS_MAX used by this port + print(ticks_add(0, -1)) + """ + utils.print_for_unimplemented_functions(ticks_add.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + +def ticks_diff(ticks1, ticks2): + """ + This function is not implemented in the simulator. + + Measure ticks difference between values returned from + :func:`utime.ticks_ms()` or :func:`ticks_us()` functions, as a signed value + which may wrap around. + + The argument order is the same as for subtraction operator, + ``ticks_diff(ticks1, ticks2)`` has the same meaning as ``ticks1 - ticks2``. + + :func:`utime.ticks_diff()` is designed to accommodate various usage + patterns, among them: + + Polling with timeout. In this case, the order of events is known, and you + will deal only with positive results of :func:`utime.ticks_diff()`: + + .. code-block:: python + + # Wait for GPIO pin to be asserted, but at most 500us + start = time.ticks_us() + while pin.value() == 0: + if time.ticks_diff(time.ticks_us(), start) > 500: + raise TimeoutError + + + Scheduling events. In this case, :func:`utime.ticks_diff()` result may be + negative if an event is overdue: + + + .. code-block:: python + + # This code snippet is not optimized + now = time.ticks_ms() + scheduled_time = task.scheduled_time() + if ticks_diff(scheduled_time, now) > 0: + print("Too early, let's nap") + sleep_ms(ticks_diff(scheduled_time, now)) + task.run() + elif ticks_diff(scheduled_time, now) == 0: + print("Right at time!") + task.run() + elif ticks_diff(scheduled_time, now) < 0: + print("Oops, running late, tell task to run faster!") + task.run(run_faster=true) + """ + utils.print_for_unimplemented_functions(ticks_diff.__name__) + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) \ No newline at end of file From 07d8d52f1043cc493adf857e9e39c7b8983f01e3 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Mar 2020 01:58:04 -0700 Subject: [PATCH 07/14] Formatted with black --- src/common/telemetry_events.py | 2 +- src/debug_user_code.py | 8 ++++++-- src/micropython/audio.py | 2 ++ src/micropython/music.py | 5 ++--- src/micropython/neopixel.py | 4 +++- src/micropython/radio.py | 10 ++++++++++ src/micropython/speech.py | 8 ++++++-- src/micropython/utime.py | 7 ++++++- src/process_user_code.py | 8 ++++++-- 9 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/common/telemetry_events.py b/src/common/telemetry_events.py index 92e81c546..bcd82c681 100644 --- a/src/common/telemetry_events.py +++ b/src/common/telemetry_events.py @@ -39,4 +39,4 @@ class TelemetryEvent(enum.Enum): MICROBIT_API_NEOPIXEL = "MICROBIT.API.NEOPIXEL" MICROBIT_API_RADIO = "MICROBIT.API.RADIO" MICROBIT_API_SPEECH = "MICROBIT.API.SPEECH" - MICROBIT_API_UTIME = "MICROBIT.API.UTIME" \ No newline at end of file + MICROBIT_API_UTIME = "MICROBIT.API.UTIME" diff --git a/src/debug_user_code.py b/src/debug_user_code.py index a22ed1158..cfb7544e9 100644 --- a/src/debug_user_code.py +++ b/src/debug_user_code.py @@ -14,11 +14,15 @@ abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) # Insert absolute path to Adafruit library for CPX into sys.path -abs_path_to_adafruit_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.ADAFRUIT_LIBRARY_NAME) +abs_path_to_adafruit_lib = os.path.join( + abs_path_to_parent_dir, CONSTANTS.ADAFRUIT_LIBRARY_NAME +) sys.path.insert(0, abs_path_to_adafruit_lib) # Insert absolute path to Micropython libraries for micro:bit into sys.path -abs_path_to_micropython_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.MICROPYTHON_LIBRARY_NAME) +abs_path_to_micropython_lib = os.path.join( + abs_path_to_parent_dir, CONSTANTS.MICROPYTHON_LIBRARY_NAME +) sys.path.insert(0, abs_path_to_micropython_lib) # This import must happen after the sys.path is modified diff --git a/src/micropython/audio.py b/src/micropython/audio.py index 50f2870e5..b16e60faf 100644 --- a/src/micropython/audio.py +++ b/src/micropython/audio.py @@ -2,6 +2,7 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent + def play(source, wait=True, pin="pin0", return_pin=None): """ This function is not implemented in the simulator. @@ -30,6 +31,7 @@ class AudioFrame: It takes just over 4 ms to play a single frame. """ + def __init__(self): utils.print_for_unimplemented_functions(AudioFrame.__init__.__qualname__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_AUDIO) diff --git a/src/micropython/music.py b/src/micropython/music.py index 7b76d636b..1c139feaa 100644 --- a/src/micropython/music.py +++ b/src/micropython/music.py @@ -2,6 +2,7 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent + def set_tempo(ticks=4, bpm=120): """ This function is not implemented in the simulator. @@ -21,6 +22,7 @@ def set_tempo(ticks=4, bpm=120): utils.print_for_unimplemented_functions(set_tempo.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) + def get_tempo(): """ This function is not implemented in the simulator. @@ -31,7 +33,6 @@ def get_tempo(): telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) - def play(music, pin="microbit.pin0", wait=True, loop=False): """ This function is not implemented in the simulator. @@ -91,7 +92,6 @@ def stop(pin="microbit.pin0"): telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) - def reset(): """ This function is not implemented in the simulator. @@ -105,4 +105,3 @@ def reset(): """ utils.print_for_unimplemented_functions(reset.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_MUSIC) - \ No newline at end of file diff --git a/src/micropython/neopixel.py b/src/micropython/neopixel.py index 6aaf4f0ce..baaa48345 100644 --- a/src/micropython/neopixel.py +++ b/src/micropython/neopixel.py @@ -2,6 +2,7 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent + class NeoPixel: """ This class is not implemented in the simulator. @@ -11,6 +12,7 @@ class NeoPixel: are given RGB (red, green, blue) values between 0-255 as a tuple. For example, ``(255,255,255)`` is white. """ + def __init__(self, pin, n): utils.print_for_unimplemented_functions(NeoPixel.__init__.__qualname__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_NEOPIXEL) @@ -27,4 +29,4 @@ def show(self): Show the pixels. Must be called for any updates to become visible. """ utils.print_for_unimplemented_functions(NeoPixel.show.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_NEOPIXEL) \ No newline at end of file + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_NEOPIXEL) diff --git a/src/micropython/radio.py b/src/micropython/radio.py index 18e0e6d2e..63f8b3073 100644 --- a/src/micropython/radio.py +++ b/src/micropython/radio.py @@ -8,6 +8,7 @@ RATE_2MBIT = "" + def on(): """ This function is not implemented in the simulator. @@ -18,6 +19,7 @@ def on(): utils.print_for_unimplemented_functions(on.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def off(): """ This function is not implemented in the simulator. @@ -27,6 +29,7 @@ def off(): utils.print_for_unimplemented_functions(off.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def config(**kwargs): """ This function is not implemented in the simulator. @@ -74,6 +77,7 @@ def config(**kwargs): utils.print_for_unimplemented_functions(config.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def reset(): """ This function is not implemented in the simulator. @@ -84,6 +88,7 @@ def reset(): utils.print_for_unimplemented_functions(reset.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def send_bytes(message): """ This function is not implemented in the simulator. @@ -93,6 +98,7 @@ def send_bytes(message): utils.print_for_unimplemented_functions(send_bytes.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def receive_bytes(): """ This function is not implemented in the simulator. @@ -103,6 +109,7 @@ def receive_bytes(): utils.print_for_unimplemented_functions(receive_bytes.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def receive_bytes_into(buffer): """ This function is not implemented in the simulator. @@ -115,6 +122,7 @@ def receive_bytes_into(buffer): utils.print_for_unimplemented_functions(receive_bytes_into.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def send(message): """ This function is not implemented in the simulator. @@ -124,6 +132,7 @@ def send(message): utils.print_for_unimplemented_functions(send.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def receive(): """ This function is not implemented in the simulator. @@ -141,6 +150,7 @@ def receive(): utils.print_for_unimplemented_functions(receive.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_RADIO) + def receive_full(): """ This function is not implemented in the simulator. diff --git a/src/micropython/speech.py b/src/micropython/speech.py index 5ae4ce449..f53a20490 100644 --- a/src/micropython/speech.py +++ b/src/micropython/speech.py @@ -2,6 +2,7 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent + def translate(words): """ This function is not implemented in the simulator. @@ -18,6 +19,7 @@ def translate(words): utils.print_for_unimplemented_functions(translate.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) + def pronounce(phonemes, pitch=64, speed=72, mouth=128, throat=128): """ This function is not implemented in the simulator. @@ -30,6 +32,7 @@ def pronounce(phonemes, pitch=64, speed=72, mouth=128, throat=128): utils.print_for_unimplemented_functions(pronounce.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) + def say(words, pitch=64, speed=72, mouth=128, throat=128): """ This function is not implemented in the simulator. @@ -41,7 +44,8 @@ def say(words, pitch=64, speed=72, mouth=128, throat=128): """ utils.print_for_unimplemented_functions(say.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) - + + def sing(phonemes, pitch=64, speed=72, mouth=128, throat=128): """ This function is not implemented in the simulator. @@ -52,4 +56,4 @@ def sing(phonemes, pitch=64, speed=72, mouth=128, throat=128): voice. """ utils.print_for_unimplemented_functions(sing.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) \ No newline at end of file + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) diff --git a/src/micropython/utime.py b/src/micropython/utime.py index 27423f705..80b2c6bdf 100644 --- a/src/micropython/utime.py +++ b/src/micropython/utime.py @@ -14,6 +14,7 @@ def sleep(seconds): time.sleep(seconds) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + def sleep_ms(ms): """ Delay for given number of milliseconds, should be positive or 0. @@ -21,6 +22,7 @@ def sleep_ms(ms): time.sleep(ms / 1000) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + def sleep_us(us): """ Delay for given number of microseconds, should be positive or 0. @@ -39,6 +41,7 @@ def ticks_ms(): utils.print_for_unimplemented_functions(ticks_ms.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + def ticks_us(): """ This function is not implemented in the simulator. @@ -48,6 +51,7 @@ def ticks_us(): utils.print_for_unimplemented_functions(ticks_us.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + def ticks_add(ticks, delta): """ This function is not implemented in the simulator. @@ -75,6 +79,7 @@ def ticks_add(ticks, delta): utils.print_for_unimplemented_functions(ticks_add.__name__) telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) + def ticks_diff(ticks1, ticks2): """ This function is not implemented in the simulator. @@ -122,4 +127,4 @@ def ticks_diff(ticks1, ticks2): task.run(run_faster=true) """ utils.print_for_unimplemented_functions(ticks_diff.__name__) - telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) \ No newline at end of file + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_UTIME) diff --git a/src/process_user_code.py b/src/process_user_code.py index 935188e25..ad797b3c3 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -24,11 +24,15 @@ abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) # Insert absolute path to Adafruit library for CPX into sys.path -abs_path_to_adafruit_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.ADAFRUIT_LIBRARY_NAME) +abs_path_to_adafruit_lib = os.path.join( + abs_path_to_parent_dir, CONSTANTS.ADAFRUIT_LIBRARY_NAME +) sys.path.insert(0, abs_path_to_adafruit_lib) # Insert absolute path to Micropython libraries for micro:bit into sys.path -abs_path_to_micropython_lib = os.path.join(abs_path_to_parent_dir, CONSTANTS.MICROPYTHON_LIBRARY_NAME) +abs_path_to_micropython_lib = os.path.join( + abs_path_to_parent_dir, CONSTANTS.MICROPYTHON_LIBRARY_NAME +) sys.path.insert(0, abs_path_to_micropython_lib) # This import must happen after the sys.path is modified From 5c366a7575f670193ea1a63e7bc05fe4a0ddd4aa Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Mar 2020 02:21:36 -0700 Subject: [PATCH 08/14] Formatted once again and added missing comments --- src/common/utils.py | 2 +- src/extension.ts | 4 ++-- src/micropython/audio.py | 2 ++ src/micropython/music.py | 2 ++ src/micropython/neopixel.py | 5 +++++ src/micropython/radio.py | 2 ++ src/micropython/speech.py | 2 ++ src/micropython/utime.py | 2 ++ 8 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/common/utils.py b/src/common/utils.py index b8b6d7de8..dbd144835 100644 --- a/src/common/utils.py +++ b/src/common/utils.py @@ -58,5 +58,5 @@ def print_for_unimplemented_functions(function_name, one_more_call=False): line_number = sys._getframe(frame_no).f_lineno user_file_name = sys._getframe(frame_no).f_code.co_filename print( - f"{function_name} on line {line_number} in {user_file_name} is not implemented in the simulator but it will work on the actual device!" + f"'{function_name}' on line {line_number} in {user_file_name} is not implemented in the simulator but it will work on the actual device!" ) diff --git a/src/extension.ts b/src/extension.ts index 59d9d87e8..4c8755bb3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1282,8 +1282,8 @@ const updatePylintArgs = (context: vscode.ExtensionContext) => { CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY ); - // update pylint args to extend system path - // to include python libs local to extention + // update pylint args to extend system path + // to include python libs local to extention updateConfigLists( "python.linting.pylintArgs", ["--init-hook", `import sys; sys.path.append(\"${outPath}\")`], diff --git a/src/micropython/audio.py b/src/micropython/audio.py index b16e60faf..36a336465 100644 --- a/src/micropython/audio.py +++ b/src/micropython/audio.py @@ -2,6 +2,8 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent +# The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/audio.html. + def play(source, wait=True, pin="pin0", return_pin=None): """ diff --git a/src/micropython/music.py b/src/micropython/music.py index 1c139feaa..ad25816a6 100644 --- a/src/micropython/music.py +++ b/src/micropython/music.py @@ -2,6 +2,8 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent +# The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/music.html. + def set_tempo(ticks=4, bpm=120): """ diff --git a/src/micropython/neopixel.py b/src/micropython/neopixel.py index baaa48345..78a87eb91 100644 --- a/src/micropython/neopixel.py +++ b/src/micropython/neopixel.py @@ -4,6 +4,7 @@ class NeoPixel: + # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/neopixel.html. """ This class is not implemented in the simulator. @@ -19,6 +20,8 @@ def __init__(self, pin, n): def clear(self): """ + This function is not implemented in the simulator. + Clear all the pixels. """ utils.print_for_unimplemented_functions(NeoPixel.clear.__name__) @@ -26,6 +29,8 @@ def clear(self): def show(self): """ + This function is not implemented in the simulator. + Show the pixels. Must be called for any updates to become visible. """ utils.print_for_unimplemented_functions(NeoPixel.show.__name__) diff --git a/src/micropython/radio.py b/src/micropython/radio.py index 63f8b3073..ac6d24fe3 100644 --- a/src/micropython/radio.py +++ b/src/micropython/radio.py @@ -2,6 +2,8 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent +# The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/radio.html. + RATE_250KBIT = "" RATE_1MBIT = "" diff --git a/src/micropython/speech.py b/src/micropython/speech.py index f53a20490..e92458a96 100644 --- a/src/micropython/speech.py +++ b/src/micropython/speech.py @@ -2,6 +2,8 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent +# The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/speech.html. + def translate(words): """ diff --git a/src/micropython/utime.py b/src/micropython/utime.py index 80b2c6bdf..9ae5ee4df 100644 --- a/src/micropython/utime.py +++ b/src/micropython/utime.py @@ -4,6 +4,8 @@ from common.telemetry import telemetry_py from common.telemetry_events import TelemetryEvent +# The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/utime.html. + def sleep(seconds): """ From aa1871567b9e31b15c1c04940bb8ba2555f9a160 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Mar 2020 02:53:43 -0700 Subject: [PATCH 09/14] fixed intellisence --- .vscode/settings.json | 3 ++- src/constants.ts | 1 + src/extension.ts | 9 ++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fa0a10487..f4713c5de 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "out": true // set this to false to include "out" folder in search results }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "python.pythonPath": "C:\\Users\\Vandy\\AppData\\Local\\Programs\\Python\\Python38-32\\python.exe" } diff --git a/src/constants.ts b/src/constants.ts index 5d30b3602..7e145dac9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -121,6 +121,7 @@ export const CONSTANTS = { FILESYSTEM: { OUTPUT_DIRECTORY: "out", PYTHON_VENV_DIR: "venv", + MICROPYTHON_DIRECTORY: "micropython", }, INFO: { ALREADY_SUCCESSFUL_INSTALL: localize( diff --git a/src/extension.ts b/src/extension.ts index 4c8755bb3..bfe83e178 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1271,7 +1271,10 @@ const handleNewFileErrorTelemetry = () => { const updatePythonExtraPaths = () => { updateConfigLists( "python.autoComplete.extraPaths", - [__dirname], + [ + __dirname, + path.join(__dirname, CONSTANTS.FILESYSTEM.MICROPYTHON_DIRECTORY), + ], vscode.ConfigurationTarget.Global ); }; @@ -1282,8 +1285,8 @@ const updatePylintArgs = (context: vscode.ExtensionContext) => { CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY ); - // update pylint args to extend system path - // to include python libs local to extention + // update pylint args to extend system path + // to include python libs local to extention updateConfigLists( "python.linting.pylintArgs", ["--init-hook", `import sys; sys.path.append(\"${outPath}\")`], From f440939df85bf918664a97032c0f6a7baefbfc73 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Mar 2020 13:10:15 -0700 Subject: [PATCH 10/14] reverted settings.json --- .vscode/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f4713c5de..fa0a10487 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,5 @@ "out": true // set this to false to include "out" folder in search results }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off", - "python.pythonPath": "C:\\Users\\Vandy\\AppData\\Local\\Programs\\Python\\Python38-32\\python.exe" + "typescript.tsc.autoDetect": "off" } From 83f2dcf63b5020a2987692412d3036cb370caee4 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Mar 2020 14:21:10 -0700 Subject: [PATCH 11/14] fix sensors not working --- src/process_user_code.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process_user_code.py b/src/process_user_code.py index ad797b3c3..69f490e72 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -41,8 +41,8 @@ from adafruit_circuitplayground.express import cpx from adafruit_circuitplayground.constants import CPX -from micropython.microbit.__model.microbit_model import __mb as mb -from micropython.microbit.__model.constants import MICROBIT +from microbit.__model.microbit_model import __mb as mb +from microbit.__model.constants import MICROBIT # Handle User Inputs Thread From 9e815c54b7bd2b6d50d5e81d15621fc18b4874e4 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Mar 2020 14:24:38 -0700 Subject: [PATCH 12/14] fixed sensors for debug mode --- src/debug_user_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug_user_code.py b/src/debug_user_code.py index cfb7544e9..cd3fd0602 100644 --- a/src/debug_user_code.py +++ b/src/debug_user_code.py @@ -27,7 +27,7 @@ # This import must happen after the sys.path is modified from adafruit_circuitplayground.express import cpx -from micropython.microbit.__model.microbit_model import __mb as mb +from microbit.__model.microbit_model import __mb as mb from common import debugger_communication_client From f2ac9c3471a1101789e2f3938a6be491b6c7b034 Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 16 Mar 2020 13:05:31 -0700 Subject: [PATCH 13/14] fixed pylint error for new microbit libs --- src/extension.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index bd120591c..b154c07cc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1286,12 +1286,17 @@ const updatePylintArgs = (context: vscode.ExtensionContext) => { context.extensionPath, CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY ); + const micropythonPath: string = utils.createEscapedPath( + context.extensionPath, + CONSTANTS.FILESYSTEM.OUTPUT_DIRECTORY, + CONSTANTS.FILESYSTEM.MICROPYTHON_DIRECTORY + ); // update pylint args to extend system path // to include python libs local to extention updateConfigLists( "python.linting.pylintArgs", - ["--init-hook", `import sys; sys.path.append(\"${outPath}\")`], + ["--init-hook", `import sys; sys.path.extend([\"${outPath}\",\"${micropythonPath}\"])`], vscode.ConfigurationTarget.Workspace ); }; From 61473784ccb967daba17294cebcdb55d6fbbe067 Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 16 Mar 2020 13:06:43 -0700 Subject: [PATCH 14/14] formatting --- src/extension.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index b154c07cc..2098041b7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1296,7 +1296,10 @@ const updatePylintArgs = (context: vscode.ExtensionContext) => { // to include python libs local to extention updateConfigLists( "python.linting.pylintArgs", - ["--init-hook", `import sys; sys.path.extend([\"${outPath}\",\"${micropythonPath}\"])`], + [ + "--init-hook", + `import sys; sys.path.extend([\"${outPath}\",\"${micropythonPath}\"])`, + ], vscode.ConfigurationTarget.Workspace ); };