Created
November 23, 2014 13:40
-
-
Save dead-claudia/1336264efb1731bd0f60 to your computer and use it in GitHub Desktop.
A dummy equivalent implementation for the Finch Robot Python API. Saved me so much hassle in multiple CS assignments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Finch Robot API Dummy Implementation | |
# ==================================== | |
# | |
# This is simply a dummy API implementation, printing each instruction to the | |
# console. It has been very faithful in helping me debug multiple CS | |
# assignments without constantly having to plug in the Finch robot. Various use | |
# cases I've experienced that this saved me a lot of hassle include sparing me | |
# from distraction from my 9 year old brother (10 years age difference is | |
# fun...), being more able to work on this at night, and being able to debug | |
# without the robot itself being with me. | |
# | |
# I thought this would be best posted online as a gist to where a lot more | |
# people could have access to it, catching bugs, or even use it themselves. | |
# | |
# If you're curious about the API, the docstrings are potentially more complete | |
# than the bundled PDF of the original drivers in API documentation. Also, | |
# unlike the original drivers, this does type-check in debug mode. | |
# | |
# If you're unfamiliar with the license, don't fear it. It actually legally | |
# guarantees you're allowed to do a lot more with it, protecting both of us. | |
# | |
# This file by Isiah Meadows is licensed under the BSD 2-Clause License: | |
# | |
# Copyright (c) 2014, Isiah Meadows | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions are met: | |
# | |
# 1. Redistributions of source code must retain the above copyright notice, | |
# this list of conditions and the following disclaimer. | |
# | |
# 2. Redistributions in binary form must reproduce the above copyright notice, | |
# this list of conditions and the following disclaimer in the documentation | |
# and/or other materials provided with the distribution. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
# POSSIBILITY OF SUCH DAMAGE. | |
from time import sleep, monotonic | |
from threading import Timer | |
from numbers import Real | |
# Arbitrary constants. Changed through dummyfinch.set_constant(name, value) | |
_c_defaults = { | |
'LIGHT_LEFT_SENSOR_READING': 0.0, | |
'LIGHT_RIGHT_SENSOR_READING': 0.0, | |
'TEMPERATURE_READING': 30.0, | |
'ACCELERATION_X_READING': 0.0, | |
'ACCELERATION_Y_READING': 0.0, | |
'ACCELERATION_Z_READING': 0.0, | |
'ACCELERATION_TAP': False, | |
'ACCELERATION_SHAKE': False, | |
'OBSTACLE_LEFT_SENSOR_READING': False, | |
'OBSTACLE_RIGHT_SENSOR_READING': False | |
} | |
_c = _c_defaults | |
# Types to check with dummyfile.set_constant(name, value) | |
_types = { | |
'LIGHT_LEFT_SENSOR_READING': float, | |
'LIGHT_RIGHT_SENSOR_READING': float, | |
'TEMPERATURE_READING': float, | |
'ACCELERATION_X_READING': float, | |
'ACCELERATION_Y_READING': float, | |
'ACCELERATION_Z_READING': float, | |
'ACCELERATION_TAP': bool, | |
'ACCELERATION_SHAKE': bool, | |
'OBSTACLE_LEFT_SENSOR_READING': bool, | |
'OBSTACLE_RIGHT_SENSOR_READING': bool | |
} | |
# Print a message from the Finch class, properly formatted. | |
def _print(msg): | |
print('Dummy Finch: ** %s **' % (msg)) | |
# Print a message, coercing the second argument to a string if it isn't | |
# already. | |
def _print_coerce(msg, t): | |
_print('%s: %s' % (msg, str(t))) | |
# Get the proper string representation of a speed. Examples: | |
# | |
# -1.0 -> "1.00000 BACKWARD" | |
# 1.0 -> "1.00000 FORWARD" | |
# 0.0 -> "STOPPED" | |
def _get_response(val): | |
if val: | |
if val > 0: | |
direction = 'FORWARD' | |
else: | |
direction = 'BACKWARD' | |
val = -val | |
return '%s %f' % (direction, min(val, 1.0)) | |
else: | |
return 'STOPPED' | |
# Here's the nice and meaty dummy implementation. | |
class Finch: | |
""" | |
A class representing a Finch robot. This just so happens to be a dummy | |
API implementation, purely meant for debugging. It also features type | |
asserts, to assist in debugging (e.g. not setting wheel speed to an | |
object). | |
The API is occasionally more verbosely documented here than in the PDF | |
reference. Just import this module in the REPL and type | |
`help(dummyfinch.Finch)` (or something different, based on how you imported | |
it) to access this documentation. | |
The API documentation for each method is in the following format: | |
type(argument_one) - description | |
type(argument_two) - description | |
return type - description | |
(or: `return void` for functions that don't have a return value) | |
Irrelevant sections are omitted for brevity | |
""" | |
def __init__(self): | |
""" | |
Initialize a Finch instance. | |
""" | |
self._active_end = 0 | |
self._active_callback = None | |
self._buzzer_silent = True | |
_print('CONNECTED') | |
# A callback function used for .buzzer() | |
def _callback(self): | |
'Private' | |
_print('BUZZ: stopped') | |
self._active_callback = None | |
self._buzzer_silent = True | |
# A method used to reset and/or start a buzzer. Uses threading.Timer to | |
# asynchronously set a timer for when to shut off the "buzzer". | |
def _reset(self, timeout): | |
'Private' | |
if self._active_callback is None: | |
self._active_callback = Timer(timeout, self._callback) | |
self._active_callback.start() | |
elif self._active_end > timeout + monotonic(): | |
self._active_callback.cancel() | |
self._active_callback = Timer(timeout, self._callback) | |
self._active_callback.start() | |
def led(self, *args): | |
""" | |
Control three LEDs (orbs). | |
1 argument: | |
str(color) - hex color string | |
Example: `.led('#00FF8B')` | |
3 arguments: | |
int(r) - red value | |
int(g) - green value | |
int(b) - blue | |
Example: `.led(0, 255, 139)` | |
return void - This is a function that does not return a value. | |
""" | |
if len(args) == 3: | |
assert type(args[0]) is int, 'r must be of type int' | |
assert type(args[1]) is int, 'g must be of type int' | |
assert type(args[2]) is int, 'b must be of type int' | |
# Convert each argument into an RGB value. | |
r, g, b = [int(x) % 256 for x in args] | |
elif len(args) == 1: | |
assert type(args[0]) is str, 'color must be of type str' | |
# Strip surrounding whitespace as to ease parsing. | |
color = args[0].strip() | |
assert len(color) == 7, 'len(color) must be 7' | |
assert color[0] == '#', "color must start with the character '#'" | |
if len(color) == 7 and color[0] == '#': | |
r = int(color[1:3], 16) | |
g = int(color[3:5], 16) | |
b = int(color[5:7], 16) | |
else: | |
# This fixes a bug in the initial string parsing, where the | |
# initial Finch API silently attempted improperly to set wheel | |
# speed to uninitialized values. | |
return | |
else: | |
assert False, 'args must be of length 3 or 1' | |
return | |
_print_coerce('LED', (r, g, b)) | |
def buzzer(self, duration, frequency): | |
""" | |
Outputs sound. Does not wait until a note is done beeping. | |
float(duration) - duration to beep, in seconds (s). | |
int(frequency) - frequency, in hertz (HZ). | |
return void - This is a function that does not return a value. | |
""" | |
assert type(duration) is float, 'duration must be of type float' | |
assert type(duration) is int, 'frequency must be of type int' | |
# The threading.Timer class operates off of miliseconds, not seconds. | |
millisec = int(duration * 1000) | |
if self.buzzer_silent: | |
self.buzzer_silent = False | |
# And, we're buzzing... | |
_print('BUZZ: %s ms, %s hz' % (millisec)) | |
self._reset(duration) | |
# Note that this is synchronous/blocking. | |
def buzzer_with_delay(self, duration, frequency): | |
""" | |
Outputs sound. Waits until a note is done beeping. | |
Real(duration) - duration to beep, in seconds (s). | |
int(frequency) - frequency, in hertz (HZ). | |
return void - This is a function that does not return a value. | |
""" | |
assert isinstance(duration, Real), 'duration must be a real number' | |
assert type(duration) is int, 'frequency must be of type int' | |
self.buzzer(duration, frequency) | |
sleep(duration*1.05) | |
def light(self): | |
""" | |
Get light sensor readings. The values ranges from 0.0 to 1.0. | |
return (float, float) - a tuple (left, right) of each sensor's | |
readings. | |
""" | |
t = (_c['LIGHT_LEFT_SENSOR_READING'], _c['LIGHT_RIGHT_SENSOR_READING']) | |
_print_coerce('LIGHT', t) | |
return t | |
def obstacle(self): | |
""" | |
Get obstacle sensor readings. | |
return (bool, bool) - a tuple (left, right), True if there is an object | |
in front of it. | |
""" | |
t = (_c['OBSTACLE_LEFT_SENSOR_READING'], | |
_c['OBSTACLE_RIGHT_SENSOR_READING']) | |
_print_coerce('OBSTACLE', t) | |
return t | |
def temperature(self): | |
""" | |
Gets the temperature, according to the Finch. | |
return float - The temperature in degrees Celcius. | |
""" | |
t = _c['TEMPERATURE_READING'] | |
_print_coerce('TEMP', t) | |
return t | |
# This is rarely used, and is mostly irrelevant for this implementation. It | |
# is only implemented for completeness. | |
def convert_raw_accel(self, a): | |
""" | |
Converts the raw acceleration obtained from the hardware into G's | |
int(a) - The hardware reading. This should be 0 >= a >= 31, and in this | |
specific module, is checked as an invariant in debug mode. | |
return float - The acceleration in G's | |
""" | |
assert 0 <= a < 32, 'a should be within the hardware constraints of 0 >= a >= 31' # noqa | |
if a > 31: | |
# a isn't limited to just 6 bits. This works for any size. The | |
# actual Finch API makes a assumption that works for all hardware | |
# output, but not for all input. | |
a = (a % 32) - 32 | |
return a * 1.6 / 32.0 | |
def acceleration(self): | |
""" | |
Returns the (x, y, z, tap, shake). x, y, and z, are the acceleration | |
readings in units of G's, and range from -1.5 to 1.5. | |
When the finch is horizontal, z is close to 1, x, y close to 0. | |
When the finch stands on its tail, y, z are close to 0, x is close to | |
-1. | |
When the finch is held with its left wing down, x, z are close to 0, | |
y is close to 1. | |
tap, shake are boolean values -- true if the corresponding event has | |
happened. | |
return (int, int, int, bool, bool) - in order, the x, y, z, tap, and | |
shake values. | |
""" | |
t = (_c['ACCELERATION_X_READING'], | |
_c['ACCELERATION_Y_READING'], | |
_c['ACCELERATION_Z_READING'], | |
_c['ACCELERATION_TAP'], | |
_c['ACCELERATION_SHAKE']) | |
_print_coerce('ACCELERATION', t) | |
return t | |
def wheels(self, left, right): | |
""" | |
Controls the left and right wheels. | |
Values must range from -1.0 (full throttle reverse) to | |
1.0 (full throttle forward). | |
use left = right = 0.0 to stop. | |
return void - This is a function that does not return a value. | |
""" | |
assert isinstance(left, Real), 'left must be of type float or int' | |
assert isinstance(right, Real), 'right must be of type float or int' | |
assert -1.0 <= left <= 1.0, 'left must satisfy -1.0 >= left >= 1.0' | |
assert -1.0 <= right <= 1.0, 'right must satisfy -1.0 >= right >= 1.0' | |
left_response = _get_response(left) | |
right_response = _get_response(right) | |
_print('WHEELS: left: %s, right: %s' % (left_response, right_response)) | |
def halt(self): | |
""" | |
Set all motors and LEDs to off. | |
return void - This is a function that does not return a value. | |
""" | |
# This does a *lot* more in the actual driver API. | |
_print('HALT') | |
def close(self): | |
""" | |
Close the current Finch instance. | |
return void - This is a function that does not return a value. | |
""" | |
# This does a *lot* more in the actual driver API. | |
_print('CLOSED') | |
# Better to check against a reference than equality to anything. None can't be | |
# used, because it still doesn't cover all possible input. | |
_default = object() | |
def set_constant(name, value=_default): | |
""" | |
Set a hard-coded constant used by this module's class instance methods. | |
This is an additional method that does not exist in the actual Finch driver | |
API. | |
Valid names, their respective types, and their default settings are below: | |
'LIGHT_LEFT_SENSOR_READING': float, 0.0 | |
'LIGHT_RIGHT_SENSOR_READING': float, 0.0 | |
'TEMPERATURE_READING': float, 30.0 | |
'ACCELERATION_X_READING': float, 0.0 | |
'ACCELERATION_Y_READING': float, 0.0 | |
'ACCELERATION_Z_READING': float, 0.0 | |
'ACCELERATION_TAP': bool, False | |
'ACCELERATION_SHAKE': bool, False | |
'OBSTACLE_LEFT_SENSOR_READING': bool, False | |
'OBSTACLE_RIGHT_SENSOR_READING': bool, False | |
Each setting should be relatively self-descriptive. | |
Arguments: | |
str(name) - The name to modify. | |
any(value) - The value to set. Omit to remove the custom setting, i.e. | |
reset the setting to the default value. This is converted to | |
its type as per above, and in debug mode, the type is strictly | |
enforced. | |
return void - This is a function that does not return a value. | |
""" | |
assert type(name) is str, 'name must be of type str' | |
name = str(name) | |
try: | |
if value is _default: | |
# Set default if the optional parameter isn't passed. | |
_c[name] = _c_defaults[name] | |
else: | |
assert type(value) is _types[name] | |
_c[name] = _types[name](value) | |
except KeyError as e: | |
# Alias the error as a more applicable type. | |
raise ValueError('name must be a valid constant.') from e |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment