discoverHpsTerminals function
- int port = hpsDefaultPort,
- Duration budget = hpsScanBudget,
- Duration probeTimeout = hpsProbeTimeout,
- Duration verifyTimeout = hpsVerifyTimeout,
- int concurrency = hpsScanConcurrency,
- bool stopAtFirst = false,
- Future<
List< interfaces()?,LocalIpv4> > - HpsTcpProbe probe = hpsTcpReachable,
- Client? httpClient,
Sucht hobex-HPS-Terminals im lokalen Netz.
Zwei Stufen, und die zweite ist die wesentliche: erst zaehlt ein
TCP-Scan die Adressen mit offenem port auf, dann fragt jede davon
GET /api/terminals. Nur wer darauf mit einer Terminal-Liste antwortet,
ist ein Treffer -- ein offener Port allein sagt nichts, auf 8080 lauscht in
fremden Netzen zu viel anderes.
Das Ergebnis traegt die TIDs gleich mit: mit DiscoveredHpsTerminal.baseUrl und einer TID daraus laesst sich unmittelbar ein HpsClient bauen, ohne eine zweite Runde ans Geraet.
iOS ab 14 verlangt dafuer die Freigabe fuer das lokale Netz. Ohne den
Eintrag NSLocalNetworkUsageDescription in der Info.plist laufen die
Verbindungen ins Leere, und die Suche meldet schlicht nichts gefunden --
ohne Fehler, der darauf hinweist. Wer diese Funktion einbaut, muss den
Eintrag setzen.
budget deckelt den ganzen Durchlauf. Laeuft es ab, hoeren die Arbeiter
auf; was bis dahin gefunden wurde, bleibt im Ergebnis -- ein Terminal, das
nach zwoelf Sekunden auftaucht, ist mehr wert als ein Abbruch mit leerer
Liste.
stopAtFirst bricht ab, sobald ein Terminal gefunden ist. In einer Kasse
mit genau einem Terminal ist das der Normalfall und spart die Haelfte bis
zwei Drittel der Wartezeit: gemessen 18,7 s ueber drei Netze gegenueber
rund 8 s, wenn nach dem Treffer im ersten Netz Schluss ist. Der Bericht
HpsDiscoveryResult.scanned fuehrt dann nur die tatsaechlich abgesuchten
Netze -- was er auch soll.
interfaces, probe und httpClient sind Naehte fuer Tests; die echte
Suche braucht sie nicht.
Implementation
Future<HpsDiscoveryResult> discoverHpsTerminals({
int port = hpsDefaultPort,
Duration budget = hpsScanBudget,
Duration probeTimeout = hpsProbeTimeout,
Duration verifyTimeout = hpsVerifyTimeout,
int concurrency = hpsScanConcurrency,
bool stopAtFirst = false,
Future<List<LocalIpv4>> Function()? interfaces,
HpsTcpProbe probe = hpsTcpReachable,
http.Client? httpClient,
}) async {
final clock = Stopwatch()..start();
final netze = selectScanInterfaces(
await (interfaces ?? listLocalIpv4)(),
);
final scanned = <ScannedHpsSubnet>[];
final found = <DiscoveredHpsTerminal>[];
final client = httpClient ?? http.Client();
final ownsClient = httpClient == null;
try {
for (final netz in netze) {
if (stopAtFirst && found.isNotEmpty) break;
final hosts = subnetHosts(netz.address);
scanned.add(ScannedHpsSubnet(
interface: netz.name,
subnet: subnetOf(netz.address),
hosts: hosts.length,
));
if (clock.elapsed >= budget) continue;
final offen = await _scanSubnet(
hosts: hosts,
port: port,
timeout: probeTimeout,
concurrency: concurrency,
budget: budget - clock.elapsed,
probe: probe,
);
for (final host in offen) {
// Kein Budget-Abbruch hier: die Kandidatenliste ist kurz (ein offener
// Port 8080 ist selten), und einen gefundenen Kandidaten ungeprueft
// liegen zu lassen waere die teuerste Sparsamkeit -- er faellt dann
// ganz aus dem Ergebnis.
final infos = await _verify(
client: client,
host: host,
port: port,
timeout: verifyTimeout,
);
if (infos == null) continue;
found.add(DiscoveredHpsTerminal(
host: host,
port: port,
terminals: List<TerminalInfo>.unmodifiable(infos),
));
if (stopAtFirst) break;
}
}
} finally {
if (ownsClient) client.close();
}
return HpsDiscoveryResult(
found: List<DiscoveredHpsTerminal>.unmodifiable(found),
scanned: List<ScannedHpsSubnet>.unmodifiable(scanned),
);
}