Skip to content

Student API

Ashwin Vangipuram edited this page Jun 17, 2021 · 13 revisions

The student API is the set of all functions that the students have available to them to communicate with the robot. This page has two overall sections. The first section is intended as documentation for the student API that can be used as reference for website updates, PiE Robot Simulator, and staff intending to learn how the PiE Robot API works. The second section is intended as a deeper dive into the inner workings of the API for Runtime developers to read.

PiE Student API Documentation

As of June 17th, 2021

Glossary

  • Run Mode: One of IDLE, AUTO, or TELEOP.
    • IDLE: Robot is stopped and no code is being executed.
    • AUTO: autonomous_setup() and autonomous_main() are executed as described below. Using Gamepad and Keyboard are invalid.
    • TELEOP: teleop_setup() and teleop_main() are executed as described below. Using Gamepad and Keyboard are valid and encouraged.
      • Note for Spring 2021, Gamepad is not allowed!
  • Setup functions: teleop_setup() and autonomous_setup()
    • When AUTO begins, autonomous_setup() is called once.
    • Same applies to TELEOP.
  • Main loop functions: teleop_main() and autonomous_main()
    • During AUTO and after autonomous_setup() completes, autonomous_main() is repeatedly called until the AUTO run mode comes to an end.
    • Same applies to TELEOP.

Robot Class

The Robot class has 1 public member start_pos which will be a string of either 'left' or 'right' signifying what the robot start position is.

Robot.get_value(device_id, param)

The get_value function returns the current value of the specified param of the device with the specified device_id (which is of the form <device_type>_<device_uuid>).

  • device_id string - the ID that specifies which PiE device will be read
  • param string - identifies which parameter on the specified PiE device will be read. Possible param values depend on the specified device. Find a list of params for each device on the lowcar devices page

This function is useful for checking the state of certain parts of your robot while it is driving. For example, calling this function with a Limit Switch Sensor's ID and “switch0” would get the value True if the first Limit Switch was pressed down and False if it was not. Possible devices include:

  • LimitSwitch
  • LineFollower
  • ServoControl
  • PolarBear
  • KoalaBear
  • BatteryBuzzer

Available for Spring 2021:

  • LineFollower
  • KoalaBear

Robot.set_value(device_id, param, value)

The set_value function sets the specified value of the specified param of the device with the specified device_id (which is of the form <device_type>_<device_uuid>).

  • device_id string - the ID that specifies which PiE device will have its parameter set
  • param string - identifies which parameter on the specified PiE device will be set to the provided value. Possible values/parameters depend on the specified device. Find a list of parameters that can be set for each device on the lowcar devices page.
  • value <int, bool, float> - the value to set the parameter to

This function is useful for changing the state of certain parts of your robot while it is driving. For example, calling this function with a KoalaBear's ID, the parameter “velocity_a”, and the value -1.0 would result in the motor attached to the KoalaBear to spin backwards at full power. Possible devices include:

  • ServoControl
  • KoalaBear

Available for Spring 2021:

  • KoalaBear

Robot.run(function_name, args...)

Executes another function with the list of args passed into the function as arguments to said function. The function specified by function_name is ran simultaneously and independently from the setup functions and main loop functions. This is also known as "spawning a thread."

  • function_name: the name of a function in the student code which will be run simultaneously with the main loop
  • args...: this is a list of zero or more inputs, which will be passed to the function_name specified previously as arguments (inputs)

This will run the specified function in the background until the function returns, or when the run mode changes (whichever is first). You may want to use Robot.run() in teleop_setup() or autonomous_setup(). For example, if you want a function arm_code() to control the arm that runs at the same time as teleop_main(), call Robot.run(arm_code) in teleop_setup():

# Example that uses arguments with Robot.run()
def counter(start, should_print):
    i = start
    while True:
        i = i + 1
        if should_print:
            print("Counter is " + i)
        Robot.sleep(1)

def arm_code():
    while True:
        # Control arm

def teleop_setup():
    # Execute functions counter() and arm_code() during teleop_main()
    Robot.run(counter, 42, True)
    Robot.run(arm_code)

def teleop_main():
    # Wheels code

Note that it's invalid to have multiple instances of Robot.run() on the same function at the same time. It's very unlikely you will ever need to do this... please! If, for some reason, you want to call Robot.run() multiple times on the same function, use Robot.is_running() to check if the previous Robot.run() is finished.

Note that unlike previous versions of the API, this does not involve the use of async or await.

Note that there is a maximum number of functions that can be run with Robot.run() at the same time. Additional calls will do nothing and print a warning. As of this writing, the maximum is 8. This may be changed but this is in the right ballpark.

Robot.is_running(function_name)

Returns a boolean value (True or False) indicating whether the specified function is still running. In most cases, you will not need to use this function.

  • function_name: the name of a function defined by the student that may or may not have been run using Robot.run

Robot.sleep(seconds)

Pauses the execution of the current function for the specified number of seconds.

  • seconds: the number of seconds to pause the execution of the current function for.

It should be emphasized that calling Robot.sleep in one function does not cause any other function that is being run by Robot.run or the main loop function to pause. Only the instance of the function that is executing the call to Robot.sleep will pause execution. It is highly recommended to not use this function in the setup functions or main loop functions--only in functions executed by Robot.run().

Gamepad Class

Gamepad.get_value(name_of_input)

Returns the state of a button or joystick of a connected gamepad.

  • name_of_input string: identifies which button or joystick value will be returned

This function is useful for checking the state of buttons or joysticks of your gamepad or controller. For example, calling this function with ”button_a” would result in True if the A button is pressed down and False if it is not.

This function is essential for controlling your robot with the gamepad. The possible values for name_of_input are:

  • "joystick_left_x"
  • "joystick_left_y"
  • "joystick_right_x"
  • "joystick_right_y"
  • "button_a"
  • "button_b"
  • "button_x"
  • "button_y"
  • "l_bumper"
  • "r_bumper"
  • "l_trigger"
  • "r_trigger"
  • "button_back"
  • "button_start"
  • "l_stick"
  • "r_stick"
  • "dpad_up"
  • "dpad_down"
  • "dpad_left"
  • "dpad_right"
  • "button_xbox"

Users should be careful to distinguish between values such as “l_stick” and values such as “joystick_left_x”. “l_stick” returns whether the joystick has been depressed like a button. “joystick_left_x” returns how far the joystick is moved on the x-axis.

When a button is passed in, this function returns a boolean value: True if the button is pressed, and False if the button is not pressed. When a joystick is passed in, this function returns a floating-point (decimal) value between -1.0 and 1.0 (inclusive), which correspond to the extremes of movement for the given joystick.

Keyboard Class

Keyboard.get_value(name_of_key)

Returns the state of the specified key on the keyboard.

  • name_of_key string: identifies the key whose state will be returned by this function

This function is essential to controlling your robot with the keyboard. Possible values for name_of_key are:

  • The letters on the keyboard, lowercase and no spaces "a"-"z"
  • The numbers on the keyboard, no spaces "0"-"9"
  • The punctuation keys ",", ".", "/", ";", "'", "[", "]"
  • The four arrow keys "left_arrow", "right_arrow", "up_arrow", "down_arrow"

The function will return True if the button is pressed, and False if the button is not pressed.

Student API Explanation for Runtime Devs

Cython

The student API is written entirely in Cython, which is a static compiler from Python to C. It provides static typing and lets you easily call C functions within Python. You can learn more about Cython at our wiki page https://github.com/pioneers/runtime/wiki/Cython.

In Cython, the .pxd files act as header files while .pyx files act as source files. In runtime.pxd, you will see many extern declarations to C functions in Runtime. In studentapi.pyx, each API function converts the Python arguments into C arguments and calls the corresponding shm_wrapper C functions to access shared memory. We will now discuss each API function in more detail.

API

print

We actually modify the default print function by changing builtins.print so that any print statement used by the student code is instead redirected to log_printf in the logger to be sent over the network. This also includes changing the default sys.stderr file stream to have the exception tracebacks be properly formatted when printed.

Robot.get_value

This function will retrieve a device parameter for a specific device id from shared memory. This uses the device_read_uid function from shm_wrapper. The device id input is of the form {type}_{uid}. The proper exceptions are thrown for invalid parameters.

Robot.set_value

This function will set a device parameter for a specific device id in shared memory. This uses the device_write_uid function from shm_wrapper. The device id input is of the form {type}_{uid}. The proper exceptions are thrown for invalid parameters.

Robot.run

This is a purely Python function that will start the given function in a separate thread using the threading module. We have to create a new Thread subclass called ThreadWrapper to have us that will properly print out the exception traceback. It will also set a threading.Event to ensure that exceptions in this action thread are correctly raised to the executor to stop running the mode.

Robot.is_running

This just returns whether the given function is being run asynchronously in another thread.

Robot.log

This allows students to log values other than device parameters on the Dawn dashboard. This uses the log_data_write function from shm_wrapper. It will also raise an exception if the type of the value is not an int, float, or bool.

Robot.sleep

We had some issues figuring out how to cancel running the student code when it is busy doing other calculations or when it is sleeping. To make this possible, all sleeps in the student code should use Robot.sleep instead of time.sleep. This function is different in that if the sleep is called in the main thread, it will use the threading.Event.wait function, which is cancellable by the executor by calling threading.Event.set. If this is called in an action thread, then it uses the normal time.sleep as these threads are killed immediately when their parent process is killed.

The reason we needed to use the threading.Event is that the Python code is being executed in the main thread of the executor process along with our C code. So if we sleep in the main thread, none of our C error-checking/timeout code will run, which is not good as it is very unsafe if we can't cancel the robot at any point in time.

Gamepad.get_value

This function will receive a gamepad value from shared memory, which is populated by the net_handler. This uses the input_read function in shm_wrapper and will throw an exception if the parameter is invalid.

Keyboard.get_value

This function will retrieve data about the keyboard state from shared memory, which is populated by net_handler. It uses the input_read function in shm_wrapper and will throw an exception if the parameter is invalid.

Device Subscriptions

To reduce the amount of data we communicate over Serial, we have to specify to the Lowcar devices which parameters we want to receive. To know which parameters the students use, we parse the student code using get_all_params to find all devices and their corresponding parameters in any Robot.get_value or Robot.set_value functions using regex. Then we request the subscriptions in executor by calling make_device_subs, which then calls place_sub_requests from shm_wrapper. One significant drawback that still exists with this is that if the devices or parameters are local variables instead of being global variables or literals, then the parsing will break which might cause the student code to give incorrect device values. This is an ongoing bug, and thus the students need to be warned to not do this.

Clone this wiki locally