Skip to content

Commit 4d450b5

Browse files
grdddjmroz22
authored andcommitted
feat: supporting TropicSquare emulator and running it for T3W1
1 parent 98593ee commit 4d450b5

16 files changed

+768
-40
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ channel_data.json
1515
.vscode
1616
.mypy_cache
1717
docker/version.txt
18-
logs/debugging.log
19-
logs/emulator_bridge.log
18+
logs/*.log
2019
logs/screens
2120
trezor-suite
21+
.model_config_save.yaml

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies = [
1010
"websockets>=12.0",
1111
"psutil>=5.8.0,<6",
1212
"Pillow>=10.0.0,<11",
13+
"tvl @ https://github.com/tropicsquare/ts-tvl/releases/download/2.3/tvl-2.3-py3-none-any.whl",
1314
]
1415

1516
[dependency-groups]

src/controller.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import bridge
1616
import emulator
1717
import helpers
18+
import tropic_model
1819
from bitcoin_regtest.rpc import BTCJsonRPC
1920

2021
if TYPE_CHECKING:
@@ -32,6 +33,7 @@ class BackgroundCheckResponse(TypedDict):
3233
bridge_status: bool | Any
3334
emulator_status: bool | Any
3435
regtest_status: bool | Any
36+
tropic_status: bool | Any
3537
background_check: bool
3638

3739
ResponseType = Union[NormalResponse, BackgroundCheckResponse]
@@ -115,20 +117,25 @@ def run_command_and_get_its_response(self) -> "ResponseType":
115117
bridge_status = bridge.get_status()
116118
emulator_status = emulator.get_status()
117119
regtest_status = is_regtest_active()
120+
tropic_status = tropic_model.get_status()
118121
return {
119122
"response": "Background check done",
120123
"bridge_status": bridge_status,
121124
"emulator_status": emulator_status,
122125
"regtest_status": regtest_status,
126+
"tropic_status": tropic_status,
123127
"background_check": True,
124128
}
125129
elif self.command == "exit":
126130
emulator.stop()
127131
bridge.stop()
132+
tropic_model.stop()
128133
log("Exiting", "red")
129134
exit(1)
130135
elif self.command.startswith("bridge"):
131136
return self.run_bridge_command()
137+
elif self.command.startswith("tropic"):
138+
return self.run_tropic_command()
132139
elif self.command.startswith("emulator"):
133140
return self.run_emulator_command()
134141
elif self.command.startswith("regtest"):
@@ -159,6 +166,20 @@ def run_bridge_command(self) -> "ResponseType":
159166
"error": f"Unknown bridge command - {self.command}",
160167
}
161168

169+
def run_tropic_command(self) -> "ResponseType":
170+
if self.command == "tropic-start":
171+
output_to_logfile = self.request_dict.get("output_to_logfile", True)
172+
tropic_model.start(output_to_logfile=output_to_logfile)
173+
return {"response": "Tropic Square model server started"}
174+
elif self.command == "tropic-stop":
175+
tropic_model.stop()
176+
return {"response": "Tropic Square model server stopped"}
177+
else:
178+
return {
179+
"success": False,
180+
"error": f"Unknown tropic command - {self.command}",
181+
}
182+
162183
def run_emulator_command(self) -> "ResponseType":
163184
global PREV_RUNNING_MODEL
164185

@@ -189,6 +210,12 @@ def run_emulator_command(self) -> "ResponseType":
189210
if model != PREV_RUNNING_MODEL:
190211
wipe = True
191212
PREV_RUNNING_MODEL = model
213+
214+
# Auto-start Tropic Square model server for T3W1 emulator
215+
if model == "T3W1" and not tropic_model.is_running():
216+
log("T3W1 requires Tropic model server, starting automatically...")
217+
tropic_model.start(output_to_logfile=output_to_logfile)
218+
192219
emulator.start(
193220
version=version,
194221
model=model,

src/dashboard/index.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,35 @@ <h3>Bridge</h3>
9494
</div>
9595
</section>
9696

97+
<section>
98+
<h3>Tropic Square Model Server</h3>
99+
<div>
100+
<i>Control Tropic Square model server (required for T3W1 emulator).</i><br />
101+
<button class="positive" @click="tropicStart()">Start</button>
102+
<button class="negative" @click="tropicStop()">Stop</button>
103+
<input
104+
id="tropicUseLogfile"
105+
type="checkbox"
106+
v-model="tropic.outputToLogfile"
107+
/>
108+
<label
109+
class="underlined"
110+
title="Save tropic model logs into logfile. Otherwise, logs are shown in console."
111+
for="tropicUseLogfile"
112+
>Logs into logfile</label
113+
><br />
114+
</div>
115+
<div>
116+
<div :style="{ color: tropic.statusColor }">
117+
<b>Status: <span>{{ tropic.status }}</span></b>
118+
</div>
119+
<div class="explain-note">
120+
The Tropic Square model server is automatically started when launching
121+
a T3W1 emulator, but can also be controlled manually here.
122+
</div>
123+
</div>
124+
</section>
125+
97126
<section>
98127
<h3>Emulator</h3>
99128
<div class="explain-note">

src/dashboard/js/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const app = createApp({
1515
statusColor: "black",
1616
hasSuiteLocal: false,
1717
},
18+
tropic: {
19+
outputToLogfile: true,
20+
status: "unknown",
21+
statusColor: "black",
22+
},
1823
emulators: {
1924
versions: {
2025
T1B1: {
@@ -264,6 +269,17 @@ const app = createApp({
264269
type: "bridge-stop",
265270
});
266271
},
272+
tropicStart() {
273+
this.sendMessage({
274+
type: "tropic-start",
275+
output_to_logfile: this.tropic.outputToLogfile,
276+
});
277+
},
278+
tropicStop() {
279+
this.sendMessage({
280+
type: "tropic-stop",
281+
});
282+
},
267283
emulatorStart(model) {
268284
const emulator = this.emulators.versions[model];
269285
if (!emulator) {
@@ -345,6 +361,9 @@ const app = createApp({
345361
if ("emulator_status" in dataObject) {
346362
this.reflectEmulatorSituation(dataObject.emulator_status);
347363
}
364+
if ("tropic_status" in dataObject) {
365+
this.reflectTropicSituation(dataObject.tropic_status);
366+
}
348367
if ("regtest_status" in dataObject) {
349368
this.reflectRegtestSituation(dataObject.regtest_status);
350369
}
@@ -374,6 +393,13 @@ const app = createApp({
374393
this.writeEmulatorStatus("Stopped", "red");
375394
}
376395
},
396+
reflectTropicSituation(status) {
397+
if (status.is_running) {
398+
this.writeTropicStatus(`Running - ${status.version}`, "green");
399+
} else {
400+
this.writeTropicStatus("Stopped", "red");
401+
}
402+
},
377403
reflectRegtestSituation(is_running) {
378404
if (is_running) {
379405
this.writeRegtestStatus("Running", "green");
@@ -389,6 +415,10 @@ const app = createApp({
389415
this.emulators.status = status;
390416
this.emulators.statusColor = color;
391417
},
418+
writeTropicStatus(status, color = "black") {
419+
this.tropic.status = status;
420+
this.tropic.statusColor = color;
421+
},
392422
writeRegtestStatus(status, color = "black") {
393423
this.regtest.status = status;
394424
this.regtest.statusColor = color;

src/helpers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
LOG_FILENAME = str(LOG_DIR / "debugging.log")
1919
EMU_BRIDGE_LOG = str(LOG_DIR / "emulator_bridge.log")
20+
TROPIC_MODEL_LOG = str(LOG_DIR / "tropic_model.log")
2021

2122

2223
logging.basicConfig(

src/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
import dashboard
99
import emulator
1010
import helpers
11+
import tropic_model
1112

1213

1314
def cleanup() -> None:
1415
emulator.stop()
1516
bridge.stop()
17+
tropic_model.stop()
1618

1719

1820
atexit.register(cleanup)

src/tropic_model.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import time
5+
from pathlib import Path
6+
from typing import TYPE_CHECKING
7+
8+
from psutil import Popen
9+
10+
import helpers
11+
12+
if TYPE_CHECKING:
13+
from typing_extensions import TypedDict
14+
15+
class StatusResponse(TypedDict):
16+
is_running: bool
17+
version: str | None
18+
19+
20+
CURRENT_TVL_VERSION = "tvl-2.3"
21+
22+
TROPIC_SERVER: Popen | None = None
23+
VERSION_RUNNING: str | None = None
24+
25+
LOG_COLOR = "yellow"
26+
ROOT_DIR = Path(__file__).resolve().parent.parent
27+
TROPIC_MODEL_DIR = ROOT_DIR / "tropic_model"
28+
START_SCRIPT = TROPIC_MODEL_DIR / "start-emulator.sh"
29+
30+
31+
def log(text: str, color: str = LOG_COLOR) -> None:
32+
helpers.log(f"TROPIC_MODEL: {text}", color)
33+
34+
35+
def is_running() -> bool:
36+
"""Check if tropic model server process is running"""
37+
if TROPIC_SERVER is None:
38+
return False
39+
return TROPIC_SERVER.is_running()
40+
41+
42+
def get_status() -> "StatusResponse":
43+
"""Return status dict with running state and version"""
44+
return {"is_running": is_running(), "version": VERSION_RUNNING}
45+
46+
47+
def start(output_to_logfile: bool = True) -> None:
48+
"""Start the Tropic Square model server"""
49+
log("Starting")
50+
global TROPIC_SERVER
51+
global VERSION_RUNNING
52+
53+
# In case the server was killed outside of the stop() function
54+
# (for example manually by the user), we need to reflect the situation
55+
if TROPIC_SERVER is not None and not is_running():
56+
log("Tropic server was probably killed manually, resetting local state")
57+
stop()
58+
59+
if TROPIC_SERVER is not None:
60+
log(
61+
"WARNING: Tropic model server is already running, not spawning a new one",
62+
"red",
63+
)
64+
return
65+
66+
# Verify the start script exists
67+
if not START_SCRIPT.exists():
68+
raise RuntimeError(
69+
f"Tropic model start script does not exist at {START_SCRIPT}"
70+
)
71+
72+
# Build command to run the start script
73+
command_list = ["bash", str(START_SCRIPT)]
74+
75+
# Spawn the process, optionally redirecting output to logfile
76+
if output_to_logfile:
77+
log_file = open(helpers.TROPIC_MODEL_LOG, "a")
78+
log(f"All tropic model output redirected to {helpers.TROPIC_MODEL_LOG}")
79+
TROPIC_SERVER = Popen(command_list, stdout=log_file, stderr=log_file)
80+
else:
81+
TROPIC_SERVER = Popen(command_list)
82+
83+
log(f"Tropic server spawned: {TROPIC_SERVER}. CMD: {TROPIC_SERVER.cmdline()}")
84+
85+
# Verifying if the server is really running
86+
time.sleep(1.0)
87+
if not TROPIC_SERVER.is_running():
88+
TROPIC_SERVER = None
89+
raise RuntimeError("Tropic model server is unable to run!")
90+
91+
VERSION_RUNNING = CURRENT_TVL_VERSION
92+
log("Tropic model server started successfully")
93+
94+
95+
def stop() -> None:
96+
"""Stop the Tropic Square model server"""
97+
log("Stopping")
98+
global TROPIC_SERVER
99+
global VERSION_RUNNING
100+
101+
if TROPIC_SERVER is None:
102+
log("WARNING: Attempting to stop tropic server, but it is not running", "red")
103+
else:
104+
try:
105+
TROPIC_SERVER.terminate()
106+
TROPIC_SERVER.wait(timeout=5)
107+
except Exception as e:
108+
log(f"Termination failed: {repr(e)}, killing process.", "yellow")
109+
TROPIC_SERVER.kill()
110+
TROPIC_SERVER.wait()
111+
112+
# Ensuring all child processes are cleaned up
113+
for child in TROPIC_SERVER.children(recursive=True):
114+
log(f"Killing child process {child.pid}")
115+
child.kill()
116+
child.wait()
117+
118+
TROPIC_SERVER = None
119+
VERSION_RUNNING = None
120+
log("Tropic model server stopped")

0 commit comments

Comments
 (0)