diff --git a/assets/vector/status/rov.svg b/assets/vector/status/rov.svg new file mode 100644 index 0000000..b15a2fc --- /dev/null +++ b/assets/vector/status/rov.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc067074ddeb85ae5503b66abbfd9d2778c00c4d4deb813f5478906faf81adb2 +size 459 diff --git a/assets/vector/status/rov.svg.import b/assets/vector/status/rov.svg.import new file mode 100644 index 0000000..b0eeb5c --- /dev/null +++ b/assets/vector/status/rov.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3kgbws2ejds6" +path="res://.godot/imported/rov.svg-85cd68e18cc64f6002858c870af4fd81.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/vector/status/rov.svg" +dest_files=["res://.godot/imported/rov.svg-85cd68e18cc64f6002858c870af4fd81.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 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/vector/status/stationkeep.svg b/assets/vector/status/stationkeep.svg new file mode 100644 index 0000000..14e5489 --- /dev/null +++ b/assets/vector/status/stationkeep.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a099a4970f2cc41d68dee79cd61e94ff539ef8780b6fe9dbbd20127ceccb07fe +size 297 diff --git a/assets/vector/status/stationkeep.svg.import b/assets/vector/status/stationkeep.svg.import new file mode 100644 index 0000000..39a6042 --- /dev/null +++ b/assets/vector/status/stationkeep.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bc2m1gknloa5i" +path="res://.godot/imported/stationkeep.svg-ba46234e5cd35725d5ce1d66d82e2fa0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/vector/status/stationkeep.svg" +dest_files=["res://.godot/imported/stationkeep.svg-ba46234e5cd35725d5ce1d66d82e2fa0.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 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/components/vehicle_status_panel.tscn b/components/vehicle_status_panel.tscn new file mode 100644 index 0000000..dc6b75a --- /dev/null +++ b/components/vehicle_status_panel.tscn @@ -0,0 +1,274 @@ +[gd_scene format=3 uid="uid://c6xqxnk0pqhg1"] + +[ext_resource type="Script" uid="uid://d3jrypgr15n4x" path="res://scripts/vehicle_status_panel.gd" id="1"] +[ext_resource type="Script" uid="uid://rh8gwu8mfqx2" path="res://scripts/vehicle_status_art.gd" id="2"] +[ext_resource type="Script" uid="uid://bqvhrignal143" path="res://scripts/status_gauge.gd" id="3"] +[ext_resource type="Texture2D" uid="uid://bc2m1gknloa5i" path="res://assets/vector/status/stationkeep.svg" id="4"] + +[node name="VehicleStatusPanel" type="PanelContainer" unique_id=489606862] +custom_minimum_size = Vector2(600, 440) +offset_right = 600.0 +offset_bottom = 440.0 +theme_type_variation = &"VehicleBackground" +script = ExtResource("1") +mode_icon = ExtResource("4") + +[node name="Margin" type="MarginContainer" parent="." unique_id=1101194281] +layout_mode = 2 +theme_override_constants/margin_left = 16 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 16 +theme_override_constants/margin_bottom = 12 + +[node name="Layout" type="VBoxContainer" parent="Margin" unique_id=221437014] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="StatusArea" type="MarginContainer" parent="Margin/Layout" unique_id=1107401965] +custom_minimum_size = Vector2(0, 164) +clip_contents = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="StatusArt" type="Control" parent="Margin/Layout/StatusArea" unique_id=649910411] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +script = ExtResource("2") + +[node name="ModeIcon" type="TextureRect" parent="Margin/Layout/StatusArea/StatusArt" unique_id=728690011] +unique_name_in_owner = true +visible = false +self_modulate = Color(1, 0.61176467, 0.25, 0.09) +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.68 +anchor_top = 0.5 +anchor_right = 0.68 +anchor_bottom = 0.5 +offset_left = -105.0 +offset_top = -105.0 +offset_right = 105.0 +offset_bottom = 105.0 +rotation = -0.20943952 +pivot_offset = Vector2(105, 105) +mouse_filter = 2 +texture = ExtResource("4") +expand_mode = 1 +stretch_mode = 5 + +[node name="Inset" type="MarginContainer" parent="Margin/Layout/StatusArea" unique_id=2050290664] +layout_mode = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 8 + +[node name="Text" type="VBoxContainer" parent="Margin/Layout/StatusArea/Inset" unique_id=905192152] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Top" type="HBoxContainer" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=424687654] +layout_mode = 2 + +[node name="Code" type="Label" parent="Margin/Layout/StatusArea/Inset/Text/Top" unique_id=1099636858] +unique_name_in_owner = true +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +theme_type_variation = &"Label" +theme_override_font_sizes/font_size = 18 + +[node name="Severity" type="Label" parent="Margin/Layout/StatusArea/Inset/Text/Top" unique_id=1356897068] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"Label" +theme_override_font_sizes/font_size = 18 +text = "NO DATA" +horizontal_alignment = 2 + +[node name="StatusName" type="Label" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=1189663888] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"Label" +theme_override_font_sizes/font_size = 30 +text = "Waiting for telemetry" +autowrap_mode = 3 + +[node name="Detail" type="Label" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=703466821] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"Label" +theme_override_font_sizes/font_size = 18 +autowrap_mode = 3 + +[node name="Action" type="Label" parent="Margin/Layout/StatusArea/Inset/Text" unique_id=514634651] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"Label" +theme_override_font_sizes/font_size = 18 +autowrap_mode = 3 + +[node name="Readouts" type="VBoxContainer" parent="Margin/Layout" unique_id=1999758637] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="Voltage" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=1002742281] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Voltage" unique_id=1312021033] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Title" type="Label" parent="Margin/Layout/Readouts/Voltage/Numbers" unique_id=264983620] +custom_minimum_size = Vector2(124, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "VOLTAGE" + +[node name="VoltageValue" type="Label" parent="Margin/Layout/Readouts/Voltage/Numbers" unique_id=1230347145] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 26 +text = "—" +horizontal_alignment = 2 + +[node name="VoltageReference" type="Label" parent="Margin/Layout/Readouts/Voltage/Numbers" unique_id=264381298] +unique_name_in_owner = true +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "/ 48 V" +vertical_alignment = 1 + +[node name="VoltageGauge" type="Control" parent="Margin/Layout/Readouts/Voltage" unique_id=446082505] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 16) +layout_mode = 2 +script = ExtResource("3") +minimum = 42.0 +maximum = 54.0 +warning_low = 47.0 +warning_high = 49.0 +fault_low = 45.0 +fault_high = 51.0 +nominal = 48.0 + +[node name="Current" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=1982270337] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Current" unique_id=619271939] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Title" type="Label" parent="Margin/Layout/Readouts/Current/Numbers" unique_id=137700803] +custom_minimum_size = Vector2(124, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "CURRENT" + +[node name="CurrentValue" type="Label" parent="Margin/Layout/Readouts/Current/Numbers" unique_id=2147417420] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 26 +text = "—" +horizontal_alignment = 2 + +[node name="CurrentReference" type="Label" parent="Margin/Layout/Readouts/Current/Numbers" unique_id=1995980746] +unique_name_in_owner = true +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "/ 30 A" +vertical_alignment = 1 + +[node name="CurrentGauge" type="Control" parent="Margin/Layout/Readouts/Current" unique_id=484645650] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 16) +layout_mode = 2 +script = ExtResource("3") +nominal = nan + +[node name="Power" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=1074490654] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Power" unique_id=111236353] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Title" type="Label" parent="Margin/Layout/Readouts/Power/Numbers" unique_id=213594380] +custom_minimum_size = Vector2(124, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "POWER" + +[node name="PowerValue" type="Label" parent="Margin/Layout/Readouts/Power/Numbers" unique_id=1921756191] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 26 +text = "—" +horizontal_alignment = 2 + +[node name="PowerReference" type="Label" parent="Margin/Layout/Readouts/Power/Numbers" unique_id=1441937096] +unique_name_in_owner = true +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "/ 1440 W" +vertical_alignment = 1 + +[node name="PowerGauge" type="Control" parent="Margin/Layout/Readouts/Power" unique_id=82393019] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 16) +layout_mode = 2 +script = ExtResource("3") +maximum = 1728.0 +warning_high = 1152.0 +fault_high = 1440.0 +nominal = nan + +[node name="Temperature" type="VBoxContainer" parent="Margin/Layout/Readouts" unique_id=658026844] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="Numbers" type="HBoxContainer" parent="Margin/Layout/Readouts/Temperature" unique_id=567006533] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="Title" type="Label" parent="Margin/Layout/Readouts/Temperature/Numbers" unique_id=1987975109] +custom_minimum_size = Vector2(124, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "INTERNAL" + +[node name="TemperatureValue" type="Label" parent="Margin/Layout/Readouts/Temperature/Numbers" unique_id=1041616611] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 26 +text = "—" +horizontal_alignment = 2 + +[node name="TemperatureReference" type="Label" parent="Margin/Layout/Readouts/Temperature/Numbers" unique_id=1032238631] +unique_name_in_owner = true +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "/ 65 °C" +vertical_alignment = 1 + +[node name="TemperatureGauge" type="Control" parent="Margin/Layout/Readouts/Temperature" unique_id=214013672] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 16) +layout_mode = 2 +script = ExtResource("3") +maximum = 90.0 +warning_high = 55.0 +fault_high = 65.0 +nominal = nan diff --git a/docs/vehicle_status.md b/docs/vehicle_status.md new file mode 100644 index 0000000..d4e002c --- /dev/null +++ b/docs/vehicle_status.md @@ -0,0 +1,30 @@ +# Vehicle status prototype + +Run `scenes/vehicle_status_demo.tscn` with F6. The controls switch between normal, warning, fault, and a stopped feed, plus high contrast and alert pulsing. + +Instance `components/vehicle_status_panel.tscn` for integration: + +```gdscript +$VehicleStatusPanel.set_status("STATIONKEEP", 0, "", "Position hold active", "Thrusters enabled") +$VehicleStatusPanel.set_telemetry(48.0, 18.2, 874.0, 34.0) +# severity: 0 normal, 1 warning, 2 fault +$VehicleStatusPanel.set_status("Enclosure overtemperature", 2, "TMP-003", + "68 °C · trip 65 °C · thrusters inhibited", "Inspect cooling before reset") +``` + +All sample fault codes, responses, and thresholds are illustrative. The UI displays reported vehicle status; gauge zones do not control the ROV or generate its faults. + +Configure each gauge in the scene Inspector. The numeric references use the voltage nominal or the other gauges' upper fault thresholds. Voltage marks 48 V at center; any deviation enters a yellow region. + +| Gauge | Display range | Warning zone | Fault zone | +| --- | --- | --- | --- | +| Voltage | 42–54 V | Either side of 48 V | Below 45 / above 51 V | +| Current | 0–36 A | 24–30 A | Above 30 A | +| Power | 0–1728 W | 1152–1440 W | Above 1440 W | +| Internal temperature | 0–90 °C | 55–65 °C | Above 65 °C | + +Only 48 V nominal and 30 A were supplied. The other thresholds need confirmation; 1440 W is the provisional 48 × 30 reference. Markers clamp to the track, but numeric readings remain unclamped. + +Labels and containers own layout. `status_gauge.gd` draws tracks and markers; `vehicle_status_art.gd` draws the watermark and status background. Warning/fault backgrounds alternate shades once per second; `pulse_alerts` disables that behavior. After two seconds without telemetry, the status is marked STALE and readings are held. Colors come from the shared palette and generated themes. + +`mode_icon` selects the SVG watermark (stationkeep and ROV samples are in `assets/vector/status/`). Edit `ModeIcon` anchors, rotation, and size in the scene. Readings are right-aligned beside a fixed-width, left-aligned limits column. Off-phase warning/fault shades are derived in the shared palette. diff --git a/scenes/vehicle_status_demo.tscn b/scenes/vehicle_status_demo.tscn new file mode 100644 index 0000000..c5464d2 --- /dev/null +++ b/scenes/vehicle_status_demo.tscn @@ -0,0 +1,44 @@ +[gd_scene format=3] + +[ext_resource type="PackedScene" path="res://components/vehicle_status_panel.tscn" id="1"] +[ext_resource type="Script" path="res://scripts/vehicle_status_demo.gd" id="2"] + +[node name="VehicleStatusDemo" type="VBoxContainer"] +offset_left = 24.0 +offset_top = 24.0 +offset_right = 624.0 +offset_bottom = 560.0 +theme_override_constants/separation = 12 +script = ExtResource("2") + +[node name="Notice" type="Label" parent="."] +layout_mode = 2 +text = "SIMULATED / gauge thresholds are provisional" + +[node name="VehicleStatusPanel" parent="." instance=ExtResource("1")] +layout_mode = 2 + +[node name="Controls" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="State" type="OptionButton" parent="Controls"] +layout_mode = 2 +item_count = 4 +selected = 0 +popup/item_0/text = "Normal" +popup/item_0/id = 0 +popup/item_1/text = "Warning" +popup/item_1/id = 1 +popup/item_2/text = "Fault" +popup/item_2/id = 2 +popup/item_3/text = "Stop feed / stale" +popup/item_3/id = 3 + +[node name="Contrast" type="CheckButton" parent="Controls"] +layout_mode = 2 +text = "High contrast" + +[node name="Pulse" type="CheckButton" parent="Controls"] +layout_mode = 2 +text = "Pulse" +button_pressed = true diff --git a/scripts/status_gauge.gd b/scripts/status_gauge.gd new file mode 100644 index 0000000..4b29cd7 --- /dev/null +++ b/scripts/status_gauge.gd @@ -0,0 +1,40 @@ +@tool +extends Control + +@export var minimum := 0.0 +@export var maximum := 36.0 +@export var warning_low := 0.0 +@export var warning_high := 24.0 +@export var fault_low := 0.0 +@export var fault_high := 30.0 +@export var nominal := NAN +var value := 0.0 +var available := false + +func _ready() -> void: + resized.connect(queue_redraw) + theme_changed.connect(queue_redraw) + +func _x(number: float) -> float: + return 4 + clampf((number - minimum) / maxf(maximum - minimum, 0.001), 0, 1) * (size.x - 8) + +func _zone(low: float, high: float, role: String) -> void: + if high <= low: + return + var color := get_theme_color(role, "SurfaceSide") + color.a = 0.65 + draw_rect(Rect2(_x(low), 4, _x(high) - _x(low), 6), color) + +func _draw() -> void: + draw_rect(Rect2(4, 4, maxf(size.x - 8, 0), 6), get_theme_color("instrument_grid", "SurfaceSide")) + _zone(minimum, fault_low, "fault") + _zone(fault_low, warning_low, "warning") + _zone(warning_high, fault_high, "warning") + _zone(fault_high, maximum, "fault") + if is_finite(nominal): + draw_line(Vector2(_x(nominal), 0), Vector2(_x(nominal), 16), get_theme_color("instrument_accent", "SurfaceSide"), 2) + if available: + var x := _x(value) + var ink := get_theme_color("font_color", "Label") + draw_line(Vector2(x, 0), Vector2(x, 14), ink, 3) + draw_colored_polygon(PackedVector2Array([Vector2(x - 5, 0), Vector2(x + 5, 0), Vector2(x, 4)]), ink) diff --git a/scripts/status_gauge.gd.uid b/scripts/status_gauge.gd.uid new file mode 100644 index 0000000..bab726e --- /dev/null +++ b/scripts/status_gauge.gd.uid @@ -0,0 +1 @@ +uid://bqvhrignal143 diff --git a/scripts/vehicle_status_art.gd b/scripts/vehicle_status_art.gd new file mode 100644 index 0000000..f791e81 --- /dev/null +++ b/scripts/vehicle_status_art.gd @@ -0,0 +1,29 @@ +@tool +extends Control + +@export_enum("Normal", "Warning", "Fault", "No data") var severity := 3 +@export var pulse_alerts := true +var _phase := -1 + +func _ready() -> void: + resized.connect(queue_redraw) + theme_changed.connect(_update_icon) + _update_icon() + +func _process(_delta: float) -> void: + var phase := int(Time.get_ticks_msec() / 1000.0) % 2 if pulse_alerts else 0 + if phase != _phase: + _phase = phase + queue_redraw() + +func _draw() -> void: + if severity in [1, 2]: + var role := "warning" if severity == 1 else "fault" + if _phase == 1: + role += "_alternate" + draw_rect(Rect2(Vector2.ZERO, size), get_theme_color(role, "SurfaceSide")) +func _update_icon() -> void: + var tint := get_theme_color("status_icon", "SurfaceSide") + tint.a = 0.09 + $ModeIcon.self_modulate = tint + queue_redraw() diff --git a/scripts/vehicle_status_art.gd.uid b/scripts/vehicle_status_art.gd.uid new file mode 100644 index 0000000..94f48eb --- /dev/null +++ b/scripts/vehicle_status_art.gd.uid @@ -0,0 +1 @@ +uid://rh8gwu8mfqx2 diff --git a/scripts/vehicle_status_demo.gd b/scripts/vehicle_status_demo.gd new file mode 100644 index 0000000..90b7765 --- /dev/null +++ b/scripts/vehicle_status_demo.gd @@ -0,0 +1,27 @@ +extends VBoxContainer + +@onready var panel = $VehicleStatusPanel +var elapsed := 0.0 + +func _ready() -> void: + $Controls/State.item_selected.connect(_select_state) + $Controls/Contrast.toggled.connect(func(high: bool) -> void: + theme = load("res://themes/theme_highcontrast.tres" if high else "res://themes/theme.tres") + ) + $Controls/Pulse.toggled.connect(func(enabled: bool) -> void: panel.pulse_alerts = enabled) + _select_state(0) + +func _select_state(index: int) -> void: + match index: + 0: panel.set_status("STATIONKEEP", 0, "", "Position hold active", "Thrusters enabled") + 1: panel.set_status("Supply voltage low", 1, "PWR-012", "46.5 V · below 48 V nominal", "Check topside supply and tether load") + 2: panel.set_status("Enclosure overtemperature", 2, "TMP-003", "68 °C · trip 65 °C · thrusters inhibited", "Inspect cooling before reset") + 3: panel.set_status("Enclosure overtemperature", 2, "TMP-003", "68 °C · trip 65 °C · thrusters inhibited", "Inspect cooling before reset") + +func _process(delta: float) -> void: + elapsed += delta + match $Controls/State.selected: + 0: panel.set_telemetry(48.0, 18.2 + sin(elapsed) * 0.6, 874 + sin(elapsed) * 29, 34) + 1: panel.set_telemetry(46.5, 25.5, 1186, 42) + 2: panel.set_telemetry(48.0, 2.1, 101, 68) + 3: pass diff --git a/scripts/vehicle_status_demo.gd.uid b/scripts/vehicle_status_demo.gd.uid new file mode 100644 index 0000000..a2b65b9 --- /dev/null +++ b/scripts/vehicle_status_demo.gd.uid @@ -0,0 +1 @@ +uid://qipln371l5is diff --git a/scripts/vehicle_status_panel.gd b/scripts/vehicle_status_panel.gd new file mode 100644 index 0000000..a2c6b9c --- /dev/null +++ b/scripts/vehicle_status_panel.gd @@ -0,0 +1,83 @@ +@tool +extends PanelContainer + +@export var mode_icon: Texture2D: + set(value): + mode_icon = value + if is_node_ready(): + %ModeIcon.texture = value + +@export var stale_after_seconds := 2.0 +@export var pulse_alerts := true: + set(value): + pulse_alerts = value + if is_node_ready(): + %StatusArt.pulse_alerts = value +var _received_ms := -1 +var _severity := 0 +var _name := "" +var _code := "" +var _detail := "" +var _action := "" +var _stale := false + +func _ready() -> void: + %ModeIcon.texture = mode_icon + _refresh_status() + _refresh_references() + +## Severity and fault explanations come from vehicle telemetry, not gauge zones. +func set_status(name_text: String, severity: int = 0, code: String = "", detail: String = "", action: String = "") -> void: + _name = name_text + _severity = clampi(severity, 0, 2) + _code = code + _detail = detail + _action = action + _refresh_status() + +func set_telemetry(voltage: float, current: float, power: float, internal_temperature: float) -> void: + for number in [voltage, current, power, internal_temperature]: + if not is_finite(number): + return + _refresh_references() + _received_ms = Time.get_ticks_msec() + var values := [voltage, current, power, internal_temperature] + var labels := [%VoltageValue, %CurrentValue, %PowerValue, %TemperatureValue] + var gauges := [%VoltageGauge, %CurrentGauge, %PowerGauge, %TemperatureGauge] + for index in range(4): + labels[index].text = (["%.1f V", "%.1f A", "%.0f W", "%.1f °C"][index]) % values[index] + gauges[index].value = values[index] + gauges[index].available = true + gauges[index].queue_redraw() + _refresh_status() + +func _process(_delta: float) -> void: + var stale := _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000 + if stale != _stale: + _refresh_status() + +func _refresh_status() -> void: + if not is_node_ready(): + return + _stale = _received_ms >= 0 and Time.get_ticks_msec() - _received_ms > stale_after_seconds * 1000 + var missing := _received_ms < 0 + var unavailable := missing or _stale + %StatusArt.severity = 3 if unavailable else _severity + %StatusArt.pulse_alerts = pulse_alerts + %StatusArt.queue_redraw() + %ModeIcon.visible = not unavailable and _severity == 0 + %Severity.text = "NO DATA" if missing else ("STALE" if _stale else ["", "WARNING", "FAULT"][_severity]) + %StatusName.text = "Waiting for telemetry" if missing else (_name if not _name.is_empty() else "Mode unavailable") + %Code.text = _code + %Code.visible = not _code.is_empty() and not missing + %Detail.text = "" if missing else _detail + %Action.text = "Values held · connection overdue" if _stale else ("" if missing else _action) + var variant := ("VehicleWarningText" if _severity == 1 else "VehicleFaultText") if _severity in [1, 2] and not unavailable else "Label" + for label in [%Severity, %StatusName, %Code, %Detail, %Action]: + label.theme_type_variation = variant + +func _refresh_references() -> void: + %VoltageReference.text = "/ %.0f V" % %VoltageGauge.nominal + %CurrentReference.text = "/ %.0f A" % %CurrentGauge.fault_high + %PowerReference.text = "/ %.0f W" % %PowerGauge.fault_high + %TemperatureReference.text = "/ %.0f °C" % %TemperatureGauge.fault_high diff --git a/scripts/vehicle_status_panel.gd.uid b/scripts/vehicle_status_panel.gd.uid new file mode 100644 index 0000000..b5a4996 --- /dev/null +++ b/scripts/vehicle_status_panel.gd.uid @@ -0,0 +1 @@ +uid://d3jrypgr15n4x