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.
You need an authenticated channel — see Authenticate.
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
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
)
)
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.
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
)
)
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]:
| Index | Axis | Notes |
|---|---|---|
| 0 | Left stick X | |
| 1 | Left stick Y | |
| 2 | Right stick X | |
| 3 | Right stick Y | |
| 4 | Left trigger | rest +1, fully pressed -1 |
| 5 | Right trigger | rest +1, fully pressed -1 |
buttons — 15 ints, 0 (released) or 1 (pressed):
| Index | Button | Index | Button | |
|---|---|---|---|---|
| 0 | A | 8 | Right stick (RS) | |
| 1 | B | 9 | Left bumper (LB) | |
| 2 | X | 10 | Right bumper (RB) | |
| 3 | Y | 11 | D-pad up | |
| 4 | Back | 12 | D-pad down | |
| 5 | Guide | 13 | D-pad left | |
| 6 | Start | 14 | D-pad right | |
| 7 | Left 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
- Command mode and arming — arm and set the speed envelope.
- Robust streaming — keep long-lived streams healthy.
- Error handling — status codes and the
success/messagepattern.