Skip to main content

Remote control

Drive the vessel manually by streaming joystick frames. The vessel routes manual control to one control unit at a time — a logical station identified by a control_unit_id. To take control you put the vessel in REMOTE mode, make your control unit the active one, and stream joystick frames tagged with that same id.

Before you begin

You need an authenticated channel — see Authenticate.

This drives a physical vessel

Confirm the vessel is armed and in REMOTE mode before streaming, and keep a way to stop. Frames go stale after ~2 s, so you must publish continuously (≈8 Hz) to retain control — if your stream stalls, the vessel stops receiving commands.

Take control

Enter REMOTE mode
remote_control.py
from greenroom.control.v1 import (
control_service_pb2,
control_service_pb2_grpc,
control_state_pb2,
joystick_pb2,
mode_pb2,
)

VESSEL = "vessel_1"
UNIT = "external_1" # your control unit id — any stable string you choose

client = control_service_pb2_grpc.ControlServiceStub(channel)

client.SetModeControl(
control_service_pb2.SetModeControlRequest(
vessel_id=VESSEL, mode=mode_pb2.CONTROL_MODE_REMOTE
)
)
REMOTE mode has preconditions

Switching to REMOTE requires the vessel armed (see Command mode and arming) and may require the gearbox in neutral; the platform negotiates the underlying control authority for you. SetModeControl returns success/message — check it rather than assuming the change took effect.

Select your control unit

Make your control_unit_id the active manual driver. Until this matches the id on your joystick stream, your frames are ignored.

client.SetControlUnit(
control_service_pb2.SetControlUnitRequest(
vessel_id=VESSEL, control_unit_id=UNIT
)
)
Stream joystick frames

PublishJoystick is a client-streaming RPC: pass an iterator of frames. Send the Standard Gamepad layout (below) at ~8 Hz, tagged with the same control_unit_id.

import time

driving = True

def frames():
while driving:
axes, buttons = read_gamepad() # your input source, in the layout below
yield control_service_pb2.PublishJoystickRequest(
vessel_id=VESSEL,
control_unit_id=UNIT,
device_id="Standard Gamepad",
joy=joystick_pb2.Joy(axes=axes, buttons=buttons),
)
time.sleep(1 / 8)

response = client.PublishJoystick(frames()) # returns when the stream closes
print(response.success, response.message)

Standard Gamepad layout

Send device_id="Standard Gamepad" with the SDL2 game-controller layout. Conventions: forward and left are positive; sticks spring back to 0.

axes — 6 floats, each in [-1, 1]:

IndexAxisNotes
0Left stick X
1Left stick Y
2Right stick X
3Right stick Y
4Left triggerrest +1, fully pressed -1
5Right triggerrest +1, fully pressed -1

buttons — 15 ints, 0 (released) or 1 (pressed):

IndexButtonIndexButton
0A8Right stick (RS)
1B9Left bumper (LB)
2X10Right bumper (RB)
3Y11D-pad up
4Back12D-pad down
5Guide13D-pad left
6Start14D-pad right
7Left stick (LS)

How the named device maps to the vessel's controls (steering, throttle, …) is configured on the vessel — the deployment must recognise the Standard Gamepad device. The SDK only carries the frame; confirm device support through your Greenroom engagement.

Confirm you hold control

Watch the active control unit and check its id is yours:

request = control_service_pb2.StreamActiveControlUnitRequest(vessel_id=VESSEL)
for response in client.StreamActiveControlUnit(request):
unit = response.control_unit
print(unit.id, control_state_pb2.ControlUnitType.Name(unit.type))

StreamControlUnits lists every control unit currently available, if you need to discover them.

Release control

Stop the frame iterator (driving = False), then clear the selection and hand the vessel back to local control:

client.SetControlUnit(
control_service_pb2.SetControlUnitRequest(vessel_id=VESSEL, control_unit_id="")
)
client.SetModeControl(
control_service_pb2.SetModeControlRequest(
vessel_id=VESSEL, mode=mode_pb2.CONTROL_MODE_LOCAL
)
)

What's next