diff --git a/implementations/main.cpp b/implementations/main.cpp index 7409f20..2b71d38 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -57,6 +57,7 @@ bool g_auth = false; bool g_enrol = false; int g_frames = 40; int g_frameGapMs = 500; +int g_samples = 10; // common.max_enrolling_samples, as shipped std::string g_logDir = "/var/log/fingerprintd"; int g_rescan = -1; // -1 = leave the config's value alone @@ -1245,7 +1246,8 @@ int Probe() { // every held frame instead gives the algorithm near-duplicate images // from a single press. en::TouchTracker tracker; - en::EnrolSession enrol; + en::EnrolSession enrol(g_samples); + int heldFrames = 0; for (int i = 0; i < g_frames && !enrol.Complete(); i++) { std::vector q(0x10, std::byte{0}); SendCommand(app, ta::Cmd::QueryEventStatus, q); @@ -1255,6 +1257,13 @@ int Probe() { auto c = SendCommand(app, ta::Cmd::CaptureImage, cap); bool finger = baseline.IsFinger(c.metric); + // A sample is taken on the RISING edge only. Holding the finger + // down produces no further touch events however long it stays, so + // a run where the finger is never lifted collects exactly one + // sample -- which is what a first attempt at this did, 55 finger + // frames and three touches. + heldFrames = finger ? heldFrames + 1 : 0; + std::string note; for (ta::Event ev : tracker.Observe(finger, en::Mode::Enrol)) { std::vector evbuf(ta::EventContextSize); @@ -1273,8 +1282,15 @@ int Probe() { } SendCommand(app, ta::Cmd::QueryEventStatus, q); - std::println(" frame {:2}: metric={:<4}{}{}", i + 1, c.metric, - finger ? " FINGER" : " ", note); + std::println(" [{:2}/{}] {:<28} metric={:<4}{}{}", + enrol.Accepted(), enrol.Total(), + enrol.Started() + ? (finger ? "hold... then LIFT" : "LIFT -- now press again") + : "press your finger", + c.metric, finger ? " FINGER" : " ", note); + if (heldFrames == 4) + std::println(" *** still held -- LIFT the finger, a sample is only " + "taken when you press again ***"); std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs)); } @@ -1332,6 +1348,7 @@ int main(int argc, char** argv) { if (a.starts_with("--log-dir=")) g_logDir = a.substr(10); if (a.starts_with("--rescan=")) g_rescan = std::stoi(std::string(a.substr(9))); if (a.starts_with("--group-path=")) g_groupPath = a.substr(13); + if (a.starts_with("--samples=")) g_samples = std::stoi(std::string(a.substr(10))); if (a.starts_with("--sfs-root=")) g_sfsRoot = a.substr(11); if (a.starts_with("--gid=")) g_gid = static_cast( std::stoul(std::string(a.substr(6)))); diff --git a/interfaces/Fingerprintd-Engine.cppm b/interfaces/Fingerprintd-Engine.cppm index 46f5684..3750ff9 100644 --- a/interfaces/Fingerprintd-Engine.cppm +++ b/interfaces/Fingerprintd-Engine.cppm @@ -132,14 +132,22 @@ export namespace fingerprintd::engine { // the value. class EnrolSession { public: + // The total is common.max_enrolling_samples and is KNOWN from the + // config, not inferred. Inferring it from the first reading is off by + // one: `rem` is reported after the sample has been processed, so the + // first observation of a healthy enrolment is already 9, not 10, and a + // session that takes 9 as the total reports one fewer accepted sample + // than actually happened. + explicit EnrolSession(std::int32_t total) : total_(total) {} + void Observe(std::int32_t remaining, bool fromEnrolPath) { if (!fromEnrolPath) return; // a release reports nothing if (remaining < 0) return; // not populated at all + if (remaining > total_) return; // nonsense if (!started_) { // A first reading of 0 is an unpopulated field, not a finished - // enrolment: the count starts at max_enrolling_samples. + // enrolment: the count starts at the total. if (remaining == 0) return; - total_ = remaining; remaining_ = remaining; started_ = true; return; @@ -161,6 +169,8 @@ export namespace fingerprintd::engine { bool started_ = false; std::int32_t total_ = 0; std::int32_t remaining_ = -1; + // Set once the total is known; see the constructor. + }; // ---- Authentication accounting ---------------------------------------- diff --git a/tests/Engine/main.cpp b/tests/Engine/main.cpp index cb158d3..ae2c85f 100644 --- a/tests/Engine/main.cpp +++ b/tests/Engine/main.cpp @@ -138,16 +138,18 @@ int main() { // ---- Enrolment progress, read from the response { - EnrolSession e; + EnrolSession e(10); Check(!e.Started(), "not started"); e.Observe(-1, true); Check(!e.Started(), "an unpopulated field does not start the session"); - e.Observe(10, true); - Check(e.Started() && e.Total() == 10 && e.Stages() == 10, "first response sets the total"); - Check(e.Accepted() == 0 && !e.Complete(), "nothing accepted yet"); + Check(e.Total() == 10 && e.Stages() == 10, "the total comes from the config"); e.Observe(9, true); - Check(e.Accepted() == 1, "rem 10 -> 9 is one accepted sample"); + // rem is reported AFTER the sample is processed, so the first reading + // of a healthy enrolment is already 9 and one sample is in. + Check(e.Started() && e.Accepted() == 1, "a first reading of 9 means one accepted"); + Check(!e.Complete(), "not complete"); for (std::int32_t r : {8, 7, 6, 5, 4, 3, 2, 1}) e.Observe(r, true); + Check(e.Accepted() == 9, "nine accepted at one remaining"); Check(!e.Complete() && e.Remaining() == 1, "not complete at one remaining"); e.Observe(0, true); Check(e.Complete() && e.Accepted() == 10, "complete at zero"); @@ -160,33 +162,34 @@ int main() { // itself finished after ONE press. SAVE_DATA is then called on an // algorithm holding no template and answers -1. { - EnrolSession e; - e.Observe(10, true); // touch: ten to go + EnrolSession e(10); + e.Observe(9, true); // touch: one accepted, nine to go e.Observe(0, false); // release: reads zero, means nothing Check(!e.Complete(), "a release reading of 0 does NOT complete the enrolment"); - Check(e.Remaining() == 10, "and does not move the count"); - Check(e.Accepted() == 0, "nothing was accepted"); + Check(e.Remaining() == 9, "and does not move the count"); + Check(e.Accepted() == 1, "the touch's one sample still stands"); } // A first reading of 0 is an unpopulated field, not a finished enrolment. { - EnrolSession e; + EnrolSession e(10); e.Observe(0, true); Check(!e.Started(), "a leading zero does not start a session"); Check(!e.Complete(), "and certainly does not finish one"); - e.Observe(10, true); + e.Observe(9, true); Check(e.Started() && e.Total() == 10, "a real count still starts it"); } // The count only falls; a jump back up is noise, not progress. { - EnrolSession e; - e.Observe(10, true); + EnrolSession e(10); e.Observe(9, true); + e.Observe(8, true); e.Observe(7, true); Check(e.Remaining() == 7, "decreases are taken"); e.Observe(9, true); Check(e.Remaining() == 7, "an increase is ignored"); + Check(e.Accepted() == 3, "three accepted, counted against the real total"); } // ---- The three recorded runs, replayed in order