Skip to content

Technical Overview

Elias Hawa edited this page Aug 28, 2024 · 1 revision

The ground station repository contains the Python code responsible for processing incoming data packets from the rocket into a format that the ground-station-ui can process.

The main ground-station script (main.py) starts 3 processes. One for handling incoming and outgoing serial data (SerialManager), one for processing commands and telemetry data (Telemetry) and one for relaying to and receiving commands from the ground-station-ui over web sockets (WebSocketHandler)

The processes communicate with each other using shared memory in the form of queues. The queues serve different purposes but are generally used to convey information about the whole stack or to send commands across processes, see here

  • serial_status is used to convey information about the serial connection such as what serial ports are available and which port the RN2483 radio module is connected to. The SerialManager process updates this queue and the Telemetry process reads from it
  • ws_commands is used to hold commands inputted from the web socket interface. This queue is populated by the WebSocketHandler process and is handled by the parse_ws_command function in the main process
    • The parse_ws_command function parses commands from the ws_commands queue and then redirects the command to either the serial_ws_commands or the telemetry_ws_commands queues depending on which process the command was for
    • Both serial_ws_commands and telemetry_ws_commands queues are handled by the SerialManager and Telemetry processes respectively
  • rn2483_radio_input is not currently in use but could be used to send commands to the radio module onboard the rocket
  • radio_signal_report contains any signal reports from the rocket (elaborate)
  • rn2483_radio_payloads contains hexadecimal string payloads from the radio module on board the rocket. This queue is populated by the SerialManager process and is read by the Telemetry process
  • telemetry_json_output contains the JSON output that is used for the front-end display. This queue is populated by the Telemetry process and is read by the WebSocketHandler process.

The processes run in parallel and each has their own while loop that processes their respective queues

SerialManager

The SerialManager process is responsible for all data communication over serial ports

The avionics team uses an RN2483 radio module to transmit telemetry data from the rocket over LoRa radio. On the ground, we receive this data from (and possibly transmit data in the future to) this module using an antenna module connected via serial port using UART.

After being started, the SerialManager begins an infinite while loop that continually checks for commands (in the form of strings) from the serial_ws_commands queue. Two main top-level commands can be processed by the SerialManager

If the command at the head of the queue is just the string update, the SerialManager calls the update_serial_ports function to scan for open serial ports on the host device. The way it does this is dependent on the host platform and the implementation can be found here

If the command at the head of the queue begins with the string rn2483, the SerialManager will either attempt to establish and maintain a connection with a serial port or terminate based on the following logic:

  • If the following string in the command is connect and no connection has been established the next string will be used to determine how the SerialManager connects with the radio module
    • If the string following connect is test, the SerialManager spawns a process to emulate a connection with the radio module (serial_rn2483_emulator.py). This is typically done to test the capabilities of the Serial Manager.
    • Otherwise, the SerialManager spawns a process to establish and maintain a connection with the given serial port using the pySerial library (serial_rn2483_radio.py).
  • If the following string in the command is connect and a connection has already been established, the message Already connected. is logged.
  • If the following string in the command is disconnect and a connection has already been established, the SerialManager terminates the spawned sub-process.
  • If the following string in the command is disconnect and no connection has been established, the SerialManager warns that there is no active connection

Once a connection has been established either via the emulator or an actual connection, the spawned sub-process will continually populate the rn2483_radio_payloads queue with hexadecimal strings representing data packets (if using the emulator, these data packets are purely randomly generated values, if using an actual connection with the radio board, these data packets come from the serial connection).

Telemetry

The Telemetry process is responsible for processing incoming telemetry data and providing a structured output JSON object that can be read by the WebSocketHandler process to update the front end.

Similar to the SerialManager process, the Telemetry process begins at infinite while loop after it is started that continually checks for input (in the form of strings) but from 4 different queues. It does so always in the same order and once a queue is empty, it proceeds to check the next one in the order until it has gone through all the queues at which point it starts over.

  1. The first queue that is checked is the telemetry_ws_commands queue. The beginning of these command strings specifies the command while the rest of the string specifies the parameters for that command. These strings are first parsed into enums representing the specific command - the logic for which can be found in websocket_commands.py - and once the command has been extracted, the command and its parameters are used call the execute_command function which is further documented below.
  2. The second queue that is checked is the radio_signal_report queue. This currently just logs any incoming radio signal data and nothing else
  3. The third queue that is checked is the serial_status queue. This queue contains updates about available serial ports and the connection status of the radio module reported by the SerialManager process. This information is processed by the parse_serial_status method which updates corresponding fields in the Telemetry process. Finally, the update_websocket function is called to update the front end
  4. The fourth queue that is checked depends on whether a mission file is being played back or not. If a mission file is being played back the Telemetry process reads input from the replay_output queue (not shared between processes) which would be populated with the contents of a .mission file. Otherwise, input is read from the radio_payloads queue which would be populated with the output from the SerialManager process. In both cases, each element in the queue represents a single transmission from the rocket in the form of a hexadecimal string, these strings are used to call the process_transmission function where transmissions are decoded to readable data packets. These data packets are used to update the Telemetry process' TelemetryData object which stores the last few copies of received data blocks. Finally, the update_websocket function is called to update the front end

Parsing transmissions

The current packet spec version is v1 so any references to functions or practices refer to this specific version.

Each transmission read from either the replay_output queue or the radio_payloads queue is passed into the parse_rn2483_transmission function from the utils.py file within the telemetry module. From the spec linked above, we know that a transmission packet contains a single header packet header and one or more data blocks.

The first step of parsing transmissions is to extract and decode the packet header. The packet header will always be 32 characters long so we can get the first 32 characters from the transmission and create a PacketHeader object from it. The packet header contains metadata about the transmission such as the callsign that was used to transmit the packet, the packet spec version and its length.

Once the packet header has been extracted, the remaining characters make up the data blocks of the transmission so we continue to process the transmission until we've read all of the characters. Remember that there can be multiple data blocks within each transmission, so each data block is prefaced by 8 characters that make up the data block header (this is also the first 8 characters of the transmission after the packet header has been removed). The data block header contains information about the data block's length, type, subtype and intended destination. This information, in addition to the packet spec version and the hexadecimal contents of the data block, is used to call the parse_radio_block function.

The parse_radio_block function first extracts the data block subtype from the data block header. Each data block subtype has its own packet format outlined here. This allows us to determine how to decode and extract information from encoded data packets. Once the subtype has been determined, we call the static parse function from data_block.py using the data block subtype and contents still in hexadecimal format. The parse function uses the from_bytes method for the corresponding data block subtype to decode the remaining hex contents of the transmission and returns a DataBlock class instance whose specific attributes are populated using the values from the transmission.

A dict representation of the DataBlock class instance is used to create a ParsedBlock instance which gets returned by parse_radio_block. The above process repeats until no more blocks are remaining in the transmission at which point a ParsedTransmission instance is created containing the packet header and all ParsedBlocks from the transmission.

Once the transmission has finished being parsed, the data is used to update the TelemetryData instance of the Telemetry process. The TelemetryData class maintains a record of the last few data packets received and also contains the output specification for the Telemetry process.

Updating the front end

Since the WebSocketHandler process periodically checks its telemetry_json_output queue, updating the front-end on the Telemetry process' side is as simple as adding JSON output to the telemetry_json_output queue.

WebSocketHandler

The WebSocketHandler process is the simplest of the three. It is responsible for starting the web socket server that allows for communication between the three processes. It is also responsible for serving the web page where commands can be sent and data can be displayed. When the web page is opened it establishes a connection with the web socket server. This allows for bi-directional communication between the web page and the web socket server

After the web socket server is started, the WebSocketHandler process periodically checks for JSON output from the Telemetry process in its telemetry_json_output queue. This periodic check waits until the queue is populated with some JSON output before taking that output and writing it to all connected clients.