Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.4.0
1.4.2
2 changes: 1 addition & 1 deletion external/robocol
Binary file added godot/assets/fields/biobuzz.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions godot/assets/fields/biobuzz.webp.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://8fyldmx3i7ir"
path="res://.godot/imported/biobuzz.webp-7e07c77e7e1540149c6b5c1dc566eb8c.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://assets/fields/biobuzz.webp"
dest_files=["res://.godot/imported/biobuzz.webp-7e07c77e7e1540149c6b5c1dc566eb8c.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
37 changes: 37 additions & 0 deletions godot/autoloads/robot_client.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
extends Node

signal connection_changed(connected: bool)
signal link_changed(link: int)
signal connection_notice(text: String)
signal opmode_list_changed(opmodes: Array)
signal phase_changed(phase: int, opmode_name: String)
signal selected_opmode_changed(opmode_name: String)
Expand All @@ -18,8 +20,13 @@ signal video_frame(source: String, texture: Texture2D)
signal video_stream_ended(source: String)

enum Phase { DISCONNECTED, IDLE, INIT, RUNNING }
enum Link { DISCONNECTED, CONNECTING, CONNECTED }

const GAMEPAD_HZ := 50.0
const BUSY_NOTICE := (
"The Robot Controller at %s is already connected to another Driver Station. "
+ "Disconnect it there, then select DISCONNECTED to try again."
)

const LOCATION_RESOURCE := "RESOURCE"
const LOCATION_LOCAL := "LOCAL_STORAGE"
Expand All @@ -32,6 +39,7 @@ const LOCATION_NONE := "NONE"
const DEFAULT_LIMELIGHT_STREAM := "http://192.168.43.1:5800/"

var phase: int = Phase.DISCONNECTED
var link: int = Link.DISCONNECTED
var selected_opmode := ""
var opmodes: Array = []
var configurations: Array = []
Expand All @@ -58,6 +66,7 @@ func _ready() -> void:
_bridge = ClassDB.instantiate("RobocolBridge")
add_child(_bridge)
_bridge.connection_changed.connect(_on_bridge_connection_changed)
_bridge.peer_busy.connect(_on_bridge_peer_busy)
_bridge.opmode_list_changed.connect(_on_bridge_opmode_list)
_bridge.phase_changed.connect(_on_bridge_phase_changed)
_bridge.telemetry_received.connect(_on_bridge_telemetry)
Expand Down Expand Up @@ -87,6 +96,7 @@ func _ready() -> void:
-1 if bind_env.is_empty() else int(bind_env),
-1 if peer_port_env.is_empty() else int(peer_port_env)
)
_set_link(Link.CONNECTING)
_setup_video_streams()


Expand All @@ -113,6 +123,23 @@ func _setup_video_streams() -> void:
_bridge.add_video_stream("limelight", limelight)


func set_link_enabled(enabled: bool) -> void:
if enabled:
connection_notice.emit("")
_set_link(Link.CONNECTING)
_bridge.connect_rc()
else:
_set_link(Link.DISCONNECTED)
_bridge.disconnect_rc()


func _set_link(new_link: int) -> void:
if new_link == link:
return
link = new_link
link_changed.emit(new_link)


func select_opmode(opmode_name: String) -> void:
if phase == Phase.IDLE:
selected_opmode = opmode_name
Expand Down Expand Up @@ -249,9 +276,19 @@ func _on_slot_changed(new_slot: int) -> void:


func _on_bridge_connection_changed(connected: bool) -> void:
if connected:
connection_notice.emit("")
_set_link(Link.CONNECTED)
elif link == Link.CONNECTED:
_set_link(Link.CONNECTING)
connection_changed.emit(connected)


func _on_bridge_peer_busy(peer: String) -> void:
_set_link(Link.DISCONNECTED)
connection_notice.emit(BUSY_NOTICE % peer)


func _on_bridge_opmode_list(list: Array) -> void:
opmodes = list
opmode_list_changed.emit(list)
Expand Down
7 changes: 7 additions & 0 deletions godot/export_presets.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,10 @@ application/version="1.0"
; rather than leaving it to the exporter.
codesign/codesign=0
notarization/notarization=0
application/additional_plist_content="<key>LSEnvironment</key>
<dict>
<key>SDL_JOYSTICK_MFI</key>
<string>0</string>
<key>SDL_HIDAPI_LIBUSB_WHITELIST</key>
<string>0</string>
</dict>"
26 changes: 25 additions & 1 deletion godot/scenes/main.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ theme_type_variation = &"StatusBarCard"
layout_mode = 2
theme_override_constants/separation = 16

[node name="ConnectionLabel" type="Label" parent="Layout/StatusBar/StatusRow/StatusCard/StatusGroup"]
[node name="ConnectionButton" type="Button" parent="Layout/StatusBar/StatusRow/StatusCard/StatusGroup"]
unique_name_in_owner = true
layout_mode = 2
flat = true
text = "DISCONNECTED"

[node name="PhaseLabel" type="Label" parent="Layout/StatusBar/StatusRow/StatusCard/StatusGroup"]
Expand Down Expand Up @@ -191,6 +192,29 @@ layout_mode = 2
size_flags_vertical = 0
text = "Dismiss"

[node name="ConnectionBanner" type="PanelContainer" parent="Layout"]
unique_name_in_owner = true
visible = false
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_error")

[node name="ConnectionRow" type="HBoxContainer" parent="Layout/ConnectionBanner"]
layout_mode = 2
theme_override_constants/separation = 12

[node name="ConnectionText" type="Label" parent="Layout/ConnectionBanner/ConnectionRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(1, 0.85, 0.85, 1)
autowrap_mode = 3

[node name="ConnectionDismiss" type="Button" parent="Layout/ConnectionBanner/ConnectionRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 0
text = "Dismiss"

[node name="Pages" type="Control" parent="Layout"]
layout_mode = 2
size_flags_vertical = 3
Expand Down
32 changes: 18 additions & 14 deletions godot/scripts/pages/config/config_screen.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var _configs: Array = []
var _stale_key := ""
var _activated_key := ""
var _group := ButtonGroup.new()
var _selected_name := ""
var _selected_key := ""
var _pending_metas: Array[Dictionary] = []
var _ambiguity_prompted := false
var _ambiguity_candidates: Array[Dictionary] = []
Expand Down Expand Up @@ -67,7 +67,7 @@ func _refresh_list() -> void:
row.toggle_mode = true
row.button_group = _group
row.alignment = HORIZONTAL_ALIGNMENT_LEFT
row.button_pressed = meta.get("name") == _selected_name
row.button_pressed = _config_key(meta) == _selected_key
row.pressed.connect(_on_config_selected.bind(meta))
%ConfigList.add_child(row)
var status := _row_status(meta)
Expand Down Expand Up @@ -141,7 +141,7 @@ func _row_status(meta: Dictionary) -> StringName:


func _on_config_selected(meta: Dictionary) -> void:
_selected_name = meta.get("name", "")
_selected_key = _config_key(meta)
_pending_metas.append(meta.duplicate())
RobotClient.request_particular_configuration(meta)

Expand All @@ -161,7 +161,7 @@ func grip_tap(grip: StringName) -> void:

func _selected_index() -> int:
for i in _configs.size():
if _configs[i].get("name") == _selected_name:
if _config_key(_configs[i]) == _selected_key:
return i
return -1

Expand All @@ -174,7 +174,7 @@ func _on_configuration_received(xml: String) -> void:


func _on_new() -> void:
_selected_name = ""
_selected_key = ""
_editor.load_new(
{
"isDirty": true,
Expand All @@ -190,21 +190,21 @@ func _on_new() -> void:


func _on_editor_saved(meta: Dictionary, active_out_of_date: bool) -> void:
_selected_name = meta.get("name", "")
_selected_key = _config_key(meta)
if active_out_of_date:
_stale_key = _config_key(meta)
if _activated_key == _stale_key:
_activated_key = ""
_save_state()
_refresh_list()
_poll_until_listed(_selected_name)
_poll_until_listed(_selected_key)


func _poll_until_listed(name: String) -> void:
func _poll_until_listed(key: String) -> void:
for _i in LIST_POLL_TRIES:
RobotClient.request_configurations()
await get_tree().create_timer(LIST_POLL_INTERVAL).timeout
if _configs.any(func(c: Dictionary) -> bool: return c.get("name") == name):
if _listed(key):
return


Expand All @@ -220,18 +220,18 @@ func _on_editor_deleted(meta: Dictionary) -> void:
_activated_key = ""
_ambiguous_keys.erase(key)
_save_state()
if _selected_name == meta.get("name", ""):
_selected_name = ""
if _selected_key == key:
_selected_key = ""
_pending_metas.clear()
_refresh_list()
_poll_until_unlisted(meta.get("name", ""))
_poll_until_unlisted(key)


func _poll_until_unlisted(name: String) -> void:
func _poll_until_unlisted(key: String) -> void:
for _i in LIST_POLL_TRIES:
RobotClient.request_configurations()
await get_tree().create_timer(LIST_POLL_INTERVAL).timeout
if not _configs.any(func(c: Dictionary) -> bool: return c.get("name") == name):
if not _listed(key):
return


Expand Down Expand Up @@ -326,6 +326,10 @@ func _on_ambiguity_dismissed() -> void:
_refresh_list()


func _listed(key: String) -> bool:
return not _listed_config(key).is_empty()


func _listed_config(key: String) -> Dictionary:
for meta: Dictionary in _configs:
if _config_key(meta) == key:
Expand Down
1 change: 0 additions & 1 deletion godot/scripts/pages/drive/drive_page.gd
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ func _apply_source(slot: int, source: int) -> void:

var header: String = SOURCE_NAMES[source]
if CAM_FEEDS.has(source):
cam.flip_v = source == Source.WEBCAM
cam.texture = _cam_textures.get(CAM_FEEDS[source])
if cam.texture == null:
header += " — no feed"
Expand Down
1 change: 1 addition & 0 deletions godot/scripts/pages/field/field_images.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class_name FieldImages

const FIELDS := [
{"name": "2026-27 BIOBUZZ", "path": "res://assets/fields/biobuzz.webp"},
{"name": "2025-26 DECODE", "path": "res://assets/fields/decode.webp"},
{"name": "2024-25 INTO THE DEEP", "path": "res://assets/fields/into_the_deep.webp"},
{"name": "Field (no game elements)", "path": "res://assets/fields/base.webp"},
Expand Down
57 changes: 50 additions & 7 deletions godot/scripts/shell/status_bar.gd
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,32 @@ const PHASE_NAMES := {
const MATCH_SECONDS := 120.0
const ENDGAME_SECONDS := 30.0
const SLOT_STATUS := {1: &"slot1", 2: &"slot2"}
const LINK_LABELS := {
RobotClient.Link.DISCONNECTED: ["DISCONNECTED", &"disconnected"],
RobotClient.Link.CONNECTING: ["CONNECTING", &"warn"],
RobotClient.Link.CONNECTED: ["CONNECTED", &"connected"],
}
const LINK_FONT_COLORS := [
&"font_color",
&"font_hover_color",
&"font_focus_color",
&"font_pressed_color",
&"font_hover_pressed_color",
]

var _clock_idle := false
var _connected := false
var _disconnect_dialog: ConfirmationDialog


func _ready() -> void:
%StopButton.pressed.connect(_on_action_pressed)
_size_action_button()
RobotClient.connection_changed.connect(_on_connection_changed)
_build_disconnect_dialog()
%ConnectionButton.pressed.connect(_on_connection_pressed)
RobotClient.link_changed.connect(_on_link_changed)
RobotClient.connection_notice.connect(_on_connection_notice)
%ConnectionDismiss.pressed.connect(_on_connection_notice.bind(""))
_on_link_changed(RobotClient.link)
RobotClient.phase_changed.connect(_on_phase_changed)
RobotClient.selected_opmode_changed.connect(_on_selected_opmode_changed)
RobotClient.battery_voltage_changed.connect(_on_battery_voltage)
Expand All @@ -45,15 +62,41 @@ func _untint(label: Label) -> void:


func refresh_tints() -> void:
_on_connection_changed(_connected)
_on_link_changed(RobotClient.link)
_update_slot_badge()
_clock_idle = false


func _on_connection_changed(connected: bool) -> void:
_connected = connected
%ConnectionLabel.text = "CONNECTED" if connected else "DISCONNECTED"
_tint(%ConnectionLabel, &"connected" if connected else &"disconnected")
func _build_disconnect_dialog() -> void:
_disconnect_dialog = ConfirmationDialog.new()
_disconnect_dialog.title = "Disconnect"
_disconnect_dialog.dialog_text = "Disconnect from the Robot Controller?"
_disconnect_dialog.ok_button_text = "Disconnect"
_disconnect_dialog.confirmed.connect(RobotClient.set_link_enabled.bind(false))
add_child(_disconnect_dialog)
DialogCancel.install(_disconnect_dialog)


func _on_connection_pressed() -> void:
if RobotClient.link == RobotClient.Link.CONNECTED:
_disconnect_dialog.popup_centered()
else:
RobotClient.set_link_enabled(true)


func _on_link_changed(link: int) -> void:
var button: Button = %ConnectionButton
button.text = LINK_LABELS[link][0]
var color := get_theme_color(LINK_LABELS[link][1], ThemeTokens.STATUS_TYPE)
for font_color: StringName in LINK_FONT_COLORS:
button.add_theme_color_override(font_color, color)
if link != RobotClient.Link.CONNECTED:
_disconnect_dialog.hide()


func _on_connection_notice(text: String) -> void:
%ConnectionText.text = text
%ConnectionBanner.visible = not text.is_empty()


func _on_phase_changed(phase: int, opmode_name: String) -> void:
Expand Down
Loading
Loading