2025-08-22 13:56:09 +09:00
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
/*
|
|
|
|
|
UnitTest for FPC1020A
|
|
|
|
|
*/
|
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
#include <Wire.h>
|
|
|
|
|
#include <M5Unified.h>
|
|
|
|
|
#include <M5UnitUnified.hpp>
|
|
|
|
|
#include <googletest/test_template.hpp>
|
|
|
|
|
#include <googletest/test_helper.hpp>
|
|
|
|
|
#include <unit/unit_FPC1xxx.hpp>
|
2026-04-08 21:00:10 +09:00
|
|
|
#include <unit/unit_FacesFinger.hpp>
|
2025-08-22 13:56:09 +09:00
|
|
|
#include <chrono>
|
|
|
|
|
#include <thread>
|
|
|
|
|
#include <iostream>
|
2026-04-07 23:00:25 +09:00
|
|
|
#include <esp_random.h>
|
2025-08-22 13:56:09 +09:00
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
using namespace m5::unit::googletest;
|
|
|
|
|
using namespace m5::unit;
|
|
|
|
|
using namespace m5::unit::fpc1xxx;
|
|
|
|
|
using namespace m5::unit::fpc1xxx::command;
|
|
|
|
|
using m5::unit::types::elapsed_time_t;
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
// Hat port UART pins (RX=SCL, TX=SDA of Hat connector)
|
|
|
|
|
#if defined(USING_HAT_FINGER)
|
|
|
|
|
namespace hat {
|
|
|
|
|
struct UartPins {
|
|
|
|
|
int rx;
|
|
|
|
|
int tx;
|
|
|
|
|
};
|
2025-08-22 13:56:09 +09:00
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
UartPins get_hat_uart_pins(const m5::board_t board)
|
|
|
|
|
{
|
|
|
|
|
switch (board) {
|
|
|
|
|
case m5::board_t::board_M5StickC:
|
|
|
|
|
case m5::board_t::board_M5StickCPlus:
|
|
|
|
|
case m5::board_t::board_M5StickCPlus2:
|
|
|
|
|
return {26, 0};
|
|
|
|
|
case m5::board_t::board_M5StickS3:
|
|
|
|
|
return {0, 8};
|
|
|
|
|
case m5::board_t::board_M5StackCoreInk:
|
|
|
|
|
return {26, 25};
|
|
|
|
|
default:
|
|
|
|
|
return {-1, -1};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // namespace hat
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-04-08 21:00:10 +09:00
|
|
|
// Faces Finger Module pins (M-Bus: RX=mbus_pin15, TX=mbus_pin16, PWR=mbus_pin10, TCH=mbus_pin20)
|
|
|
|
|
#if defined(USING_FACES_FINGER)
|
|
|
|
|
namespace faces {
|
|
|
|
|
struct FacesPins {
|
|
|
|
|
int rx;
|
|
|
|
|
int tx;
|
|
|
|
|
int panel_power;
|
|
|
|
|
int touch_power;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FacesPins get_faces_pins()
|
|
|
|
|
{
|
|
|
|
|
return {
|
|
|
|
|
M5.getPin(m5::pin_name_t::mbus_pin15),
|
|
|
|
|
M5.getPin(m5::pin_name_t::mbus_pin16),
|
|
|
|
|
M5.getPin(m5::pin_name_t::mbus_pin10),
|
|
|
|
|
M5.getPin(m5::pin_name_t::mbus_pin20),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
} // namespace faces
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(USING_FACES_FINGER)
|
|
|
|
|
class TestFPC1020A : public UARTComponentTestBase<UnitFacesFinger> {
|
|
|
|
|
protected:
|
|
|
|
|
virtual UnitFacesFinger* get_instance() override
|
|
|
|
|
{
|
2026-04-15 19:54:15 +09:00
|
|
|
auto ptr = new m5::unit::UnitFacesFinger();
|
|
|
|
|
const auto fp = faces::get_faces_pins();
|
2026-04-08 21:00:10 +09:00
|
|
|
auto cfg = ptr->config();
|
|
|
|
|
cfg.panel_power_pin = fp.panel_power;
|
|
|
|
|
cfg.touch_power_pin = fp.touch_power;
|
|
|
|
|
ptr->config(cfg);
|
|
|
|
|
return ptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void get_serial_pins(int& pin_num_in, int& pin_num_out)
|
|
|
|
|
{
|
|
|
|
|
const auto fp = faces::get_faces_pins();
|
|
|
|
|
pin_num_in = fp.rx;
|
|
|
|
|
pin_num_out = fp.tx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual HardwareSerial* init_serial() override
|
|
|
|
|
{
|
|
|
|
|
int pin_num_in{-1}, pin_num_out{-1};
|
|
|
|
|
get_serial_pins(pin_num_in, pin_num_out);
|
|
|
|
|
|
|
|
|
|
// clang-format off
|
|
|
|
|
#if defined(CONFIG_IDF_TARGET_ESP32C6)
|
|
|
|
|
auto& s = Serial1;
|
|
|
|
|
#elif SOC_UART_NUM > 2
|
|
|
|
|
auto& s = Serial2;
|
|
|
|
|
#elif SOC_UART_NUM > 1
|
|
|
|
|
auto& s = Serial1;
|
|
|
|
|
#else
|
|
|
|
|
#error "Not enough Serial"
|
|
|
|
|
#endif
|
|
|
|
|
// clang-format on
|
|
|
|
|
|
|
|
|
|
M5_LOGI("getPin: %d,%d", pin_num_in, pin_num_out);
|
|
|
|
|
s.end();
|
|
|
|
|
s.begin(19200, SERIAL_8N1, pin_num_in, pin_num_out);
|
|
|
|
|
return &s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void reset_serial(const uint32_t baud = 19200)
|
|
|
|
|
{
|
|
|
|
|
int pin_num_in{-1}, pin_num_out{-1};
|
|
|
|
|
get_serial_pins(pin_num_in, pin_num_out);
|
|
|
|
|
serial->end();
|
|
|
|
|
m5::utility::delay(100);
|
|
|
|
|
serial->begin(baud, SERIAL_8N1, pin_num_in, pin_num_out);
|
|
|
|
|
while (serial->available()) {
|
|
|
|
|
serial->read();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
#else
|
2026-04-07 23:00:25 +09:00
|
|
|
class TestFPC1020A : public UARTComponentTestBase<UnitFPC1020A> {
|
2025-08-22 13:56:09 +09:00
|
|
|
protected:
|
|
|
|
|
virtual UnitFPC1020A* get_instance() override
|
|
|
|
|
{
|
|
|
|
|
auto ptr = new m5::unit::UnitFPC1020A();
|
|
|
|
|
return ptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
void get_serial_pins(int& pin_num_in, int& pin_num_out)
|
2025-08-22 13:56:09 +09:00
|
|
|
{
|
2026-04-07 23:00:25 +09:00
|
|
|
#if defined(USING_HAT_FINGER)
|
|
|
|
|
auto board = M5.getBoard();
|
|
|
|
|
const auto pins = hat::get_hat_uart_pins(board);
|
|
|
|
|
pin_num_in = pins.rx;
|
|
|
|
|
pin_num_out = pins.tx;
|
|
|
|
|
#else
|
|
|
|
|
pin_num_in = M5.getPin(m5::pin_name_t::port_c_rxd);
|
|
|
|
|
pin_num_out = M5.getPin(m5::pin_name_t::port_c_txd);
|
2025-08-22 13:56:09 +09:00
|
|
|
if (pin_num_in < 0 || pin_num_out < 0) {
|
2026-04-07 23:00:25 +09:00
|
|
|
// NanoC6: Ex_I2C.setPort() registers m5gfx::i2c on GPIO 1/2;
|
|
|
|
|
// Wire.end() alone won't release it, causing dual-driver conflict on uart_driver_install
|
|
|
|
|
if (M5.getBoard() == m5::board_t::board_M5NanoC6) {
|
|
|
|
|
M5.Ex_I2C.release();
|
|
|
|
|
}
|
2025-08-22 13:56:09 +09:00
|
|
|
Wire.end();
|
|
|
|
|
pin_num_in = M5.getPin(m5::pin_name_t::port_a_pin1);
|
|
|
|
|
pin_num_out = M5.getPin(m5::pin_name_t::port_a_pin2);
|
|
|
|
|
}
|
2026-04-07 23:00:25 +09:00
|
|
|
#endif
|
|
|
|
|
}
|
2025-08-22 13:56:09 +09:00
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
virtual HardwareSerial* init_serial() override
|
|
|
|
|
{
|
|
|
|
|
int pin_num_in{-1}, pin_num_out{-1};
|
|
|
|
|
get_serial_pins(pin_num_in, pin_num_out);
|
|
|
|
|
|
|
|
|
|
// clang-format off
|
|
|
|
|
#if defined(CONFIG_IDF_TARGET_ESP32C6)
|
|
|
|
|
auto& s = Serial1;
|
|
|
|
|
#elif SOC_UART_NUM > 2
|
|
|
|
|
auto& s = Serial2;
|
2025-08-22 13:56:09 +09:00
|
|
|
#elif SOC_UART_NUM > 1
|
2026-04-07 23:00:25 +09:00
|
|
|
auto& s = Serial1;
|
2025-08-22 13:56:09 +09:00
|
|
|
#else
|
|
|
|
|
#error "Not enough Serial"
|
|
|
|
|
#endif
|
2026-04-07 23:00:25 +09:00
|
|
|
// clang-format on
|
2025-08-22 13:56:09 +09:00
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
M5_LOGI("getPin: %d,%d", pin_num_in, pin_num_out);
|
2025-08-22 13:56:09 +09:00
|
|
|
s.end();
|
|
|
|
|
s.begin(19200, SERIAL_8N1, pin_num_in, pin_num_out);
|
|
|
|
|
return &s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void reset_serial(const uint32_t baud = 19200)
|
|
|
|
|
{
|
2026-04-07 23:00:25 +09:00
|
|
|
int pin_num_in{-1}, pin_num_out{-1};
|
|
|
|
|
get_serial_pins(pin_num_in, pin_num_out);
|
2025-08-22 13:56:09 +09:00
|
|
|
serial->end();
|
2026-04-07 23:00:25 +09:00
|
|
|
m5::utility::delay(100);
|
2025-08-22 13:56:09 +09:00
|
|
|
serial->begin(baud, SERIAL_8N1, pin_num_in, pin_num_out);
|
|
|
|
|
while (serial->available()) {
|
|
|
|
|
serial->read();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-08 21:00:10 +09:00
|
|
|
#endif // USING_FACES_FINGER
|
2025-08-22 13:56:09 +09:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
void print_all_users(UnitFPC1020A* unit)
|
|
|
|
|
{
|
|
|
|
|
std::vector<User> v{};
|
|
|
|
|
if (unit->readAllUser(v)) {
|
|
|
|
|
M5.Log.printf("All user data (%u):\n", v.size());
|
|
|
|
|
uint16_t idx{};
|
|
|
|
|
for (auto&& u : v) {
|
|
|
|
|
M5.Log.printf(" [%3d] ID:%5u, PERMISSION:%u\n", idx++, u.id, u.permission);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr BaudRate br_table[5] = {
|
|
|
|
|
BaudRate::Baud9600, BaudRate::Baud19200, BaudRate::Baud38400, BaudRate::Baud57600, BaudRate::Baud115200,
|
|
|
|
|
};
|
|
|
|
|
constexpr uint32_t brv_table[5] = {9600, 19200, 38400, 57600, 115200};
|
|
|
|
|
|
|
|
|
|
constexpr uint8_t ch_data[193] = {
|
|
|
|
|
0x7C, 0x8C, 0x37, 0xDF, 0xC1, 0xAD, 0xA5, 0xD1, 0x33, 0xD1, 0x3A, 0xBE, 0x03, 0xF0, 0x21, 0xE9, 0xB1, 0xB7,
|
|
|
|
|
0x8C, 0xCB, 0xD8, 0x2F, 0x7F, 0xF2, 0xB3, 0x8C, 0x6D, 0x48, 0xD0, 0x1E, 0x48, 0x1B, 0x2D, 0x4F, 0xAF, 0x71,
|
|
|
|
|
0x71, 0x80, 0x5F, 0xD7, 0xF2, 0xD3, 0x9E, 0xF4, 0xC4, 0xF1, 0x9B, 0x94, 0x96, 0xE8, 0x1D, 0xAB, 0x81, 0x93,
|
|
|
|
|
0xB3, 0x73, 0x7E, 0x1B, 0x27, 0xD9, 0xC4, 0x39, 0x57, 0x16, 0x64, 0x41, 0xB9, 0x35, 0x15, 0xE8, 0xF0, 0x3C,
|
|
|
|
|
0x95, 0xD8, 0xE8, 0xCE, 0x1E, 0x18, 0x64, 0xFA, 0xAD, 0x68, 0xDD, 0xFC, 0x59, 0x32, 0x13, 0x01, 0x09, 0x39,
|
|
|
|
|
0x0B, 0x0F, 0x1F, 0xE5, 0xCA, 0x71, 0x68, 0x05, 0xF8, 0x36, 0x2E, 0x98, 0xDC, 0xCA, 0xAD, 0xC8, 0x6A, 0xDB,
|
|
|
|
|
0xED, 0x25, 0x80, 0x1A, 0x9A, 0x9D, 0xCF, 0xA6, 0x26, 0x43, 0x19, 0xDD, 0xAF, 0xE8, 0x3A, 0x89, 0xC5, 0x1F,
|
|
|
|
|
0x3C, 0x6D, 0x19, 0x9D, 0x38, 0xDE, 0x10, 0xE6, 0x60, 0xC3, 0x7B, 0xE8, 0x72, 0xC3, 0xF2, 0xB3, 0x16, 0x60,
|
|
|
|
|
0xDE, 0x8B, 0xC9, 0x59, 0x02, 0xB9, 0x10, 0x32, 0x62, 0xCD, 0xB9, 0x41, 0xF7, 0x73, 0x76, 0xF5, 0xD3, 0xDB,
|
|
|
|
|
0xB7, 0xA3, 0xD5, 0xA3, 0x87, 0x79, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x1A,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
TEST_F(TestFPC1020A, Baud)
|
2025-08-22 13:56:09 +09:00
|
|
|
{
|
|
|
|
|
uint32_t idx{};
|
|
|
|
|
for (auto&& br : br_table) {
|
|
|
|
|
M5_LOGI("%u/%u", br, brv_table[idx]);
|
|
|
|
|
EXPECT_TRUE(unit->writeBaudRate(br));
|
|
|
|
|
reset_serial(brv_table[idx]);
|
|
|
|
|
|
|
|
|
|
uint32_t sno{0xdeadbeef};
|
|
|
|
|
EXPECT_TRUE(unit->readSerialNumber(sno));
|
|
|
|
|
EXPECT_NE(sno, 0xdeadbeef);
|
|
|
|
|
|
|
|
|
|
++idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->writeBaudRate(BaudRate::Baud19200));
|
|
|
|
|
reset_serial(19200);
|
|
|
|
|
uint32_t sno{0xdeadbeef};
|
|
|
|
|
EXPECT_TRUE(unit->readSerialNumber(sno));
|
|
|
|
|
EXPECT_NE(sno, 0xdeadbeef);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
TEST_F(TestFPC1020A, Basic)
|
2025-08-22 13:56:09 +09:00
|
|
|
{
|
|
|
|
|
SCOPED_TRACE(ustr);
|
|
|
|
|
|
|
|
|
|
uint32_t sno{0xdeadbeef};
|
|
|
|
|
char ver[9]{};
|
|
|
|
|
EXPECT_TRUE(unit->readSerialNumber(sno));
|
|
|
|
|
EXPECT_NE(sno, 0xdeadbeef);
|
|
|
|
|
EXPECT_TRUE(unit->readVersion(ver));
|
|
|
|
|
EXPECT_EQ(strlen(ver), 8);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
TEST_F(TestFPC1020A, Settings)
|
2025-08-22 13:56:09 +09:00
|
|
|
{
|
|
|
|
|
SCOPED_TRACE(ustr);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
uint16_t users{};
|
|
|
|
|
EXPECT_TRUE(unit->deleteAllUsers());
|
|
|
|
|
EXPECT_TRUE(unit->readRegisteredUserCount(users));
|
|
|
|
|
EXPECT_EQ(users, 0);
|
|
|
|
|
|
|
|
|
|
Mode m{};
|
|
|
|
|
EXPECT_TRUE(unit->readRegistrationMode(m));
|
|
|
|
|
|
|
|
|
|
// Allow
|
|
|
|
|
EXPECT_TRUE(unit->writeRegistrationMode(Mode::AllowDuplicate));
|
|
|
|
|
EXPECT_TRUE(unit->readRegistrationMode(m));
|
|
|
|
|
EXPECT_EQ(m, Mode::AllowDuplicate);
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
uint8_t perm = esp_random() % 3 + 1;
|
2025-08-22 13:56:09 +09:00
|
|
|
std::array<uint8_t, 193> characteristic{};
|
|
|
|
|
std::generate(characteristic.begin(), characteristic.end(),
|
2026-04-07 23:00:25 +09:00
|
|
|
[]() { return static_cast<uint8_t>(esp_random() & 0xFF); });
|
2025-08-22 13:56:09 +09:00
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->registerCharacteristic(unit->maximumUserID(), perm, characteristic.data()));
|
|
|
|
|
EXPECT_TRUE(unit->registerCharacteristic(unit->maximumUserID() - 1, perm, characteristic.data()));
|
|
|
|
|
|
2025-09-16 19:23:42 +09:00
|
|
|
// Prohibit (Applies to fingerprint registration only)
|
|
|
|
|
EXPECT_TRUE(unit->writeRegistrationMode(Mode::ProhibitDuplicate));
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_TRUE(unit->readRegistrationMode(m));
|
2025-09-16 19:23:42 +09:00
|
|
|
EXPECT_EQ(m, Mode::ProhibitDuplicate);
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_TRUE(unit->registerCharacteristic(unit->maximumUserID() - 2, perm, characteristic.data()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
for (uint8_t lv = 0; lv < 10; ++lv) {
|
|
|
|
|
EXPECT_TRUE(unit->writeComparisonLevel(lv));
|
|
|
|
|
uint8_t lv2{};
|
|
|
|
|
EXPECT_TRUE(unit->readComparisonLevel(lv2));
|
|
|
|
|
EXPECT_EQ(lv2, lv);
|
|
|
|
|
}
|
|
|
|
|
EXPECT_FALSE(unit->writeComparisonLevel(10));
|
|
|
|
|
EXPECT_FALSE(unit->writeComparisonLevel(100));
|
|
|
|
|
EXPECT_FALSE(unit->writeComparisonLevel(255));
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->writeComparisonLevel(5));
|
|
|
|
|
uint8_t lv2{};
|
|
|
|
|
EXPECT_TRUE(unit->readComparisonLevel(lv2));
|
|
|
|
|
EXPECT_EQ(lv2, 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
uint32_t count{8};
|
|
|
|
|
while (count--) {
|
2026-04-07 23:00:25 +09:00
|
|
|
uint8_t to = esp_random() & 0xFF;
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_TRUE(unit->writeTimeout(to));
|
|
|
|
|
uint8_t to2{};
|
|
|
|
|
EXPECT_TRUE(unit->readTimeout(to2));
|
|
|
|
|
EXPECT_EQ(to2, to);
|
|
|
|
|
}
|
|
|
|
|
EXPECT_TRUE(unit->writeTimeout(0));
|
|
|
|
|
uint8_t to2{};
|
|
|
|
|
EXPECT_TRUE(unit->readTimeout(to2));
|
|
|
|
|
EXPECT_EQ(to2, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
TEST_F(TestFPC1020A, User)
|
2025-08-22 13:56:09 +09:00
|
|
|
{
|
|
|
|
|
uint16_t users{};
|
|
|
|
|
EXPECT_TRUE(unit->deleteAllUsers());
|
|
|
|
|
EXPECT_TRUE(unit->readRegisteredUserCount(users));
|
|
|
|
|
EXPECT_EQ(users, 0);
|
|
|
|
|
{
|
|
|
|
|
uint16_t id{};
|
2025-09-16 19:23:42 +09:00
|
|
|
EXPECT_TRUE(unit->findAvailableUserID(id));
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_EQ(id, 1);
|
|
|
|
|
|
2025-09-16 19:23:42 +09:00
|
|
|
EXPECT_TRUE(unit->findAvailableUserID(id, 1, 2));
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_EQ(id, 1);
|
|
|
|
|
|
2025-09-16 19:23:42 +09:00
|
|
|
EXPECT_TRUE(unit->findAvailableUserID(id, 100, 110));
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_EQ(id, 100);
|
|
|
|
|
|
2025-09-16 19:23:42 +09:00
|
|
|
EXPECT_TRUE(unit->findAvailableUserID(id, unit->maximumUserID(), unit->maximumUserID()));
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_EQ(id, unit->maximumUserID());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make random users
|
|
|
|
|
// for (uint_fast8_t i = unit->minimumUserID(); i <= unit->maximumUserID(); ++i) {
|
|
|
|
|
for (uint_fast8_t i = 1; i <= 10; ++i) {
|
2026-04-07 23:00:25 +09:00
|
|
|
uint8_t perm = esp_random() % 3 + 1;
|
2025-08-22 13:56:09 +09:00
|
|
|
std::array<uint8_t, 193> characteristic{};
|
|
|
|
|
std::generate(characteristic.begin(), characteristic.end(),
|
2026-04-07 23:00:25 +09:00
|
|
|
[]() { return static_cast<uint8_t>(esp_random() & 0xFF); });
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_TRUE(unit->registerCharacteristic(i, perm, characteristic.data()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
uint16_t id{};
|
2025-09-16 19:23:42 +09:00
|
|
|
EXPECT_TRUE(unit->findAvailableUserID(id));
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_EQ(id, 11);
|
|
|
|
|
|
2025-09-16 19:23:42 +09:00
|
|
|
EXPECT_FALSE(unit->findAvailableUserID(id, 1, 10));
|
2025-08-22 13:56:09 +09:00
|
|
|
EXPECT_EQ(id, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->readRegisteredUserCount(users));
|
|
|
|
|
EXPECT_NE(users, 0);
|
|
|
|
|
|
|
|
|
|
std::vector<User> uv;
|
|
|
|
|
EXPECT_TRUE(unit->readAllUser(uv));
|
|
|
|
|
EXPECT_EQ(uv.size(), users);
|
|
|
|
|
|
|
|
|
|
for (uint16_t id = 1; id <= users / 2; ++id) {
|
|
|
|
|
uint8_t perm{};
|
|
|
|
|
uint8_t characteristic[193]{};
|
|
|
|
|
EXPECT_TRUE(unit->readUser(perm, id)) << id;
|
|
|
|
|
EXPECT_TRUE(unit->readUserCharacteristic(characteristic, id)) << id;
|
|
|
|
|
EXPECT_GE(perm, 1) << id;
|
|
|
|
|
EXPECT_LE(perm, 3) << id;
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->deleteUser(id));
|
|
|
|
|
EXPECT_FALSE(unit->readUser(perm, id)) << id;
|
|
|
|
|
EXPECT_FALSE(unit->readUserCharacteristic(characteristic, id)) << id;
|
|
|
|
|
EXPECT_FALSE(unit->deleteUser(id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint16_t users2{};
|
|
|
|
|
EXPECT_TRUE(unit->readRegisteredUserCount(users2));
|
|
|
|
|
EXPECT_EQ(users2, users - users / 2);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->deleteAllUsers());
|
|
|
|
|
EXPECT_TRUE(unit->readRegisteredUserCount(users2));
|
|
|
|
|
EXPECT_EQ(users2, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
TEST_F(TestFPC1020A, Finger)
|
2025-08-22 13:56:09 +09:00
|
|
|
{
|
|
|
|
|
SCOPED_TRACE(ustr);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->deleteAllUsers());
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(unit->registerFinger(1, 1));
|
|
|
|
|
|
|
|
|
|
bool match{};
|
|
|
|
|
EXPECT_TRUE(unit->verifyFinger(match, 1));
|
|
|
|
|
EXPECT_FALSE(match);
|
|
|
|
|
|
|
|
|
|
uint16_t id{};
|
|
|
|
|
uint8_t perm{};
|
|
|
|
|
EXPECT_FALSE(unit->identifyFinger(id, perm));
|
|
|
|
|
EXPECT_EQ(id, 0);
|
|
|
|
|
EXPECT_EQ(perm, 0);
|
|
|
|
|
|
|
|
|
|
uint8_t characteristic[193]{};
|
|
|
|
|
EXPECT_FALSE(unit->scanCharacteristic(characteristic));
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> img{};
|
|
|
|
|
EXPECT_FALSE(unit->captureImage(img));
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->registerCharacteristic(1, 2, ch_data));
|
|
|
|
|
EXPECT_TRUE(unit->verifyCharacteristic(match, 1, ch_data));
|
|
|
|
|
EXPECT_TRUE(match);
|
|
|
|
|
EXPECT_TRUE(unit->verifyCharacteristic(match, 2, ch_data));
|
|
|
|
|
EXPECT_FALSE(match);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(unit->identifyCharacteristic(id, ch_data));
|
|
|
|
|
EXPECT_EQ(id, 1);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(unit->compareCharacteristic(match, ch_data));
|
|
|
|
|
EXPECT_FALSE(match);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 23:00:25 +09:00
|
|
|
TEST_F(TestFPC1020A, Sleep)
|
2025-08-22 13:56:09 +09:00
|
|
|
{
|
|
|
|
|
SCOPED_TRACE(ustr);
|
|
|
|
|
|
|
|
|
|
// Deep sleep
|
|
|
|
|
uint32_t sno{0xdeadbeef};
|
|
|
|
|
char ver[9]{};
|
|
|
|
|
EXPECT_TRUE(unit->sleep());
|
|
|
|
|
EXPECT_FALSE(unit->readSerialNumber(sno));
|
|
|
|
|
EXPECT_FALSE(unit->readVersion(ver));
|
|
|
|
|
}
|
2026-04-07 23:00:25 +09:00
|
|
|
|
|
|
|
|
// --- detail functions (no hardware required) ---
|
|
|
|
|
class TestFPC1020A_Detail : public ::testing::Test {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using Frame = m5::unit::UnitFPC1XXX::Frame;
|
|
|
|
|
using namespace m5::unit::fpc1xxx::detail;
|
|
|
|
|
|
|
|
|
|
TEST_F(TestFPC1020A_Detail, XorSum)
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
uint8_t data[] = {0};
|
|
|
|
|
EXPECT_EQ(xorSum(data, 0), 0);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
uint8_t data[] = {0xAB};
|
|
|
|
|
EXPECT_EQ(xorSum(data, 1), 0xAB);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
|
|
|
|
|
EXPECT_EQ(xorSum(data, 5), 0x01);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
uint8_t data[] = {0xFF, 0xFF, 0xFF, 0xFF};
|
|
|
|
|
EXPECT_EQ(xorSum(data, 4), 0x00);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(TestFPC1020A_Detail, IsValidSum)
|
|
|
|
|
{
|
|
|
|
|
// Valid frame
|
|
|
|
|
{
|
|
|
|
|
Frame f{};
|
|
|
|
|
f[0] = MARKER;
|
|
|
|
|
f[1] = 0x01;
|
|
|
|
|
f[2] = 0x02;
|
|
|
|
|
f[3] = 0x03;
|
|
|
|
|
f[4] = 0x04;
|
|
|
|
|
f[5] = 0x00;
|
|
|
|
|
f[6] = xorSum(f.data() + 1, 5);
|
|
|
|
|
f[7] = MARKER;
|
|
|
|
|
EXPECT_TRUE(is_valid_sum(f, true));
|
|
|
|
|
EXPECT_TRUE(is_valid_sum(f, false));
|
|
|
|
|
}
|
|
|
|
|
// Bad checksum — must return false (was the original bug)
|
|
|
|
|
{
|
|
|
|
|
Frame f{};
|
|
|
|
|
f[0] = MARKER;
|
|
|
|
|
f[1] = 0x01;
|
|
|
|
|
f[2] = 0x02;
|
|
|
|
|
f[3] = 0x03;
|
|
|
|
|
f[4] = 0x04;
|
|
|
|
|
f[5] = 0x00;
|
|
|
|
|
f[6] = xorSum(f.data() + 1, 5) ^ 0xFF;
|
|
|
|
|
f[7] = MARKER;
|
|
|
|
|
EXPECT_FALSE(is_valid_sum(f, true));
|
|
|
|
|
EXPECT_FALSE(is_valid_sum(f, false));
|
|
|
|
|
}
|
|
|
|
|
// Valid checksum, bad marker
|
|
|
|
|
{
|
|
|
|
|
Frame f{};
|
|
|
|
|
f[0] = 0x00;
|
|
|
|
|
f[1] = 0x01;
|
|
|
|
|
f[2] = 0x02;
|
|
|
|
|
f[3] = 0x03;
|
|
|
|
|
f[4] = 0x04;
|
|
|
|
|
f[5] = 0x00;
|
|
|
|
|
f[6] = xorSum(f.data() + 1, 5);
|
|
|
|
|
f[7] = MARKER;
|
|
|
|
|
EXPECT_FALSE(is_valid_sum(f, true));
|
|
|
|
|
EXPECT_TRUE(is_valid_sum(f, false));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(TestFPC1020A_Detail, IsValidPayload)
|
|
|
|
|
{
|
|
|
|
|
// Valid: MARKER DATA CHK MARKER
|
|
|
|
|
{
|
|
|
|
|
uint8_t d[] = {MARKER, 0x11, 0x22, 0x00, MARKER};
|
|
|
|
|
d[3] = xorSum(d + 1, 2);
|
|
|
|
|
EXPECT_TRUE(is_valid_payload(d, sizeof(d)));
|
|
|
|
|
}
|
|
|
|
|
// Bad checksum
|
|
|
|
|
{
|
|
|
|
|
uint8_t d[] = {MARKER, 0x11, 0x22, 0xFF, MARKER};
|
|
|
|
|
EXPECT_FALSE(is_valid_payload(d, sizeof(d)));
|
|
|
|
|
}
|
|
|
|
|
// Bad head marker
|
|
|
|
|
{
|
|
|
|
|
uint8_t d[] = {0x00, 0x11, 0x00, MARKER};
|
|
|
|
|
d[2] = xorSum(d + 1, 1);
|
|
|
|
|
EXPECT_FALSE(is_valid_payload(d, sizeof(d)));
|
|
|
|
|
}
|
|
|
|
|
// Too short
|
|
|
|
|
{
|
|
|
|
|
uint8_t d[] = {MARKER, 0x11, MARKER};
|
|
|
|
|
EXPECT_FALSE(is_valid_payload(d, sizeof(d)));
|
|
|
|
|
}
|
|
|
|
|
// Null
|
|
|
|
|
EXPECT_FALSE(is_valid_payload(nullptr, 10));
|
|
|
|
|
}
|