To send custom messages (such as data from custom sensors wired to the Pi) from the Pi to the tethered computer, I wrote two python scripts, one running on the Pi and the other on the topside computer. Intially, I tried to use the mavlink protocol over UDP, which is how the robot streams telemetry to QGroundControl. However messaging with mavlink only worked on the same port as QGroundControl, which meant that the scripts were also picking up on all the other telemetry data, which was very unnecessary for the scope of this functionality. Therefore, I switched to using python's built-in socket module, which used TCP, and this worked flawlessly. Oddly enough, the socket implementation only worked if the Pi ran as the server and the topside computer as the client.
For controlling the robot with commands from the topside computer, I used
bluerov_ros_playground, an unofficial ROS package created by one of BlueRobotics' own engineers. From pretty early on, we were set on using ROS; Dr. Hartmann's students and I were all familiar with it and there was a good chance that other BlueROV2 owners - perhaps other researchers - had made their own packages for the robot. In fact, BlueRobotics had created an official ROS package for the BlueROV, the predecessor to the current robot. Unfortunately, they had not seriously updated the package in years.
With this package, I was able to successfully control the robot by publishing UInt16 values to the set_pwm topics for the various rc channels. Each of these channels were responsible for a different motion for the robot, i.e. rc_channel2 for roll, rc_channel5 for forward/backward, and rc_channel6 for later side-to-side motion. What this is essentially doing is mimicking the values that the autopilot would otherwise receive from joysticks on a physical controller, with each rc_channel representing an axis on the joysticks. The gif below demonstrates the robot moving in rectangular path, implemented as a series of publishes to topics run via rosnode.