Link test: UDP echo sweep, soak and TCP each way, driven by the app
CI / Analyze & Test (push) Successful in 10s
CI / Debug APK (push) Successful in 25s

The tablet's app sockets are the only ones bound to the USB link, so the
measurement has to run in the app. tool/linktest_server.py serves the
Orin end and logs every result with its own TCP_INFO. Startable without
touching the screen: am start ... --es linktest <label>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
2026-10-11 15:55:52 +09:00
parent f14202047f
commit 6b5fef41c4
5 changed files with 416 additions and 6 deletions
+39 -6
View File
@@ -6,6 +6,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'boundary.dart';
import 'linktest.dart';
import 'net.dart';
/// Override for development, e.g. `--dart-define=YUKART_GW=ws://halogen.local:7700/`.
@@ -16,32 +17,56 @@ Future<void> main() async {
final usb = UsbLink();
await usb.bindFor(Uri.parse(defaultUrl));
final boundary = Boundary(defaultUrl)..start();
runApp(YukartApp(boundary: boundary, usb: usb));
final linkTest = LinkTest(usb);
// Development hook: `am start ... --es linktest <label>` runs the test on the gateway's host.
Future<void> runLinkTest(String label) async {
navigatorKey.currentState?.push(MaterialPageRoute<void>(builder: (_) => linkTestPage(boundary, linkTest, usb)));
await linkTest.run(Uri.parse(boundary.url).host, label);
await usb.bindFor(Uri.parse(boundary.url));
}
usb.onLinkTest = runLinkTest;
runApp(YukartApp(boundary: boundary, usb: usb, linkTest: linkTest));
final pending = await usb.takeLinkTest();
if (pending != null) {
WidgetsBinding.instance.addPostFrameCallback((_) => runLinkTest(pending));
}
}
final navigatorKey = GlobalKey<NavigatorState>();
Widget linkTestPage(Boundary b, LinkTest t, UsbLink usb) => LinkTestPage(
test: t,
host: Uri.parse(b.url).host,
restoreBinding: () => usb.bindFor(Uri.parse(b.url)),
);
class YukartApp extends StatelessWidget {
const YukartApp({super.key, required this.boundary, required this.usb});
const YukartApp({super.key, required this.boundary, required this.usb, required this.linkTest});
final Boundary boundary;
final UsbLink usb;
final LinkTest linkTest;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Yukart',
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
theme: ThemeData(colorSchemeSeed: Colors.teal, brightness: Brightness.dark, useMaterial3: true),
home: ListenableBuilder(
listenable: Listenable.merge([boundary, usb]),
builder: (context, _) => Readout(boundary: boundary, usb: usb),
builder: (context, _) => Readout(boundary: boundary, usb: usb, linkTest: linkTest),
),
);
}
}
class Readout extends StatelessWidget {
const Readout({super.key, required this.boundary, required this.usb});
const Readout({super.key, required this.boundary, required this.usb, required this.linkTest});
final Boundary boundary;
final UsbLink usb;
final LinkTest linkTest;
@override
Widget build(BuildContext context) {
@@ -53,7 +78,7 @@ class Readout extends StatelessWidget {
children: [
// Mock data must look like mock data: on the surface, not in a menu.
if (b.mock) const MockBanner(),
LinkBar(boundary: b, usb: usb),
LinkBar(boundary: b, usb: usb, linkTest: linkTest),
Expanded(
child: LayoutBuilder(
builder: (context, c) {
@@ -100,9 +125,10 @@ class MockBanner extends StatelessWidget {
}
class LinkBar extends StatelessWidget {
const LinkBar({super.key, required this.boundary, required this.usb});
const LinkBar({super.key, required this.boundary, required this.usb, required this.linkTest});
final Boundary boundary;
final UsbLink usb;
final LinkTest linkTest;
@override
Widget build(BuildContext context) {
@@ -127,6 +153,13 @@ class LinkBar extends StatelessWidget {
Text('last msg $age'),
Text('Orin−tablet clock $offset'),
if (usb.supported) Text('USB link ${usb.up ? 'up' : 'absent'}${usb.bound ? ', bound' : ''}'),
TextButton.icon(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => linkTestPage(b, linkTest, usb)),
),
icon: const Icon(Icons.speed),
label: const Text('Link test'),
),
]),
),
),