mirror of
https://github.com/MCKero6423/uv-k5-v3-emulator.git
synced 2026-10-02 03:15:36 +00:00
Start powered off; the user presses On
The server now owns the QEMU process by default but does not launch it. You open the page to a dark screen and press On, which is the behaviour asked for: like walking up to a machine rather than finding it already booted. This inverts the flag from the plan. Owning the process has to be the default, since it is the only way On/Off can work at all; --attach is the opt-in for joining a run.sh instance, where Off is refused. Two tests guard the intent rather than the wiring: one asserts main() has --attach and not --own-emulator, another asserts main() never calls power_on(), so a future edit cannot quietly restore auto-boot. Verified on the live server with no QEMU running beforehand: startup 0 QEMU processes, powered=false, frame.png 503, page 200 On 1 QEMU process, powered=true, frame.png 200 (2920 bytes) Off 0 QEMU processes, frame.png 503, page still 200, keys 409 On again 1 QEMU process, powered=true, frame.png 200 The web server stays up across Off, which is what you asked for: the screen goes dark and waits for the next person to press On.
This commit is contained in:
1 parent
6a9cf9f28a
commit
5b53cbbc82
2 files changed
+72
-4
No files matched your search
@@ -437,5 +437,39 @@ class TestPowerBar(unittest.TestCase):
|
||||
self.assertIn("screen-off", self.body)
|
||||
|
||||
|
||||
class TestStartsPoweredOff(unittest.TestCase):
|
||||
"""The emulator must not be running until the user asks for it."""
|
||||
|
||||
def test_app_with_no_client_serves_a_dark_screen(self):
|
||||
app = webui.create_app(None, frame_addr=0x1000, status_addr=0x2000)
|
||||
app.config.update(TESTING=True)
|
||||
http = app.test_client()
|
||||
|
||||
self.assertEqual(http.get("/").status_code, 200)
|
||||
self.assertFalse(http.get("/api/status").get_json()["powered"])
|
||||
# No frame yet, and that is a state rather than an error.
|
||||
self.assertEqual(http.get("/frame.png").status_code, 503)
|
||||
|
||||
def test_page_offers_an_on_button_while_off(self):
|
||||
app = webui.create_app(None, frame_addr=0x1000, status_addr=0x2000)
|
||||
app.config.update(TESTING=True)
|
||||
body = app.test_client().get("/").get_data(as_text=True)
|
||||
self.assertIn('data-power="on"', body)
|
||||
|
||||
def test_main_has_an_attach_flag_not_an_own_flag(self):
|
||||
"""Owning the process is the default; attaching is the opt-in."""
|
||||
import inspect
|
||||
src = inspect.getsource(webui.main)
|
||||
self.assertIn("--attach", src)
|
||||
self.assertNotIn("--own-emulator", src)
|
||||
|
||||
def test_main_does_not_power_on_at_startup(self):
|
||||
"""Arriving at a dark screen is the point; do not boot it for them."""
|
||||
import inspect
|
||||
src = inspect.getsource(webui.main)
|
||||
# power_on may only appear under the attach branch, never unconditionally.
|
||||
self.assertNotIn("supervisor.power_on()", src)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+38
-4
@@ -19,6 +19,7 @@ Two things worth knowing:
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from flask import Flask, Response, jsonify, request
|
||||
@@ -445,13 +446,46 @@ def main() -> int:
|
||||
ap.add_argument("--host", default="127.0.0.1")
|
||||
ap.add_argument("--port", type=int, default=8080)
|
||||
ap.add_argument("--scale", type=int, default=4)
|
||||
ap.add_argument("--attach", action="store_true",
|
||||
help="attach to an emulator started elsewhere (run.sh) "
|
||||
"instead of managing one. Off is then refused, since "
|
||||
"this server did not start that process.")
|
||||
ap.add_argument("--qemu", default=os.path.expanduser(
|
||||
"~/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm"))
|
||||
ap.add_argument("--elf", default=os.path.expanduser(
|
||||
"~/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf"))
|
||||
ap.add_argument("--flash", default=os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"assets", "flash.img"))
|
||||
ap.add_argument("--gdb-port", type=int, default=1234)
|
||||
args = ap.parse_args()
|
||||
|
||||
from uvk5_qmp import QmpClient
|
||||
client = QmpClient(args.qmp)
|
||||
app = create_app(client, args.frame_addr, args.status_addr, args.scale)
|
||||
print(f"serving on http://{args.host}:{args.port}/ "
|
||||
f"(no authentication; loopback only unless you changed --host)")
|
||||
from uvk5_supervisor import Supervisor, default_launcher, wait_for_socket
|
||||
|
||||
def connect():
|
||||
if not wait_for_socket(args.qmp, timeout=15):
|
||||
raise RuntimeError(f"QMP socket never appeared at {args.qmp}")
|
||||
return QmpClient(args.qmp)
|
||||
|
||||
supervisor = Supervisor(
|
||||
launch=default_launcher(args.qemu, args.flash, args.elf, args.qmp,
|
||||
gdb_port=args.gdb_port),
|
||||
connect=connect)
|
||||
|
||||
if args.attach:
|
||||
# Someone else owns the process; adopt it so the screen works, but Off
|
||||
# will refuse.
|
||||
supervisor.adopt(connect())
|
||||
# Otherwise the emulator stays OFF on purpose. The user presses On, so the
|
||||
# page behaves like walking up to a machine rather than finding it booted.
|
||||
|
||||
app = create_app(supervisor.client(), args.frame_addr, args.status_addr,
|
||||
args.scale, supervisor=supervisor)
|
||||
print(f"serving on http://{args.host}:{args.port}/")
|
||||
print("attached to a running emulator" if args.attach
|
||||
else "emulator is OFF; press On in the browser to boot it")
|
||||
print("no authentication: anyone who can reach this port controls the radio")
|
||||
app.run(host=args.host, port=args.port, threaded=True)
|
||||
return 0
|
||||
|
||||
|
||||
Reference in new issue
Block a user