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

Added light, temperature and accelerometer on python side #196

Merged
merged 22 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
"category": "%deviceSimulatorExpressExtension.commands.label%"
},
{
"command": "deviceSimulatorExpress.closeSerialMonitor",
"title": "%deviceSimulatorExpressExtension.commands.closeSerialMonitor%",
"command": "deviceSimulatorExpress.openSerialMonitor",
"title": "%deviceSimulatorExpressExtension.commands.openSerialMonitor%",
"category": "%deviceSimulatorExpressExtension.commands.label%"
},
{
Expand Down
10 changes: 9 additions & 1 deletion src/microbit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .__model.image import Image
from .__model.microbit_model import __mb

accelerometer = __mb.accelerometer
button_a = __mb.button_a
button_b = __mb.button_b
display = __mb.display
Expand All @@ -21,4 +22,11 @@ def running_time():
Return the number of milliseconds since the board was switched on or
restarted.
"""
__mb.running_time()
return __mb.running_time()


def temperature():
"""
Return the temperature of the micro:bit in degrees Celcius.
"""
return __mb.temperature()
114 changes: 114 additions & 0 deletions src/microbit/__model/accelerometer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from . import constants as CONSTANTS


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.
"""
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.
"""
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.
"""
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.
"""
return (self.__x, self.__y, self.__z)

def current_gesture(self):
"""
Return the name of the current 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.
"""
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.
"""
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.
"""
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)
28 changes: 28 additions & 0 deletions src/microbit/__model/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@
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"
Expand All @@ -120,6 +144,10 @@
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

Expand Down
13 changes: 9 additions & 4 deletions src/microbit/__model/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ class Display:
def __init__(self):
self.__image = Image()
self.__on = True
self.__current_pid = None
self.__light_level = 0
self.__blank_image = Image()

self.__current_pid = None
self.__lock = threading.Lock()

def scroll(self, value, delay=150, wait=True, loop=False, monospace=False):
Expand Down Expand Up @@ -226,13 +227,17 @@ def is_on(self):

def read_light_level(self):
"""
Not implemented yet.

Use the display's LEDs in reverse-bias mode to sense the amount of light
falling on the display. Returns an integer between 0 and 255 representing
the light level, with larger meaning more light.
"""
raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR)
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

Expand Down
18 changes: 17 additions & 1 deletion src/microbit/__model/microbit_model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import time

from .accelerometer import Accelerometer
from .button import Button
from .display import Display
from . import constants as CONSTANTS
Expand All @@ -8,11 +9,14 @@
class MicrobitModel:
def __init__(self):
# State in the Python process
self.accelerometer = Accelerometer()
self.button_a = Button()
self.button_b = Button()
self.__start_time = time.time()
self.display = Display()

self.__start_time = time.time()
self.__temperature = 0

self.microbit_button_dict = {
"button_a": self.button_a,
"button_b": self.button_b,
Expand All @@ -25,6 +29,18 @@ 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):
for button_name in CONSTANTS.EXPECTED_INPUT_BUTTONS:
button = self.microbit_button_dict[button_name]
Expand Down
104 changes: 104 additions & 0 deletions src/microbit/test/test_accelerometer.py
Original file line number Diff line number Diff line change
@@ -0,0 +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()
22 changes: 22 additions & 0 deletions src/microbit/test/test_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,28 @@ def test_async_tests(self):
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)
Expand Down
Loading