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

Commit

Permalink
Added light, temperature and accelerometer on python side (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
vandyliu authored Feb 12, 2020
1 parent a8cd70d commit 2e4e326
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 31 deletions.
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

0 comments on commit 2e4e326

Please sign in to comment.