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

Commit

Permalink
Merge remote-tracking branch 'origin/users/t-anmah/sensor-integration…
Browse files Browse the repository at this point in the history
…' into users/t-xunguy/sensors
  • Loading branch information
xnkevinnguyen committed Feb 11, 2020
2 parents 64491e4 + 7d6b4c8 commit 4f4338e
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 6 deletions.
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()
129 changes: 129 additions & 0 deletions src/microbit/__model/accelerometer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import enum

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 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 __set_x(self, x):
self.__x = self.__get_valid_acceleration(x)

def __set_y(self, y):
self.__y = self.__get_valid_acceleration(y)

def __set_z(self, z):
self.__z = self.__get_valid_acceleration(z)

def __get_valid_acceleration(self, acceleration):
if acceleration < CONSTANTS.MIN_ACCELERATION:
return CONSTANTS.MIN_ACCELERATION
elif acceleration > CONSTANTS.MAX_ACCELERATION:
return CONSTANTS.MAX_ACCELERATION
else:
return acceleration

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 axis == "x":
self.__x = self.__get_valid_acceleration(accel)
elif axis == "y":
self.__y = self.__get_valid_acceleration(accel)
elif axis == "z":
self.__z = self.__get_valid_acceleration(accel)

def __set_gesture(self, gesture):
if gesture in CONSTANTS.GESTURES:
self.__current_gesture = gesture
else:
self.__current_gesture = ""

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)
25 changes: 25 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,7 @@
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"

TIME_DELAY = 0.03

Expand Down
15 changes: 11 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,19 @@ 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:
self.__light_level = CONSTANTS.MIN_LIGHT_LEVEL
elif level > CONSTANTS.MAX_LIGHT_LEVEL:
self.__light_level = CONSTANTS.MAX_LIGHT_LEVEL
else:
self.__light_level = level

# Helpers

Expand Down
43 changes: 42 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,13 @@
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 +28,17 @@ 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:
self.__temperature = CONSTANTS.MIN_TEMPERATURE
elif temperature > CONSTANTS.MAX_TEMPERATURE:
self.__temperature = CONSTANTS.MAX_TEMPERATURE
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 All @@ -38,5 +52,32 @@ def update_state(self, new_state):
else:
button._Button__release()

# set motion_x, motion_y, motion_z
for name, direction in CONSTANTS.EXPECTED_INPUT_ACCEL_MICROBIT:
previous_motion_val = self.accelerometer._Accelerometer__get_accel(
direction
)
new_motion_val = new_state.get(name, previous_motion_val)
if new_motion_val != previous_motion_val:
self.accelerometer._Accelerometer__set_accel(
direction, new_motion_val
)

# set temperature
previous_temp = self.temperature()
new_temp = new_state.get(
CONSTANTS.EXPECTED_INPUT_TEMP_MICROBIT, previous_temp
)
if new_temp != previous_temp:
self._MicrobitModel__set_temperature(new_temp)

# set light level
previous_light_level = self.display.read_light_level()
new_light_level = new_state.get(
CONSTANTS.EXPECTED_INPUT_LIGHT_MICROBIT, previous_light_level
)
if new_light_level != previous_light_level:
self.display._Display__set_light_level(new_light_level)


__mb = MicrobitModel()
96 changes: 96 additions & 0 deletions src/microbit/test/test_accelerometer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import pytest
from unittest import mock

from ..__model import constants as CONSTANTS
from ..__model.accelerometer import Accelerometer


class TestAccelerometer(object):
def setup_method(self):
self.accelerometer = Accelerometer()

@pytest.mark.parametrize(
"accel, expected",
[
(CONSTANTS.MIN_ACCELERATION - 10, CONSTANTS.MIN_ACCELERATION),
(CONSTANTS.MIN_ACCELERATION, CONSTANTS.MIN_ACCELERATION),
(100, 100),
(CONSTANTS.MAX_ACCELERATION, CONSTANTS.MAX_ACCELERATION),
(CONSTANTS.MAX_ACCELERATION + 1, CONSTANTS.MAX_ACCELERATION),
],
)
def test_x_y_z(self, accel, expected):
self.accelerometer._Accelerometer__set_x(accel)
assert expected == self.accelerometer.get_x()
self.accelerometer._Accelerometer__set_y(accel)
assert expected == self.accelerometer.get_y()
self.accelerometer._Accelerometer__set_z(accel)
assert expected == self.accelerometer.get_z()

@pytest.mark.parametrize(
"accels, expected",
[
((23, 25, 26), (23, 25, 26)),
((204, 234, -534), (204, 234, -534)),
(
(CONSTANTS.MIN_ACCELERATION - 10, 234, CONSTANTS.MAX_ACCELERATION),
(CONSTANTS.MIN_ACCELERATION, 234, CONSTANTS.MAX_ACCELERATION),
),
],
)
def test_get_values(self, accels, expected):
self.accelerometer._Accelerometer__set_x(accels[0])
self.accelerometer._Accelerometer__set_y(accels[1])
self.accelerometer._Accelerometer__set_z(accels[2])
assert expected == 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()
Loading

0 comments on commit 4f4338e

Please sign in to comment.