Skip to content

Runtime

benliao1 edited this page Jul 28, 2023 · 11 revisions

Welcome to the PiE Runtime repo! If you're new a staff member, then welcome to PiE, and welcome to the Runtime project! We're so glad to have you with us, and that you chose to give Runtime a shot! This Wiki will hopefully be a nice overview of what Runtime is all about and how it's built, and answer some basic questions you might have about our project and this code. If you have any questions, please ask the Runtime project manager(s)! We highly encourage you to read through the whole thing, it'll be really useful, we promise! Without further ado, let's get started...

What is Runtime?

As you know, Pioneers in Engineering is a student organizations that hosts a robotics competition for underserved high schools in the SF Bay Area. To do this, we build everything in house, including our software. So, when the students get their robotics kits to build their robots, it includes motors, sensors, servos, gears, metal, and a computer to actually run their robot. Runtime is responsible for writing the software that runs on that computer (a Raspberry Pi, at the time of this writing), as well as the software that runs on the Arduinos attached to all the motors, sensors, and servos.

Students also write their own code to control their robots, which they upload to the robot for it to run. Most of their code consists of things like "when the Y button on the gamepad is pressed, I want to move this servo to this position" which does something that scores points. The code that Runtime writes for the Raspberry Pi needs to be able to run the student's code and translate that into actual commands to the sensors on the robots.

Students use the "Dawn" computer application (also made by PiE) running on their laptops to write their code, connect their Xbox controllers (we call them "gamepads" in Runtime) to, and view the current state of the various devices attached to their robot. Runtime's software must be able to communicate with Dawn to receive commands and student code, and report device data back to Dawn.

During the competition, the software "Shepherd" (another PiE software project) is used to control the field and run the game. Shepherd connects to the robots in order to tell them which side of the field they're on, where the robots are starting within each side, and when to go into autonomous (auto) mode, teleoperated (teleop) mode, and idle mode. Runtime's software must be able to communicate with Shepherd to receive these commands and report any status updates back to Shepherd.

The devices that are attached to the robot (motors, servos, sensors) must, of course, interact with the software on the Raspberry Pi. When the student's code wants to command a motor or servo to a new speed or position, it needs to send that information to the Arduino on the device that is controlling it. When the student's code wants to read the value of a sensor, the software on the Raspberry Pi must make that information available to it. And, the software on the Raspberry Pi must be able to detect when new devices have connected, or devices have been disconnected (unplugged) from the robot. Runtime is responsible for ensuring all of this behavior.

So, as you can see, Runtime does a lot! But, how does it all work?

Design Principles

Runtime is all about speed and reliability.

It needs to be fast, so that student code doesn't lag, so that inputs from the gamepads don't lag, and so that it can handle as many devices as the students would ever put onto their robot without crashing or noticeable slowing down of the system. This guided a lot of our early design decisions, and is part of the reason why we chose to write the entire system in C. 1

Runtime also needs to be easy to debug and very robust, because nothing frustrates a student more than when their robot breaks while they're working (or worse, during a competition match), and the cause of the failure was not due to faulty student code but due to faulty robot software (i.e. Runtime crashing). They get even more frustrated when they bring the broken robot to PiE staff members, who then take hours to try and solve the problem because the staff members can't debug the problem efficiently.

Lastly, Runtime should be as easy to understand, set up, and maintain as possible, because the last thing we want is for the learning curve for staff to become so steep that it becomes impossible to keep the code stable, simple, and healthy.

We're not going to sugarcoat it here: Runtime is complex by nature. It has a steep learning curve. It will require effort and dedication to learn and contribute to. However, we have tried our very best to reduce this complexity, make the learning curve more gentle, and ease the onboarding process through a combination of careful design, extensive documentation, and attention to detail, while still achieving the design goals. And in the end, when students use their robots and they don't break... it feels awesome to know that you contributed to their good experience with PiE, maybe even inspired them to go into STEM, while you also hopefully learned a lot by joining Runtime!

With that said, we now see the need for the following design principles:

  • Keep It Simple, Stupid (KISS). In Runtime, this means that we want to keep the number of dependencies on third-party libraries to a minimum. This reduces the complexity of building Runtime. Third party-libraries also often have incomplete or questionable documentation; having fewer third-party dependencies means that there is less documentation new staff members have to read in order to begin understanding and working on Runtime.
  • Document, document, document. As you can probably tell by the length and detail in this wiki, we take our documentation seriously. Mark your TODO items. Describe your functions. Name things well. Write and update READMEs and the wiki whenever possible. We need to document everything to make it easy for staff to onboard, and for seasoned staff to maintain and extend the code.
  • Be Able to Log Anywhere. Runtime has a logger that can send logs from anywhere within the system to a file, terminal, or over the network to Dawn if Dawn is connected. It is crucial that this logger be maintained, and for log statements to be added into the program wherever necessary (including in the Arduino devices). When a problem arises, staff must be able to turn on these logs, allowing them to see exactly what is happening in the system from somewhere, which enables efficient debugging.
  • Consider All Possibilities. Try to consider all the errors and situations possible. What should happen in each case? Should Runtime restart? Should it handle the error and continue as normal? Should it revert to some default behavior? This all helps make Runtime as robust as possible, helps us debug efficiently when Runtime crashes, and helps keep students happy when Runtime doesn't crash.
  • Test, test, test. The testing framework and continuous integration tool (Github Workflows) is there for a reason! Use it! Document the behavior changes, write tests that demonstrate that the behavior of the system is as expected, etc.

Overall Structure

Runtime can be divided into a few neatly containerized parts:

  • The Network Handler: abbreviated net_handler, this is a process in Runtime, and is responsible for all communication between the Raspberry Pi and Dawn, and between the Raspberry Pi and Shepherd.
  • The Device Handler: abbreviated dev_handler, this is a process in Runtime, and is responsible for all communication between the Raspberry Pi and all the devices that are attached to the robot at any given time.
  • The Executor: this is a process in Runtime, and is responsible for running the student code that is uploaded to the robot from Dawn.
  • The Network Switch: this is a process in Runtime, and is responsible for getting the Raspberry Pi to switch Wifi networks on demand.
  • The Shared Memory Wrapper: abbreviated shm_wrapper, this is the tool that facilitates the efficient communication between the above three processes. Think of this wrapper as the "glue" that holds the three processes together, and lets them talk to each other.
  • The Logger: this is the tool that Runtime uses to gather all the logs generated at various places in the code (including by student code) and outputs them to a terminal window, to a file, to Dawn over the network, or to some combination of the three.
  • The Device Code: codenamed "lowcar" 2 this is the set of all the code on the Arduinos that directly control an individual device that is connected to the robot. All of this code is collectively called the "lowcar library".
  • The Systemd Services: this is a collection of system services that allow Runtime to start automatically when the Raspberry Pi is turned on and recover when Runtime crashes.
  • The Runtime Utility: this is a collection of helper functions and defined constants that are used throughout Runtime. Most of them have to do with certain Runtime configuration values, maximum sizes for certain messages, or retrieving information about the different types of lowcar devices.
  • The Test Framework: contained within the tests folder, this is a set of tools and folders that allow us to both manually and automatically run tests to verify the behavior of Runtime. These include clients to net_handler, executor, executor, and shm_wrapper; and command-line tools for these clients (known in the business as "CLI"'s, for "Command Line Interface").

Next, we will describe briefly the data that flows between the various parts, and the manner in which that data is sent:

  • net_handler communicates with both Dawn and Shepherd. It receives start and stop commands, and information about the student's inputs to the robots (via their keyboards or an Xbox 360 controller). It sends log messages and data about the connected lowcar devices. This data is sent over a Wifi network via TCP to Shepherd and Dawn, and the data is packaged using Google Protocol Buffers.
  • dev_handler and net_handler are connected via shm_wrapper. This connection is used for the net_handler to fetch the most recent state of the connected devices, which is then sent to Dawn for the students to see.
  • executor and net_handler are connected via shm_wrapper. The connection is used for the executor to know what mode the robot should be in at any given time (autonomous mode, teleop mode, or idle).
  • executor and dev_handler are connected via shm_wrapper. This connection is used for executor to send commands to the attached devices (tell a motor to run at a certain speed, move a servo to certain position, etc.), and for dev_handler to serve executor with the device data that it needs to run the student code.
  • dev_handler communicates with lowcar via serial connection. This connection is used to poll for new devices, detect when devices have disconnected, and send device data, logs, and commands between the Raspberry Pi and the Arduinos.
  • All three processes output log messages through the logger tool. If logs are to be sent over the network to Dawn, those logs are put into a FIFO pipe which is opened by net_handler, where the logs are processed and sent to Dawn.

Footnotes

1: Previous iterations of Runtime were written in Python; whether or not this was a good choice is up to the reader's discretion.

2: This name has an interesting origin. The code for the devices used to be in a completely separate PiE project called "Hibike" (pronounced HEE-bee-kay). Some members of Hibike who wrote much of the original Arduino code were avid fans of anime. So, they named the project Hibike after the anime "Hibike! Yufoniamu" (Sound! Euphonium). Later, some members who were not such avid fans of anime decided to pronounce "Hibike" as "HAI-bike", which sounds like the two English words "High Bike". Later still, when members decided to refactor the code to be more object-oriented, it was decided that a new name was needed for the project. Ben Liao coined the name "Low Car" as wordplay on "High Bike", and the name stuck.