/* * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD * * SPDX-License-Identifier: MIT */ /* Simple example using M5UnitUnified for UnitFinger */ #include #include #include #include // ************************************************************* // Choose one define symbol to match the unit you are using // ************************************************************* #if !defined(USING_UNIT_FINGER) && !defined(USING_HAT_FINGER) && !defined(USING_FACES_FINGER) // For UnitFinger (SKU:U008) // #define USING_UNIT_FINGER // For HatFinger (SKU:U074) // #define USING_HAT_FINGER // For FacesFinger (SKU:A066) // #define USING_FACES_FINGER #endif // ************************************************************* using namespace m5::unit::fpc1xxx; namespace { auto& lcd = M5.Display; m5::unit::UnitUnified Units; #if defined(USING_UNIT_FINGER) m5::unit::UnitFinger unit; #elif defined(USING_HAT_FINGER) m5::unit::HatFinger unit; #elif defined(USING_FACES_FINGER) m5::unit::UnitFacesFinger unit; #else #error Please choose unit! #endif uint16_t target_user_id{}; #if defined(USING_HAT_FINGER) struct UartPins { int rx; // SCL pin of Hat connector int tx; // SDA pin of Hat connector }; 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}; } } #endif #if defined(USING_FACES_FINGER) // M-Bus pins for Faces Finger (GPIO varies by board) struct FacesPins { int rx; // UART RX (mbus_pin15) int tx; // UART TX (mbus_pin16) int panel_power; // Panel power (mbus_pin10) int touch_power; // Touch IC power (mbus_pin20) }; FacesPins get_faces_pins() { switch (M5.getBoard()) { case m5::board_t::board_M5Stack: // Core/Gray/Fire return {16, 17, 26, 5}; default: return {-1, -1, -1, -1}; } } #endif void print_all_users() { std::vector 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); } } } } // namespace void setup() { auto m5cfg = M5.config(); #if defined(USING_HAT_FINGER) m5cfg.pmic_button = false; // Disable BtnPWR m5cfg.internal_imu = false; // Disable internal IMU m5cfg.internal_rtc = false; // Disable internal RTC #endif M5.begin(m5cfg); M5.setTouchButtonHeightByRatio(100); // The screen shall be in landscape mode if (lcd.height() > lcd.width()) { lcd.setRotation(1); } #if defined(USING_HAT_FINGER) const auto pins = get_hat_uart_pins(M5.getBoard()); M5_LOGI("getHatPin: RX:%d TX:%d", pins.rx, pins.tx); if (pins.rx < 0 || pins.tx < 0) { M5_LOGE("No Hat port on this board"); lcd.fillScreen(TFT_RED); while (true) { m5::utility::delay(10000); } } auto pin_num_in = pins.rx; auto pin_num_out = pins.tx; #elif defined(USING_FACES_FINGER) const auto fp = get_faces_pins(); M5_LOGI("getFacesPin: RX:%d TX:%d PWR:%d TCH:%d", fp.rx, fp.tx, fp.panel_power, fp.touch_power); if (fp.rx < 0 || fp.tx < 0) { M5_LOGE("No M-Bus on this board"); lcd.fillScreen(TFT_RED); while (true) { m5::utility::delay(10000); } } auto pin_num_in = fp.rx; auto pin_num_out = fp.tx; { auto cfg = unit.config(); cfg.panel_power_pin = fp.panel_power; cfg.touch_power_pin = fp.touch_power; unit.config(cfg); } #else auto pin_num_in = M5.getPin(m5::pin_name_t::port_c_rxd); auto pin_num_out = M5.getPin(m5::pin_name_t::port_c_txd); if (pin_num_in < 0 || pin_num_out < 0) { M5_LOGW("PortC is not available"); // NanoC6: Ex_I2C.setPort() registers m5gfx::i2c on GROVE pins; // 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(); } 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); } #endif M5_LOGI("getPin: %d,%d", 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 s.end(); // When changing the chip's baud rate, make sure to synchronize it // s.begin(9600, SERIAL_8N1, pin_num_in, pin_num_out); s.begin(19200, SERIAL_8N1, pin_num_in, pin_num_out); // as default // s.begin(38400, SERIAL_8N1, pin_num_in, pin_num_out); // s.begin(57600, SERIAL_8N1, pin_num_in, pin_num_out); // s.begin(115200, SERIAL_8N1, pin_num_in, pin_num_out); if (!Units.add(unit, s) || !Units.begin()) { M5_LOGE("Failed to begin"); lcd.fillScreen(TFT_RED); while (true) { m5::utility::delay(10000); } } M5_LOGI("M5UnitUnified initialized"); M5_LOGI("%s", Units.debugInfo().c_str()); { uint8_t clv{}, timeout{}; Mode mode{}; uint16_t user_count{}; unit.readRegistrationMode(mode); unit.readComparisonLevel(clv); unit.readTimeout(timeout); unit.readRegisteredUserCount(user_count); M5.Log.printf("=== %s information ===\n", unit.deviceName()); M5.Log.printf(" Mode: %s\n", mode == Mode::ProhibitDuplicate ? "Prohibit duplicate" : "Allow duplicate"); M5.Log.printf(" Comparison Lv: %u\n", clv); M5.Log.printf(" Timeout: %u\n", timeout); M5.Log.printf("Registered user: %u\n", user_count); print_all_users(); if (unit.findAvailableUserID(target_user_id)) { M5.Log.printf("Lowest unregistered UserID: %u\n", target_user_id); } uint8_t characteristic[193]{}; if (target_user_id > 1 && unit.readUserCharacteristic(characteristic, target_user_id - 1)) { M5.Log.printf("--- UserID:%u characteristic\n", target_user_id - 1); m5::utility::log::dump(characteristic, m5::stl::size(characteristic), false); } } lcd.fillScreen(TFT_DARKGREEN); } void loop() { M5.update(); Units.update(); // Register finger if (M5.BtnA.wasHold()) { lcd.fillScreen(TFT_DARKGREEN); M5.Speaker.tone(2500, 20); lcd.drawString("Try register", 0, 0); M5.Log.printf("Try register\n"); if (unit.registerFinger(target_user_id, 1)) { M5.Speaker.tone(2000, 20); lcd.setCursor(0, 0); lcd.printf("Registered %u", target_user_id); M5.Log.printf("Registered user %u\n", target_user_id); ++target_user_id; } else { M5.Log.printf("Failed to register %u", target_user_id); lcd.drawString("Try again ", 0, 0); } return; } // Identify and verify finger if (M5.BtnA.wasClicked()) { uint16_t id{}; uint8_t per{}; M5.Speaker.tone(1500, 20); lcd.drawString("Try identify", 0, 0); M5.Log.printf("Try identify\n"); if (unit.identifyFinger(id, per)) { if (id) { M5.Speaker.tone(2000, 20); M5.Log.printf("Identified user:%u, permission:%u\n", id, per); // If it was identified, it should pass verification lcd.drawString("Try verify ", 0, 0); M5.Log.printf("Try verify\n"); bool match{}; if (unit.verifyFinger(match, id) && match) { M5.Speaker.tone(2500, 20); M5.Log.printf("Verified: %u\n", id); lcd.drawString("Verified ", 0, 0); } else { M5.Log.printf("Failed or not match %u\n", id); lcd.drawString("Failed to Verify ", 0, 0); } } else { M5.Speaker.tone(800, 40); M5.Log.printf("No user\n"); lcd.drawString("No user ", 0, 0); } } else { M5.Speaker.tone(800, 40); M5.Log.printf("No user\n"); lcd.drawString("No user ", 0, 0); } } }