Run the frame loop at 40 ms, and time it
500 ms between frames was the research harness's pace, chosen so a person could read the transcript as it scrolled. It became the daemon's pace by inheritance, not by decision, and it is the root of both complaints from real use: false negatives and a slow answer after lifting. The matcher rejects the early frames of a correct press and matches several frames in -- frames 3 and 8 in the acceptance run. So the number of frames a press gets is what decides it, and at 500 ms a one-second press gets two. And a lift is only noticed on the next frame, so the gap is also the latency a user feels before verify-no-match. A frame costs four QTEE round trips whatever the gap; the gap is pure delay on top. 40 ms is a starting point, not a measurement: the loop now logs milliseconds per frame so the real achievable rate is read rather than assumed, and logs the IRQ line alongside the metric so it is visible whether gpio75 tracks the finger under an armed session -- if it does, lift detection can become an edge wait instead of a poll. --frame-gap= overrides it.
This commit is contained in:
parent
1368b2f474
commit
9fbf5e6c73
1 changed files with 26 additions and 4 deletions
|
|
@ -68,7 +68,14 @@ namespace {
|
||||||
constexpr const char* Version = "0.1.0";
|
constexpr const char* Version = "0.1.0";
|
||||||
|
|
||||||
bool g_verbose = false;
|
bool g_verbose = false;
|
||||||
int g_frameGapMs = 500;
|
// 500 ms was the research harness's pace, chosen so a human could read the
|
||||||
|
// transcript scroll by. It is not a design. The matcher rejects the early
|
||||||
|
// frames of a correct press and matches several frames in (frames 3 and 8 in
|
||||||
|
// the acceptance run), so frames-per-press is what decides a press -- and a
|
||||||
|
// lift is only noticed on the NEXT frame, so it is also the latency a user
|
||||||
|
// feels. A frame costs four QTEE round trips regardless; the gap on top is
|
||||||
|
// pure delay.
|
||||||
|
int g_frameGapMs = 40;
|
||||||
int g_samples = 10; // common.max_enrolling_samples, as shipped
|
int g_samples = 10; // common.max_enrolling_samples, as shipped
|
||||||
std::string g_logDir = "/var/log/fingerprintd";
|
std::string g_logDir = "/var/log/fingerprintd";
|
||||||
std::string g_stateDir = "/var/lib/fingerprintd";
|
std::string g_stateDir = "/var/lib/fingerprintd";
|
||||||
|
|
@ -1242,6 +1249,11 @@ public:
|
||||||
bool inPress = false, pressMatched = false, pressRejected = false;
|
bool inPress = false, pressMatched = false, pressRejected = false;
|
||||||
int pressFrames = 0, rescans = 0;
|
int pressFrames = 0, rescans = 0;
|
||||||
std::uint32_t pressFid = 0;
|
std::uint32_t pressFid = 0;
|
||||||
|
auto t0 = std::chrono::steady_clock::now();
|
||||||
|
auto msSince = [&](auto t) {
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - t).count();
|
||||||
|
};
|
||||||
|
|
||||||
// The reference frame loop is {QUERY, CAPTURE, REPORT, QUERY, REPORT}.
|
// The reference frame loop is {QUERY, CAPTURE, REPORT, QUERY, REPORT}.
|
||||||
// The trailing query acknowledges the trustlet's event state; without
|
// The trailing query acknowledges the trustlet's event state; without
|
||||||
|
|
@ -1300,13 +1312,22 @@ public:
|
||||||
out.decided = true; out.matched = true; out.fid = pressFid;
|
out.decided = true; out.matched = true; out.fid = pressFid;
|
||||||
std::println(" press {}: {} frames -> MATCH fid={}", out.presses, pressFrames, pressFid);
|
std::println(" press {}: {} frames -> MATCH fid={}", out.presses, pressFrames, pressFid);
|
||||||
}
|
}
|
||||||
if (g_verbose)
|
|
||||||
std::println(" frame {:3}: metric={:<4}{}{}", i + 1, c.metric,
|
|
||||||
finger ? " FINGER" : " ", note);
|
|
||||||
SendCommand(app_, ta::Cmd::QueryEventStatus, q);
|
SendCommand(app_, ta::Cmd::QueryEventStatus, q);
|
||||||
|
if (g_verbose) {
|
||||||
|
// The IRQ line alongside the metric: if it tracks the finger
|
||||||
|
// under an armed session, lift detection can become an edge
|
||||||
|
// wait instead of a poll.
|
||||||
|
auto irq = sensor_.ReadIrq();
|
||||||
|
std::println(" frame {:3} @{:5}ms: metric={:<4}{} irq={}{}", i + 1,
|
||||||
|
msSince(t0), c.metric, finger ? " FINGER" : " ",
|
||||||
|
irq ? std::to_string(*irq) : "?", note);
|
||||||
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs));
|
std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs));
|
||||||
}
|
}
|
||||||
fingerPresent_.store(false);
|
fingerPresent_.store(false);
|
||||||
|
std::println(" verify loop: {} frames in {} ms ({} ms/frame incl. {} ms gap)",
|
||||||
|
out.frames, msSince(t0),
|
||||||
|
out.frames ? msSince(t0) / out.frames : 0, g_frameGapMs);
|
||||||
if (cancel) {
|
if (cancel) {
|
||||||
SendCommand(app_, ta::Cmd::Cancel, {});
|
SendCommand(app_, ta::Cmd::Cancel, {});
|
||||||
out.cancelled = true;
|
out.cancelled = true;
|
||||||
|
|
@ -2006,6 +2027,7 @@ int main(int argc, char** argv) {
|
||||||
if (a == "--enrol") { doEnrol = true; probe = true; }
|
if (a == "--enrol") { doEnrol = true; probe = true; }
|
||||||
if (a == "--cal-save") { doCalSave = true; probe = true; g_verbose = true; }
|
if (a == "--cal-save") { doCalSave = true; probe = true; g_verbose = true; }
|
||||||
if (a.starts_with("--frames=")) frames = std::stoi(std::string(a.substr(9)));
|
if (a.starts_with("--frames=")) frames = std::stoi(std::string(a.substr(9)));
|
||||||
|
if (a.starts_with("--frame-gap=")) g_frameGapMs = std::stoi(std::string(a.substr(12)));
|
||||||
if (a.starts_with("--log-dir=")) g_logDir = a.substr(10);
|
if (a.starts_with("--log-dir=")) g_logDir = a.substr(10);
|
||||||
if (a.starts_with("--state-dir=")) g_stateDir = a.substr(12);
|
if (a.starts_with("--state-dir=")) g_stateDir = a.substr(12);
|
||||||
if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));
|
if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9)));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue