Tighten Textual dashboard controls and panel layout
This commit is contained in:
@@ -9,8 +9,8 @@ from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Horizontal, VerticalScroll
|
||||
from textual.widgets import Static, Header, Footer
|
||||
from textual.containers import Grid, Horizontal, HorizontalScroll, Vertical, VerticalScroll
|
||||
from textual.widgets import Button, Footer, Header, Static
|
||||
from textual.timer import Timer
|
||||
|
||||
from . import constants
|
||||
@@ -33,8 +33,12 @@ class TextualDashboard(App):
|
||||
|
||||
CSS = """
|
||||
Screen { layout: vertical; }
|
||||
.col { width: 1fr; }
|
||||
.panel { padding: 0 1; border: solid gray; }
|
||||
#controls { padding: 0 0; height: auto; }
|
||||
#controls .row { height: auto; }
|
||||
#controls Button { min-width: 8; padding: 0 1; }
|
||||
.columns { height: 1fr; }
|
||||
.col { width: 1fr; height: 1fr; }
|
||||
.panel { padding: 0 1; border: round $primary; }
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
@@ -61,6 +65,7 @@ class TextualDashboard(App):
|
||||
("c", "toggle_consumer", "Consumer"),
|
||||
("a", "toggle_auto_rods", "Auto rods"),
|
||||
("f12", "snapshot", "Snapshot"),
|
||||
# Maintenance is mouse-only here; keyboard remains in curses.
|
||||
]
|
||||
|
||||
timestep: float = 1.0
|
||||
@@ -104,27 +109,30 @@ class TextualDashboard(App):
|
||||
self.help_panel = Static(classes="panel")
|
||||
self.log_panel = Static(classes="panel")
|
||||
self.status_panel = Static(classes="panel")
|
||||
self.controls_panel: Grid | None = None
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
with Horizontal():
|
||||
controls = self._build_controls()
|
||||
yield controls
|
||||
with Horizontal(classes="columns"):
|
||||
with VerticalScroll(classes="col"):
|
||||
yield self.core_panel
|
||||
yield self.trend_panel
|
||||
yield self.poison_panel
|
||||
yield self.primary_panel
|
||||
yield self.secondary_panel
|
||||
yield self.power_panel
|
||||
yield self.generator_panel
|
||||
yield self.status_panel
|
||||
with VerticalScroll(classes="col"):
|
||||
yield self.turbine_panel
|
||||
yield self.generator_panel
|
||||
yield self.power_panel
|
||||
yield self.hx_panel
|
||||
yield self.protection_panel
|
||||
yield self.maintenance_panel
|
||||
yield self.health_panel
|
||||
yield self.log_panel
|
||||
yield self.help_panel
|
||||
yield self.status_panel
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
@@ -206,6 +214,37 @@ class TextualDashboard(App):
|
||||
def action_toggle_auto_rods(self) -> None:
|
||||
self._enqueue(ReactorCommand(rod_manual=not self.reactor.control.manual_control))
|
||||
|
||||
# Maintenance (mouse-driven)
|
||||
def action_maintain_core(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("core"))
|
||||
|
||||
def action_maintain_primary_p1(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("primary_pump_1"))
|
||||
|
||||
def action_maintain_primary_p2(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("primary_pump_2"))
|
||||
|
||||
def action_maintain_secondary_p1(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("secondary_pump_1"))
|
||||
|
||||
def action_maintain_secondary_p2(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("secondary_pump_2"))
|
||||
|
||||
def action_maintain_turbine_1(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("turbine_1"))
|
||||
|
||||
def action_maintain_turbine_2(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("turbine_2"))
|
||||
|
||||
def action_maintain_turbine_3(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("turbine_3"))
|
||||
|
||||
def action_maintain_generator_1(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("generator_1"))
|
||||
|
||||
def action_maintain_generator_2(self) -> None:
|
||||
self._enqueue(ReactorCommand.maintain("generator_2"))
|
||||
|
||||
def action_snapshot(self) -> None:
|
||||
self._save_snapshot(auto=False)
|
||||
|
||||
@@ -229,40 +268,89 @@ class TextualDashboard(App):
|
||||
if self.snapshot_at is not None and not self.snapshot_done and self.state.time_elapsed >= self.snapshot_at:
|
||||
self._save_snapshot(auto=True)
|
||||
|
||||
def _build_controls(self) -> Vertical:
|
||||
row1 = Horizontal(
|
||||
Button("SCRAM", id="scram"),
|
||||
Button("P1", id="p1"),
|
||||
Button("P2", id="p2"),
|
||||
Button("S1", id="s1"),
|
||||
Button("S2", id="s2"),
|
||||
Button("RelP", id="relief_pri"),
|
||||
Button("RelS", id="relief_sec"),
|
||||
Button("Turb", id="turbines"),
|
||||
Button("T1", id="t1"),
|
||||
Button("T2", id="t2"),
|
||||
Button("T3", id="t3"),
|
||||
Button("G1", id="g1"),
|
||||
Button("G2", id="g2"),
|
||||
Button("Grid", id="consumer"),
|
||||
Button("AutoR", id="auto_rods"),
|
||||
Button("+Rod", id="rods_plus"),
|
||||
Button("-Rod", id="rods_minus"),
|
||||
classes="row",
|
||||
)
|
||||
row2 = Horizontal(
|
||||
Button("D+50", id="demand_up"),
|
||||
Button("D-50", id="demand_down"),
|
||||
Button("SP+250", id="sp_up"),
|
||||
Button("SP-250", id="sp_down"),
|
||||
Button("Snap", id="snapshot"),
|
||||
classes="row",
|
||||
)
|
||||
row3 = Horizontal(
|
||||
Button("MCore", id="m_core"),
|
||||
Button("MP1", id="m_p1"),
|
||||
Button("MP2", id="m_p2"),
|
||||
Button("MS1", id="m_s1"),
|
||||
Button("MS2", id="m_s2"),
|
||||
Button("MT1", id="m_t1"),
|
||||
Button("MT2", id="m_t2"),
|
||||
Button("MT3", id="m_t3"),
|
||||
Button("MG1", id="m_g1"),
|
||||
Button("MG2", id="m_g2"),
|
||||
classes="row",
|
||||
)
|
||||
return Vertical(
|
||||
HorizontalScroll(row1, classes="row"),
|
||||
HorizontalScroll(row2, classes="row"),
|
||||
HorizontalScroll(row3, classes="row"),
|
||||
id="controls",
|
||||
)
|
||||
|
||||
def _render_panels(self) -> None:
|
||||
self._trend_history.append((self.state.time_elapsed, self.state.core.fuel_temperature, self.state.core.power_output_mw))
|
||||
|
||||
self.core_panel.update(
|
||||
f"[bold cyan]Core[/bold cyan]\\n"
|
||||
f"Power {self.state.core.power_output_mw:6.1f} MW (Nom {constants.NORMAL_CORE_POWER_MW:4.0f}/Max {constants.TEST_MAX_POWER_MW:4.0f})\\n"
|
||||
f"Fuel {self.state.core.fuel_temperature:6.1f} K Rods {self.reactor.control.rod_fraction:.3f} ({'AUTO' if not self.reactor.control.manual_control else 'MAN'})\\n"
|
||||
f"Setpoint {self.reactor.control.setpoint_mw:5.0f} MW Reactivity {self.state.core.reactivity_margin:+.4f}\\n"
|
||||
"[bold cyan]Core[/bold cyan]\n"
|
||||
f"Power {self.state.core.power_output_mw:6.1f} MW (Nom {constants.NORMAL_CORE_POWER_MW:4.0f}/Max {constants.TEST_MAX_POWER_MW:4.0f})\n"
|
||||
f"Fuel {self.state.core.fuel_temperature:6.1f} K Rods {self.reactor.control.rod_fraction:.3f} ({'AUTO' if not self.reactor.control.manual_control else 'MAN'})\n"
|
||||
f"Setpoint {self.reactor.control.setpoint_mw:5.0f} MW Reactivity {self.state.core.reactivity_margin:+.4f}\n"
|
||||
f"DNB {self.state.core.dnb_margin:4.2f} Subcool {self.state.core.subcooling_margin:4.1f}K"
|
||||
)
|
||||
self.trend_panel.update(self._trend_text())
|
||||
self.poison_panel.update(self._poison_text())
|
||||
self.primary_panel.update(
|
||||
f"[bold cyan]Primary Loop[/bold cyan]\\n"
|
||||
f"Flow {self.state.primary_loop.mass_flow_rate:7.0f}/{self.reactor.primary_pump.nominal_flow * len(self.reactor.primary_pump_units):.0f} kg/s\\n"
|
||||
f"Level {self.state.primary_loop.level*100:6.1f}%\\n"
|
||||
f"Tin {self.state.primary_loop.temperature_in:7.1f} K Tout {self.state.primary_loop.temperature_out:7.1f} K (Target {constants.PRIMARY_OUTLET_TARGET_K:4.0f})\\n"
|
||||
f"P {self.state.primary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa Pressurizer {self.reactor.pressurizer_level*100:6.1f}% @ {constants.PRIMARY_PRESSURIZER_SETPOINT_MPA:4.1f} MPa\\n"
|
||||
"[bold cyan]Primary Loop[/bold cyan]\n"
|
||||
f"Flow {self.state.primary_loop.mass_flow_rate:7.0f}/{self.reactor.primary_pump.nominal_flow * len(self.reactor.primary_pump_units):.0f} kg/s\n"
|
||||
f"Level {self.state.primary_loop.level*100:6.1f}%\n"
|
||||
f"Tin {self.state.primary_loop.temperature_in:7.1f} K Tout {self.state.primary_loop.temperature_out:7.1f} K (Target {constants.PRIMARY_OUTLET_TARGET_K:4.0f})\n"
|
||||
f"P {self.state.primary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa Pressurizer {self.reactor.pressurizer_level*100:6.1f}% @ {constants.PRIMARY_PRESSURIZER_SETPOINT_MPA:4.1f} MPa\n"
|
||||
f"Relief {'OPEN' if self.reactor.primary_relief_open else 'CLOSED'} Pumps {[p.status for p in self.state.primary_pumps]}"
|
||||
)
|
||||
self.secondary_panel.update(
|
||||
f"[bold cyan]Secondary Loop[/bold cyan]\\n"
|
||||
f"Flow {self.state.secondary_loop.mass_flow_rate:7.0f}/{self.reactor.secondary_pump.nominal_flow * len(self.reactor.secondary_pump_units):.0f} kg/s\\n"
|
||||
f"Level {self.state.secondary_loop.level*100:6.1f}%\\n"
|
||||
f"Tin {self.state.secondary_loop.temperature_in:7.1f} K Tout {self.state.secondary_loop.temperature_out:7.1f} K (Target {constants.SECONDARY_OUTLET_TARGET_K:4.0f})\\n"
|
||||
f"P {self.state.secondary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa q {self.state.secondary_loop.steam_quality:5.2f}/1.00\\n"
|
||||
"[bold cyan]Secondary Loop[/bold cyan]\n"
|
||||
f"Flow {self.state.secondary_loop.mass_flow_rate:7.0f}/{self.reactor.secondary_pump.nominal_flow * len(self.reactor.secondary_pump_units):.0f} kg/s\n"
|
||||
f"Level {self.state.secondary_loop.level*100:6.1f}%\n"
|
||||
f"Tin {self.state.secondary_loop.temperature_in:7.1f} K Tout {self.state.secondary_loop.temperature_out:7.1f} K (Target {constants.SECONDARY_OUTLET_TARGET_K:4.0f})\n"
|
||||
f"P {self.state.secondary_loop.pressure:5.2f}/{constants.MAX_PRESSURE:4.1f} MPa q {self.state.secondary_loop.steam_quality:5.2f}/1.00\n"
|
||||
f"Relief {'OPEN' if self.reactor.secondary_relief_open else 'CLOSED'} Pumps {[p.status for p in self.state.secondary_pumps]}"
|
||||
)
|
||||
self.turbine_panel.update(self._turbine_text())
|
||||
self.generator_panel.update(self._generator_text())
|
||||
self.power_panel.update(self._power_text())
|
||||
self.hx_panel.update(
|
||||
f"[bold cyan]Heat Exchanger[/bold cyan]\\n"
|
||||
f"ΔT (pri-sec) {self.state.primary_to_secondary_delta_t:4.0f} K\\n"
|
||||
"[bold cyan]Heat Exchanger[/bold cyan]\n"
|
||||
f"ΔT (pri-sec) {self.state.primary_to_secondary_delta_t:4.0f} K\n"
|
||||
f"Efficiency {self.state.heat_exchanger_efficiency*100:5.1f}%"
|
||||
)
|
||||
self.protection_panel.update(self._protection_text())
|
||||
@@ -272,15 +360,15 @@ class TextualDashboard(App):
|
||||
self.help_panel.update(self._help_text())
|
||||
failures = ", ".join(self.reactor.health_monitor.failure_log) if self.reactor.health_monitor.failure_log else "None"
|
||||
self.status_panel.update(
|
||||
f"[bold cyan]Status[/bold cyan]\\n"
|
||||
f"Time {self.state.time_elapsed:6.1f}s\\n"
|
||||
f"Consumer {'ON' if (self.reactor.consumer and self.reactor.consumer.online) else 'OFF'} Demand {self.reactor.consumer.demand_mw if self.reactor.consumer else 0.0:5.1f} MW\\n"
|
||||
"[bold cyan]Status[/bold cyan]\n"
|
||||
f"Time {self.state.time_elapsed:6.1f}s\n"
|
||||
f"Consumer {'ON' if (self.reactor.consumer and self.reactor.consumer.online) else 'OFF'} Demand {self.reactor.consumer.demand_mw if self.reactor.consumer else 0.0:5.1f} MW\n"
|
||||
f"Failures: {failures}"
|
||||
)
|
||||
|
||||
def _trend_text(self) -> str:
|
||||
if len(self._trend_history) < 2:
|
||||
return "[bold cyan]Trends[/bold cyan]\\nFuel Temp Δ n/a\\nCore Power Δ n/a"
|
||||
return "[bold cyan]Trends[/bold cyan]\nFuel Temp Δ n/a\nCore Power Δ n/a"
|
||||
start_t, start_temp, start_power = self._trend_history[0]
|
||||
end_t, end_temp, end_power = self._trend_history[-1]
|
||||
duration = max(1.0, end_t - start_t)
|
||||
@@ -289,8 +377,8 @@ class TextualDashboard(App):
|
||||
temp_rate = temp_delta / duration
|
||||
power_rate = power_delta / duration
|
||||
return (
|
||||
"[bold cyan]Trends[/bold cyan]\\n"
|
||||
f"Fuel Temp Δ {end_temp:7.1f} K (Δ{temp_delta:+6.1f} / {duration:4.0f}s, {temp_rate:+5.2f}/s)\\n"
|
||||
"[bold cyan]Trends[/bold cyan]\n"
|
||||
f"Fuel Temp Δ {end_temp:7.1f} K (Δ{temp_delta:+6.1f} / {duration:4.0f}s, {temp_rate:+5.2f}/s)\n"
|
||||
f"Core Power Δ {end_power:7.1f} MW (Δ{power_delta:+6.1f} / {duration:4.0f}s, {power_rate:+5.2f}/s)"
|
||||
)
|
||||
|
||||
@@ -302,11 +390,11 @@ class TextualDashboard(App):
|
||||
iodine = inventory.get("I", 0.0)
|
||||
xe_drho = getattr(self.state.core, "reactivity_margin", 0.0)
|
||||
return (
|
||||
"[bold cyan]Key Poisons / Emitters[/bold cyan]\\n"
|
||||
f"Xe (xenon): {xe:9.2e}\\n"
|
||||
f"Sm (samarium): {sm:9.2e}\\n"
|
||||
f"I (iodine): {iodine:9.2e}\\n"
|
||||
f"Xe Δρ: {xe_drho:+.4f}\\n"
|
||||
"[bold cyan]Key Poisons / Emitters[/bold cyan]\n"
|
||||
f"Xe (xenon): {xe:9.2e}\n"
|
||||
f"Sm (samarium): {sm:9.2e}\n"
|
||||
f"I (iodine): {iodine:9.2e}\n"
|
||||
f"Xe Δρ: {xe_drho:+.4f}\n"
|
||||
f"Neutrons (src): {particles.get('neutrons', 0.0):.2e}"
|
||||
)
|
||||
|
||||
@@ -331,7 +419,7 @@ class TextualDashboard(App):
|
||||
lines.append(f"Electrical {self.state.total_electrical_output():7.1f} MW Load {self._total_load_supplied(self.state):7.1f}/{self._total_load_demand(self.state):7.1f} MW")
|
||||
if self.reactor.consumer:
|
||||
lines.append(f"Consumer {'ONLINE' if self.reactor.consumer.online else 'OFF'} Demand {self.reactor.consumer.demand_mw:7.1f} MW")
|
||||
return "\\n".join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _generator_text(self) -> str:
|
||||
lines = ["[bold cyan]Generators[/bold cyan]"]
|
||||
@@ -340,7 +428,7 @@ class TextualDashboard(App):
|
||||
if g.starting:
|
||||
status = "START"
|
||||
lines.append(f"Gen{idx+1}: {status:5} {g.power_output_mw:5.1f} MW batt {g.battery_charge*100:5.1f}%")
|
||||
return "\\n".join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _power_text(self) -> str:
|
||||
draws = getattr(self.state, "aux_draws", {}) or {}
|
||||
@@ -352,9 +440,9 @@ class TextualDashboard(App):
|
||||
gen_out = draws.get("generator_output", 0.0)
|
||||
turb_out = draws.get("turbine_output", 0.0)
|
||||
return (
|
||||
"[bold cyan]Power Stats[/bold cyan]\\n"
|
||||
f"Base Aux {base:5.1f} MW Prim Aux {prim:5.1f} MW Sec Aux {sec:5.1f} MW\\n"
|
||||
f"Aux demand {demand:5.1f} MW supplied {supplied:5.1f} MW\\n"
|
||||
"[bold cyan]Power Stats[/bold cyan]\n"
|
||||
f"Base Aux {base:5.1f} MW Prim Aux {prim:5.1f} MW Sec Aux {sec:5.1f} MW\n"
|
||||
f"Aux demand {demand:5.1f} MW supplied {supplied:5.1f} MW\n"
|
||||
f"Gen out {gen_out:5.1f} MW Turbine out {turb_out:5.1f} MW"
|
||||
)
|
||||
|
||||
@@ -375,17 +463,17 @@ class TextualDashboard(App):
|
||||
lines.append(f"DNB margin {self.state.core.dnb_margin:4.2f}")
|
||||
lines.append(f"Subcooling {self.state.core.subcooling_margin:5.1f} K")
|
||||
lines.append(f"Reliefs pri={'OPEN' if self.reactor.primary_relief_open else 'CLOSED'} sec={'OPEN' if self.reactor.secondary_relief_open else 'CLOSED'}")
|
||||
return "\\n".join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _maintenance_text(self) -> str:
|
||||
active = list(self.reactor.maintenance_active)
|
||||
return "[bold cyan]Maintenance[/bold cyan]\\nActive: " + (", ".join(active) if active else "None")
|
||||
return "[bold cyan]Maintenance[/bold cyan]\nActive: " + (", ".join(active) if active else "None")
|
||||
|
||||
def _health_text(self) -> str:
|
||||
lines = ["[bold cyan]Component Health[/bold cyan]"]
|
||||
for name, comp in self.reactor.health_monitor.components.items():
|
||||
lines.append(_bar(name, comp.integrity))
|
||||
return "\\n".join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _help_text(self) -> str:
|
||||
tips = [
|
||||
@@ -395,7 +483,7 @@ class TextualDashboard(App):
|
||||
"Use m/n/,/. in curses; mapped to j/k etc here.",
|
||||
"F12 saves a snapshot; set FISSION_SNAPSHOT_AT for auto.",
|
||||
]
|
||||
return "[bold cyan]Controls & Tips[/bold cyan]\\n" + "\\n".join(f"- {t}" for t in tips)
|
||||
return "[bold cyan]Controls & Tips[/bold cyan]\n" + "\n".join(f"- {t}" for t in tips)
|
||||
|
||||
def _log_text(self) -> str:
|
||||
lines = ["[bold cyan]Logs[/bold cyan]"]
|
||||
@@ -403,7 +491,7 @@ class TextualDashboard(App):
|
||||
lines.append("No recent logs.")
|
||||
else:
|
||||
lines.extend(list(self.log_buffer))
|
||||
return "\\n".join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _turbine_status_lines(self) -> list[str]:
|
||||
if not self.reactor.turbine_unit_active:
|
||||
@@ -437,14 +525,19 @@ class TextualDashboard(App):
|
||||
def _save_snapshot(self, auto: bool = False) -> None:
|
||||
try:
|
||||
self.snapshot_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.snapshot_path.write_text("\\n".join(self._snapshot_lines()))
|
||||
self.snapshot_path.write_text("\n".join(self._snapshot_lines()))
|
||||
self.snapshot_done = True
|
||||
LOGGER.info("Saved dashboard snapshot to %s%s", self.snapshot_path, " (auto)" if auto else "")
|
||||
except Exception as exc: # pragma: no cover
|
||||
LOGGER.error("Failed to save snapshot: %s", exc)
|
||||
|
||||
def _install_log_capture(self) -> None:
|
||||
# Silence existing root handlers to avoid spewing logs over the UI.
|
||||
root = logging.getLogger()
|
||||
root.handlers = []
|
||||
# Capture reactor_sim logs into the on-screen log buffer.
|
||||
logger = logging.getLogger("reactor_sim")
|
||||
logger.propagate = False
|
||||
self._previous_handlers = list(logger.handlers)
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(logging.INFO)
|
||||
@@ -465,6 +558,45 @@ class TextualDashboard(App):
|
||||
if self._log_handler and self._log_handler in logger.handlers:
|
||||
logger.removeHandler(self._log_handler)
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None: # type: ignore[override]
|
||||
mapping = {
|
||||
"scram": self.action_scram,
|
||||
"p1": self.action_toggle_primary_p1,
|
||||
"p2": self.action_toggle_primary_p2,
|
||||
"s1": self.action_toggle_secondary_p1,
|
||||
"s2": self.action_toggle_secondary_p2,
|
||||
"relief_pri": self.action_toggle_primary_relief,
|
||||
"relief_sec": self.action_toggle_secondary_relief,
|
||||
"turbines": self.action_toggle_turbine_bank,
|
||||
"t1": lambda: self.action_toggle_turbine_unit("1"),
|
||||
"t2": lambda: self.action_toggle_turbine_unit("2"),
|
||||
"t3": lambda: self.action_toggle_turbine_unit("3"),
|
||||
"g1": self.action_toggle_generator1,
|
||||
"g2": self.action_toggle_generator2,
|
||||
"consumer": self.action_toggle_consumer,
|
||||
"auto_rods": self.action_toggle_auto_rods,
|
||||
"rods_plus": self.action_rods_insert,
|
||||
"rods_minus": self.action_rods_withdraw,
|
||||
"demand_up": self.action_demand_up,
|
||||
"demand_down": self.action_demand_down,
|
||||
"sp_up": self.action_setpoint_up,
|
||||
"sp_down": self.action_setpoint_down,
|
||||
"snapshot": lambda: self._save_snapshot(auto=False),
|
||||
"m_core": self.action_maintain_core,
|
||||
"m_p1": self.action_maintain_primary_p1,
|
||||
"m_p2": self.action_maintain_primary_p2,
|
||||
"m_s1": self.action_maintain_secondary_p1,
|
||||
"m_s2": self.action_maintain_secondary_p2,
|
||||
"m_t1": self.action_maintain_turbine_1,
|
||||
"m_t2": self.action_maintain_turbine_2,
|
||||
"m_t3": self.action_maintain_turbine_3,
|
||||
"m_g1": self.action_maintain_generator_1,
|
||||
"m_g2": self.action_maintain_generator_2,
|
||||
}
|
||||
handler = mapping.get(event.button.id or "")
|
||||
if handler:
|
||||
handler()
|
||||
|
||||
|
||||
def run_textual_dashboard(reactor: Reactor, start_state: Optional[PlantState], timestep: float, save_path: Optional[str]) -> None:
|
||||
app = TextualDashboard(reactor, start_state, timestep=timestep, save_path=save_path)
|
||||
|
||||
Reference in New Issue
Block a user