Skip to content

Commit 1477dd4

Browse files
frahlgclaude
andcommitted
Fix scan reliability and simplify detection UI
- Fix scan timeout: 200ms default was too aggressive, bumped to 5s - Revert to sequential port scanning (parallel overwhelms single-connection devices) - Revert to sequential slave ID detection (same reason) - Replace verbose 20-row detection log with compact progress indicator - All Modbus TCP connections are now strictly sequential per device Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1ec0f02 commit 1477dd4

2 files changed

Lines changed: 53 additions & 96 deletions

File tree

cmd/modbus-debug/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func handleNetworkScan(w http.ResponseWriter, r *http.Request) {
6060
if req.Port == 0 {
6161
req.Port = 502
6262
}
63-
timeout := 200 * time.Millisecond
63+
timeout := 5000 * time.Millisecond
6464
if req.Timeout > 0 {
6565
timeout = time.Duration(req.Timeout) * time.Millisecond
6666
}

cmd/modbus-debug/web/index.html

Lines changed: 52 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,8 @@ <h2><span class="step">1</span> Find Your Inverter</h2>
6868
<div class="input-group mt-2">
6969
<label>Scan timeout</label>
7070
<select x-model.number="scanTimeoutMs" style="max-width: 200px;">
71-
<option value="500">500ms (fast LAN only)</option>
72-
<option value="2000">2000ms (default)</option>
73-
<option value="5000">5000ms (Wi-Fi dongles)</option>
71+
<option value="2000">2000ms (fast LAN)</option>
72+
<option value="5000">5000ms (default)</option>
7473
<option value="10000">10000ms (very slow devices)</option>
7574
</select>
7675
</div>
@@ -181,27 +180,18 @@ <h2>
181180
</h2>
182181
<p class="text-sm text-muted mb-2">Trying to identify your inverter by reading serial number registers...</p>
183182

184-
<!-- Detection progress log -->
185-
<div x-show="detectLog.length > 0" class="mt-4">
186-
<div x-show="detectSlaveID !== null" class="text-sm text-muted mb-2">
187-
Trying slave ID <strong x-text="detectSlaveID"></strong>
188-
</div>
183+
<!-- Detection progress -->
184+
<div x-show="detectLoading || detectLog.length > 0" class="mt-4">
189185
<div class="scan-log">
190-
<template x-for="entry in detectLog" :key="entry.profile">
191-
<div class="scan-log-entry" :class="{ done: entry.status !== 'trying' }">
192-
<span>
193-
<span x-text="entry.displayName"></span>
194-
<span class="text-muted text-sm" x-text="'(reg ' + entry.serialAddr + ')'"></span>
195-
</span>
196-
<span x-show="entry.status === 'trying'" class="flex items-center gap-2"><span class="spinner"></span> reading...</span>
197-
<span x-show="entry.status === 'found'" class="badge badge-ok" x-text="'MATCH' + (entry.serial ? ' — ' + entry.serial : '')"></span>
198-
<span x-show="entry.status === 'no_match'" class="flex items-center gap-2">
199-
<span class="badge badge-info">no match</span>
200-
<span class="text-muted text-sm" x-text="entry.durationMs != null ? entry.durationMs + 'ms' : ''"></span>
201-
</span>
202-
<span x-show="entry.status === 'error'" class="badge badge-fail">error</span>
203-
</div>
204-
</template>
186+
<!-- Slave ID being tried -->
187+
<div class="scan-log-entry" x-show="detectSlaveID !== null">
188+
<span>Slave ID <strong x-text="detectSlaveID"></strong></span>
189+
<span x-show="detectLoading" class="flex items-center gap-2">
190+
<span class="spinner"></span>
191+
<span class="text-muted text-sm" x-text="detectProgress"></span>
192+
</span>
193+
<span x-show="!detectLoading && !detection?.detected" class="badge badge-info">no match</span>
194+
</div>
205195
</div>
206196
</div>
207197

@@ -492,7 +482,7 @@ <h2>History</h2>
492482
scanPorts: [],
493483
scanLog: [],
494484
customPortInput: '',
495-
scanTimeoutMs: 2000,
485+
scanTimeoutMs: 5000,
496486

497487
// Target
498488
target: { host: '', port: 502, slaveID: 1 },
@@ -506,6 +496,7 @@ <h2>History</h2>
506496
detectLoading: false,
507497
detectLog: [],
508498
detectSlaveID: null,
499+
detectProgress: '',
509500
profiles: [],
510501
selectedProfile: '',
511502

@@ -584,11 +575,12 @@ <h2>History</h2>
584575
const ports = this.scanPorts.filter(p => p.enabled);
585576
if (ports.length === 0) { this.scanning = false; return; }
586577

587-
// Show all ports as "scanning" immediately
588-
this.scanLog = ports.map(p => ({ port: p.port, desc: p.description, status: 'scanning', found: 0 }));
578+
// Scan ports sequentially (parallel ports overwhelm slow devices like Wi-Fi dongles)
579+
// Hosts within each port are still scanned in parallel (256 concurrent)
580+
let allResults = [];
581+
for (const portInfo of ports) {
582+
this.scanLog = [...this.scanLog, { port: portInfo.port, desc: portInfo.description, status: 'scanning', found: 0 }];
589583

590-
// Fire all port scans in parallel
591-
const promises = ports.map(async (portInfo) => {
592584
try {
593585
const res = await fetch('/api/network/scan', {
594586
method: 'POST',
@@ -597,23 +589,18 @@ <h2>History</h2>
597589
});
598590
const hosts = await res.json();
599591
const found = Array.isArray(hosts) ? hosts : [];
592+
allResults = [...allResults, ...found];
600593

601-
// Update this port's log entry to done
602594
this.scanLog = this.scanLog.map(e =>
603595
e.port === portInfo.port ? { ...e, status: 'done', found: found.length } : e
604596
);
605-
606-
return found;
607597
} catch (e) {
608598
console.error('Scan error port ' + portInfo.port + ':', e);
609599
this.scanLog = this.scanLog.map(e =>
610600
e.port === portInfo.port ? { ...e, status: 'done', found: 0 } : e
611601
);
612-
return [];
613602
}
614-
});
615-
616-
const allResults = (await Promise.all(promises)).flat();
603+
}
617604

618605
// Deduplicate by ip:port
619606
const seen = new Set();
@@ -665,18 +652,15 @@ <h2>History</h2>
665652
else if (tcpMs > 50) timeoutMs = 1000;
666653
}
667654

668-
// Show all profiles as "trying"
669-
this.detectLog = this.profiles.map(p => ({
670-
profile: p.name,
671-
displayName: p.display_name,
672-
serialAddr: p.serial_address || '?',
673-
status: 'trying'
674-
}));
655+
let found = false;
675656

676-
this.detectSlaveID = 'all (1, 0, 247)';
657+
// Try slave IDs sequentially (many devices only allow 1 TCP connection)
658+
for (const slaveID of slaveIDs) {
659+
if (found) break;
660+
this.detectSlaveID = slaveID;
661+
this.detectProgress = 'Testing ' + this.profiles.length + ' profiles...';
662+
this.detectLog = [{ slaveID }];
677663

678-
// Fire all slave IDs in parallel
679-
const promises = slaveIDs.map(async (slaveID) => {
680664
try {
681665
const res = await fetch('/api/diagnose/detect-batch', {
682666
method: 'POST',
@@ -689,60 +673,33 @@ <h2>History</h2>
689673
})
690674
});
691675
const results = await res.json();
692-
if (!Array.isArray(results)) return null;
693-
const match = results.find(r => r.detected);
694-
return { slaveID, results, match: match || null };
695-
} catch (e) {
696-
return null;
697-
}
698-
});
699676

700-
const allResults = await Promise.all(promises);
701-
702-
// Find the first match (prefer slave ID order: 1, 0, 247)
703-
let winner = null;
704-
let bestResults = null;
705-
for (const r of allResults) {
706-
if (r && r.match && !winner) {
707-
winner = r;
708-
}
709-
// Use results from slave ID 1 for the log display (or first non-null)
710-
if (r && r.results && !bestResults) {
711-
bestResults = r;
677+
if (Array.isArray(results)) {
678+
const match = results.find(r => r.detected);
679+
const totalMs = results.reduce((sum, r) => sum + (r.duration_ms || 0), 0);
680+
681+
if (match) {
682+
this.detectProgress = '';
683+
this.detection = {
684+
detected: true,
685+
profile_name: match.profile,
686+
display_name: match.display_name,
687+
serial: match.serial,
688+
slave_id: slaveID
689+
};
690+
this.selectedProfile = match.profile;
691+
this.target.slaveID = slaveID;
692+
found = true;
693+
} else {
694+
this.detectProgress = results.length + ' profiles tested in ' + totalMs + 'ms — no match';
695+
}
696+
}
697+
} catch (e) {
698+
this.detectProgress = 'Connection error';
712699
}
713700
}
714701

715-
if (winner) {
716-
this.detectSlaveID = winner.slaveID;
717-
this.detectLog = winner.results.map(r => ({
718-
profile: r.profile,
719-
displayName: r.display_name,
720-
serialAddr: (this.profiles.find(p => p.name === r.profile) || {}).serial_address || '?',
721-
status: r.detected ? 'found' : 'no_match',
722-
durationMs: r.duration_ms,
723-
serial: r.serial || ''
724-
}));
725-
this.detection = {
726-
detected: true,
727-
profile_name: winner.match.profile,
728-
display_name: winner.match.display_name,
729-
serial: winner.match.serial,
730-
slave_id: winner.slaveID
731-
};
732-
this.selectedProfile = winner.match.profile;
733-
this.target.slaveID = winner.slaveID;
734-
} else {
735-
// Show results from first slave ID attempt
736-
if (bestResults) {
737-
this.detectLog = bestResults.results.map(r => ({
738-
profile: r.profile,
739-
displayName: r.display_name,
740-
serialAddr: (this.profiles.find(p => p.name === r.profile) || {}).serial_address || '?',
741-
status: 'no_match',
742-
durationMs: r.duration_ms,
743-
serial: ''
744-
}));
745-
}
702+
if (!found) {
746703
this.detection = { detected: false };
747704
}
748705
this.detectLoading = false;

0 commit comments

Comments
 (0)