Repository: trezor/trezor-firmware
Pull request #4983: allow infinite testing of NFC functionalities
Author: login: TychoVrahe
Created: 2025-04-30T13:08:41Z | Merged: 2025-05-01T17:53:00Z

For usage during testing, certifications it is needed to be able to have NFC turned on continuously 


Diff:
diff --git a/core/embed/projects/prodtest/README.md b/core/embed/projects/prodtest/README.md
index cde2bee6a97..71a9514056a 100644
--- a/core/embed/projects/prodtest/README.md
+++ b/core/embed/projects/prodtest/README.md
@@ -16,9 +16,10 @@ To exit from interactive mode type `.+ENTER`.
 
 ### Commands
 These commands begin with the command name and may optionally include parameters separated by spaces.
+Parameters are marked with angle brackets (`<...>`), square brackets (`[<...>]`) indicate optional parameters.
 
 Command Format:
-`command <arg1> <arg2> ...`
+`command <arg1> <arg2> [<optional_arg3>]...`
 
 Example:
 ```
@@ -940,10 +941,13 @@ wpc-update
 ### nfc-read-card
 Activate the NFC in reader mode for a given time. Read general information from firstly discovered NFC tag or exits on timeout.
 
+When used without a timeout, the command will wait indefinitely for a card to be placed on the reader and will repeat the read operation
+each 100ms.
+
 Example:
 ```
-nfc-read-card <timeout_seconds>
-# NFC activated in reader mode for <timeout_seconds> seconds.
+nfc-read-card [<timeout_ms>]
+# NFC activated in reader mode for <timeout_ms> ms.
 # NFC card detected.
 # NFC Type A: UID: %s
 OK
@@ -951,12 +955,12 @@ OK
 
 
 ### nfc-emulate-card
-Activate NFC in Card Emulator mode for given time.
+Activate NFC in Card Emulator mode for given time, or infinite time if no timeout is specified.
 
 Example:
 ```
-nfc-emulate-card <timeout_seconds>
-# Emulation started for <timeout_seconds>
+nfc-emulate-card [<timeout_ms>]
+# Emulation started for <timeout_ms>
 # Emulation over
 OK
 ```
@@ -964,10 +968,13 @@ OK
 ### nfc-write-card
 Activates the NFC reader for given time. Writes the NDEF URI message into the first discovered NFC tag type A or exits on timeout.
 
+When used without a timeout, the command will wait indefinitely for a card to be placed on the reader and will repeat the write operation
+each 100ms.
+
 Example:
 ```
-nfc-write_card <timeout_seconds>
-# NFC reader on, put the card on the reader (timeout <timeout_seconds> s)
+nfc-write-card [<timeout_ms>]
+# NFC reader on, put the card on the reader (timeout <timeout_ms> ms)
 # Writting URI to NFC tag 7AF403
 
 ### unit-test-run
diff --git a/core/embed/projects/prodtest/cmd/prodtest_nfc.c b/core/embed/projects/prodtest/cmd/prodtest_nfc.c
index 023a9185c34..cbc2064a781 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_nfc.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_nfc.c
@@ -29,12 +29,15 @@ static nfc_dev_info_t dev_info = {0};
 
 static void prodtest_nfc_read_card(cli_t* cli) {
   uint32_t timeout = 0;
+  bool timeout_set = false;
   memset(&dev_info, 0, sizeof(dev_info));
 
-  if (cli_has_arg(cli, "timeout") &&
-      !cli_arg_uint32(cli, "timeout", &timeout)) {
-    cli_error_arg(cli, "Expecting timeout argument.");
-    return;
+  if (cli_has_arg(cli, "timeout")) {
+    if (!cli_arg_uint32(cli, "timeout", &timeout)) {
+      cli_error_arg(cli, "Expecting timeout argument.");
+      return;
+    }
+    timeout_set = true;
   }
 
   if (cli_arg_count(cli) > 1) {
@@ -48,7 +51,11 @@ static void prodtest_nfc_read_card(cli_t* cli) {
     cli_error(cli, CLI_ERROR_FATAL, "NFC init failed");
     goto cleanup;
   } else {
-    cli_trace(cli, "NFC activated in reader mode for %d ms.", timeout);
+    if (timeout_set) {
+      cli_trace(cli, "NFC activated in reader mode for %d ms.", timeout);
+    } else {
+      cli_trace(cli, "NFC activated in reader mode");
+    }
   }
 
   nfc_register_tech(NFC_POLLER_TECH_A | NFC_POLLER_TECH_B | NFC_POLLER_TECH_F |
@@ -59,7 +66,7 @@ static void prodtest_nfc_read_card(cli_t* cli) {
   uint32_t expire_time = ticks_timeout(timeout);
 
   while (true) {
-    if (ticks_expired(expire_time)) {
+    if (timeout_set && ticks_expired(expire_time)) {
       cli_error(cli, CLI_ERROR_TIMEOUT, "NFC timeout");
       goto cleanup;
     }
@@ -73,44 +80,51 @@ static void prodtest_nfc_read_card(cli_t* cli) {
 
     if (nfc_event == NFC_EVENT_ACTIVATED) {
       nfc_dev_read_info(&dev_info);
+
+      cli_trace(cli, "NFC card detected.");
+
+      switch (dev_info.type) {
+        case NFC_DEV_TYPE_A:
+          cli_trace(cli, "NFC Type A: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_B:
+          cli_trace(cli, "NFC Type B: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_F:
+          cli_trace(cli, "NFC Type F: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_V:
+          cli_trace(cli, "NFC Type V: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_ST25TB:
+          cli_trace(cli, "NFC Type ST25TB: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_AP2P:
+          cli_trace(cli, "NFC Type AP2P: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_UNKNOWN:
+          cli_trace(cli, "NFC Type UNKNOWN");
+          break;
+        default:
+          cli_error(cli, CLI_ERROR_ABORT, "NFC ERROR Unexpected");
+          goto cleanup;
+      }
+
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
+      }
+
+      systick_delay_ms(100);
       nfc_dev_deactivate();
-      break;
     }
 
     if (cli_aborted(cli)) {
       goto cleanup;
     }
-  }
-
-  cli_trace(cli, "NFC card detected.");
-
-  switch (dev_info.type) {
-    case NFC_DEV_TYPE_A:
-      cli_trace(cli, "NFC Type A: UID: %s", dev_info.uid);
-      break;
-    case NFC_DEV_TYPE_B:
-      cli_trace(cli, "NFC Type B: UID: %s", dev_info.uid);
-      break;
-    case NFC_DEV_TYPE_F:
-      cli_trace(cli, "NFC Type F: UID: %s", dev_info.uid);
-      break;
-    case NFC_DEV_TYPE_V:
-      cli_trace(cli, "NFC Type V: UID: %s", dev_info.uid);
-      break;
-    case NFC_DEV_TYPE_ST25TB:
-      cli_trace(cli, "NFC Type ST25TB: UID: %s", dev_info.uid);
-      break;
-    case NFC_DEV_TYPE_AP2P:
-      cli_trace(cli, "NFC Type AP2P: UID: %s", dev_info.uid);
-      break;
-    case NFC_DEV_TYPE_UNKNOWN:
-      cli_error(cli, CLI_ERROR_ABORT, "NFC Type UNKNOWN");
-      goto cleanup;
-      return;
 
-    default:
-      cli_error(cli, CLI_ERROR_ABORT, "NFC Type UNKNOWN");
-      goto cleanup;
+    systick_delay_ms(1);
   }
 
   cli_ok(cli, "");
@@ -121,11 +135,14 @@ static void prodtest_nfc_read_card(cli_t* cli) {
 
 static void prodtest_nfc_emulate_card(cli_t* cli) {
   uint32_t timeout = 0;
+  bool timeout_set = false;
 
-  if (cli_has_arg(cli, "timeout") &&
-      !cli_arg_uint32(cli, "timeout", &timeout)) {
-    cli_error_arg(cli, "Expecting timeout argument.");
-    return;
+  if (cli_has_arg(cli, "timeout")) {
+    if (!cli_arg_uint32(cli, "timeout", &timeout)) {
+      cli_error_arg(cli, "Expecting timeout argument.");
+      return;
+    }
+    timeout_set = true;
   }
 
   if (cli_arg_count(cli) > 1) {
@@ -139,7 +156,11 @@ static void prodtest_nfc_emulate_card(cli_t* cli) {
     cli_error(cli, CLI_ERROR_FATAL, "NFC init failed");
     goto cleanup;
   } else {
-    cli_trace(cli, "Emulation started for %d ms", timeout);
+    if (timeout_set) {
+      cli_trace(cli, "Emulation started for %d ms", timeout);
+    } else {
+      cli_trace(cli, "Emulation started");
+    }
   }
 
   nfc_register_tech(NFC_CARD_EMU_TECH_A);
@@ -148,7 +169,7 @@ static void prodtest_nfc_emulate_card(cli_t* cli) {
   uint32_t expire_time = ticks_timeout(timeout);
   nfc_event_t nfc_event;
 
-  while (!ticks_expired(expire_time)) {
+  while (!timeout_set || !ticks_expired(expire_time)) {
     nfc_status_t nfc_status = nfc_get_event(&nfc_event);
 
     if (nfc_status != NFC_OK) {
@@ -159,6 +180,7 @@ static void prodtest_nfc_emulate_card(cli_t* cli) {
     if (cli_aborted(cli)) {
       goto cleanup;
     }
+    systick_delay_ms(1);
   }
 
   cli_trace(cli, "Emulation over");
@@ -171,12 +193,15 @@ static void prodtest_nfc_emulate_card(cli_t* cli) {
 
 static void prodtest_nfc_write_card(cli_t* cli) {
   uint32_t timeout = 0;
+  bool timeout_set = false;
   memset(&dev_info, 0, sizeof(dev_info));
 
-  if (cli_has_arg(cli, "timeout") &&
-      !cli_arg_uint32(cli, "timeout", &timeout)) {
-    cli_error_arg(cli, "Expecting timeout argument.");
-    return;
+  if (cli_has_arg(cli, "timeout")) {
+    if (!cli_arg_uint32(cli, "timeout", &timeout)) {
+      cli_error_arg(cli, "Expecting timeout argument.");
+      return;
+    }
+    timeout_set = true;
   }
 
   if (cli_arg_count(cli) > 1) {
@@ -190,8 +215,13 @@ static void prodtest_nfc_write_card(cli_t* cli) {
     cli_error(cli, CLI_ERROR_FATAL, "NFC init failed");
     goto cleanup;
   } else {
-    cli_trace(cli, "NFC reader on, put the card on the reader (timeout %d ms)",
-              timeout);
+    if (timeout_set) {
+      cli_trace(cli,
+                "NFC reader on, put the card on the reader (timeout %d ms)",
+                timeout);
+    } else {
+      cli_trace(cli, "NFC reader on, put the card on the reader");
+    }
   }
 
   nfc_register_tech(NFC_POLLER_TECH_A | NFC_POLLER_TECH_B | NFC_POLLER_TECH_F |
@@ -202,7 +232,7 @@ static void prodtest_nfc_write_card(cli_t* cli) {
   uint32_t expire_time = ticks_timeout(timeout);
 
   while (true) {
-    if (ticks_expired(expire_time)) {
+    if (timeout_set && ticks_expired(expire_time)) {
       cli_error(cli, CLI_ERROR_TIMEOUT, "NFC timeout");
       goto cleanup;
     }
@@ -222,16 +252,24 @@ static void prodtest_nfc_write_card(cli_t* cli) {
         goto cleanup;
       }
 
-      cli_trace(cli, "Writting URI to NFC tag %s", dev_info.uid);
+      cli_trace(cli, "Writing URI to NFC tag %s", dev_info.uid);
       nfc_dev_write_ndef_uri();
 
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
+      }
+
+      systick_delay_ms(100);
       nfc_dev_deactivate();
-      break;
     }
 
     if (cli_aborted(cli)) {
       goto cleanup;
     }
+
+    systick_delay_ms(1);
   }
 
   cli_ok(cli, "");
@@ -246,21 +284,21 @@ PRODTEST_CLI_CMD(
   .name = "nfc-read-card",
   .func = prodtest_nfc_read_card,
   .info = "Activate NFC in reader mode",
-  .args = "<timeout>"
+  .args = "[<timeout>]"
 );
 
 PRODTEST_CLI_CMD(
   .name = "nfc-emulate-card",
   .func = prodtest_nfc_emulate_card,
   .info = "Activate NFC in card emulation (CE) mode",
-  .args = "<timeout>"
+  .args = "[<timeout>]"
 );
 
 PRODTEST_CLI_CMD(
   .name = "nfc-write-card",
   .func = prodtest_nfc_write_card,
   .info = "Activate NFC in reader mode and write a URI to the attached card",
-  .args = "<timeout>"
+  .args = "[<timeout>]"
 );
 
 #endif  // USE_NFC


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR enables continuous NFC testing by allowing commands to run indefinitely when no timeout is provided. Key changes include updated handling of the timeout argument in NFC functions, enhanced logging messages for clarity, and revised command argument syntax in the README.

### Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

| File                                                               | Description                                                                   |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| core/embed/projects/prodtest/cmd/prodtest_nfc.c                     | Modified NFC command implementations to support infinite operation and improved logging. |
| core/embed/projects/prodtest/README.md                              | Updated documentation to reflect the new optional timeout syntax and continuous testing behavior. |


<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**core/embed/projects/prodtest/cmd/prodtest_nfc.c:252**
* Typo in logging message: 'Writting' should be corrected to 'Writing'.
```
cli_trace(cli, "Writting URI to NFC tag %s", dev_info.uid);
```
**core/embed/projects/prodtest/README.md:975**
* Inconsistency in command naming: the code defines the command as 'nfc-write-card' (with hyphen) while the README still uses an underscore.
```
nfc-write_card [<timeout_seconds>]
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-04-30T13:15:45Z
user: login: kopecdav
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:05:57Z
user: login: TychoVrahe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:30:00Z
user: login: TychoVrahe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:38:46Z
user: login: TychoVrahe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:39:17Z
user: login: TychoVrahe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:40:12Z
user: login: TychoVrahe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:41:34Z
user: login: kopecdav
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:45:07Z
user: login: kopecdav
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:55:11Z
user: login: kopecdav
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:01:49Z

Review comments:
diff_hunk: @@ -73,46 +80,51 @@ static void prodtest_nfc_read_card(cli_t* cli) {
 
     if (nfc_event == NFC_EVENT_ACTIVATED) {
       nfc_dev_read_info(&dev_info);
+
+      cli_trace(cli, "NFC card detected.");
+
+      switch (dev_info.type) {
+        case NFC_DEV_TYPE_A:
+          cli_trace(cli, "NFC Type A: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_B:
+          cli_trace(cli, "NFC Type B: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_F:
+          cli_trace(cli, "NFC Type F: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_V:
+          cli_trace(cli, "NFC Type V: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_ST25TB:
+          cli_trace(cli, "NFC Type ST25TB: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_AP2P:
+          cli_trace(cli, "NFC Type AP2P: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_UNKNOWN:
+          cli_trace(cli, "NFC Type UNKNOWN");
+          break;
+        default:
+          cli_error(cli, CLI_ERROR_ABORT, "NFC ERROR Unexpected");
+          goto cleanup;
+      }
+
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: kopecdav
body: If you break here, you finish the prodtest without calling nfc_deinit(). You can swap return for cleanup.
created_at: 2025-05-01T06:39:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,8 +252,14 @@ static void prodtest_nfc_write_card(cli_t* cli) {
       cli_trace(cli, "Writting URI to NFC tag %s", dev_info.uid);
       nfc_dev_write_ndef_uri();
 
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: kopecdav
body: If you break here, you finish the prodtest without calling nfc_deinit(). You can swap return for cleanup.
created_at: 2025-05-01T06:40:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,8 +252,14 @@ static void prodtest_nfc_write_card(cli_t* cli) {
       cli_trace(cli, "Writting URI to NFC tag %s", dev_info.uid);
       nfc_dev_write_ndef_uri();
 
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
+      }
+
+      systick_delay_ms(100);
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: kopecdav
body: Adding delay into the polling loops same as you did in read function is a good idea, but I think that delay should be outside of this **if** to delay calling the nfc_get_event() function too often. Here the nfc_dev_deactivate() could be called without any delay, all interactions with the paired card are over.

also can you add the delay into the nfc_emulate_card prodtest as well?
created_at: 2025-05-01T06:51:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,7 +167,7 @@ static void prodtest_nfc_emulate_card(cli_t* cli) {
   uint32_t expire_time = ticks_timeout(timeout);
   nfc_event_t nfc_event;
 
-  while (!ticks_expired(expire_time)) {
+  while (!timeout_set || !ticks_expired(expire_time)) {
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: kopecdav
body: One thing I missed before, we should also deinitilize nfc when card emulation expires.
created_at: 2025-05-01T07:11:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -958,8 +958,8 @@ Activate NFC in Card Emulator mode for given time, or infinite time if no timeou
 
 Example:
 ```
-nfc-emulate-card [<timeout_seconds>]
-# Emulation started for <timeout_seconds>
+nfc-emulate-card [<timeout_ms>]
path: core/embed/projects/prodtest/README.md
user: login: kopecdav
body: Update README.md cli documentation
created_at: 2025-05-01T09:44:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,7 +167,7 @@ static void prodtest_nfc_emulate_card(cli_t* cli) {
   uint32_t expire_time = ticks_timeout(timeout);
   nfc_event_t nfc_event;
 
-  while (!ticks_expired(expire_time)) {
+  while (!timeout_set || !ticks_expired(expire_time)) {
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: TychoVrahe
body: you mean calling nfc_dev_deactivate?
created_at: 2025-05-01T15:30:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -958,8 +958,8 @@ Activate NFC in Card Emulator mode for given time, or infinite time if no timeou
 
 Example:
 ```
-nfc-emulate-card [<timeout_seconds>]
-# Emulation started for <timeout_seconds>
+nfc-emulate-card [<timeout_ms>]
path: core/embed/projects/prodtest/README.md
user: login: TychoVrahe
body: did you mean this? https://github.com/trezor/trezor-firmware/commit/6050c55c22edc49e5c426bff3c445ad5b69f9ff2
created_at: 2025-05-01T15:38:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,8 +252,14 @@ static void prodtest_nfc_write_card(cli_t* cli) {
       cli_trace(cli, "Writting URI to NFC tag %s", dev_info.uid);
       nfc_dev_write_ndef_uri();
 
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
+      }
+
+      systick_delay_ms(100);
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: TychoVrahe
body: i don't actually mind calling `nfc_get_event` too often, what i wanted to prevent is to reporting a card read/ writing to a card too often.

i added a smaller delay before calling nfc_get_event though: https://github.com/trezor/trezor-firmware/commit/84377ef41d36f6b65ff4d736c3cc587fde21c80e
created_at: 2025-05-01T15:39:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,46 +80,51 @@ static void prodtest_nfc_read_card(cli_t* cli) {
 
     if (nfc_event == NFC_EVENT_ACTIVATED) {
       nfc_dev_read_info(&dev_info);
+
+      cli_trace(cli, "NFC card detected.");
+
+      switch (dev_info.type) {
+        case NFC_DEV_TYPE_A:
+          cli_trace(cli, "NFC Type A: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_B:
+          cli_trace(cli, "NFC Type B: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_F:
+          cli_trace(cli, "NFC Type F: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_V:
+          cli_trace(cli, "NFC Type V: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_ST25TB:
+          cli_trace(cli, "NFC Type ST25TB: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_AP2P:
+          cli_trace(cli, "NFC Type AP2P: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_UNKNOWN:
+          cli_trace(cli, "NFC Type UNKNOWN");
+          break;
+        default:
+          cli_error(cli, CLI_ERROR_ABORT, "NFC ERROR Unexpected");
+          goto cleanup;
+      }
+
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: TychoVrahe
body: no this does not return, it jumps out of the loop and then cli_ok + nfc_deinit is called
created_at: 2025-05-01T15:40:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,8 +252,14 @@ static void prodtest_nfc_write_card(cli_t* cli) {
       cli_trace(cli, "Writting URI to NFC tag %s", dev_info.uid);
       nfc_dev_write_ndef_uri();
 
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: TychoVrahe
body: see above
created_at: 2025-05-01T15:41:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -958,8 +958,8 @@ Activate NFC in Card Emulator mode for given time, or infinite time if no timeou
 
 Example:
 ```
-nfc-emulate-card [<timeout_seconds>]
-# Emulation started for <timeout_seconds>
+nfc-emulate-card [<timeout_ms>]
path: core/embed/projects/prodtest/README.md
user: login: kopecdav
body: I ment to explicitly describe eqch nfc command that it might be called with or without parameter, but yeah this might be sufficient
created_at: 2025-05-01T15:45:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,46 +80,51 @@ static void prodtest_nfc_read_card(cli_t* cli) {
 
     if (nfc_event == NFC_EVENT_ACTIVATED) {
       nfc_dev_read_info(&dev_info);
+
+      cli_trace(cli, "NFC card detected.");
+
+      switch (dev_info.type) {
+        case NFC_DEV_TYPE_A:
+          cli_trace(cli, "NFC Type A: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_B:
+          cli_trace(cli, "NFC Type B: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_F:
+          cli_trace(cli, "NFC Type F: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_V:
+          cli_trace(cli, "NFC Type V: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_ST25TB:
+          cli_trace(cli, "NFC Type ST25TB: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_AP2P:
+          cli_trace(cli, "NFC Type AP2P: UID: %s", dev_info.uid);
+          break;
+        case NFC_DEV_TYPE_UNKNOWN:
+          cli_trace(cli, "NFC Type UNKNOWN");
+          break;
+        default:
+          cli_error(cli, CLI_ERROR_ABORT, "NFC ERROR Unexpected");
+          goto cleanup;
+      }
+
+      if (timeout_set) {
+        nfc_dev_deactivate();
+        cli_trace(cli, "NFC reader mode over");
+        break;
path: core/embed/projects/prodtest/cmd/prodtest_nfc.c
user: login: kopecdav
body: Ahh yes, silly me. I missed there is no return above cleanup section, closing this and two similar comments.
created_at: 2025-05-01T15:55:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-30T13:10:01Z
body: <!-- ui-comment-Core -->
|core UI changes|device test|click test|persistence test|
|---------------|-----------|----------|----------------|
|T2T1 Model T   |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_device_test/status.png" width="24" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_device_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_device_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_device_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_device_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_click_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_click_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_click_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_click_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_click_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_persistence_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_persistence_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_persistence_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_persistence_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T2T1-en-core_persistence_test/master_diff.html))||
|T3B1 Safe 3    |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_device_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_device_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_device_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_device_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_device_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_click_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_click_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_click_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_click_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_click_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_persistence_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_persistence_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_persistence_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_persistence_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3B1-en-core_persistence_test/master_diff.html))||
|T3T1 Safe 5    |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_device_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_device_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_device_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_device_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_device_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_click_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_click_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_click_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_click_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_click_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_persistence_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_persistence_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_persistence_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_persistence_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/14778697072/T3T1-en-core_persistence_test/master_diff.html))||
|All            |<img src="https://data.trezor.io/dev/firmware/master_diff/14778697072/status.png" width="20px" height="20px" /> [main](https://data.trezor.io/dev/firmware/master_diff/14778697072/index.html)([screens](https://data.trezor.io/dev/firmware/master_diff/14778697072/master_diff.html)) ||
author_association: NONE
user: login: kopecdav
created_at: 2025-05-01T07:09:57Z
body: Tested on bench with NFC tags and reader, all commands works as expected.
author_association: CONTRIBUTOR
user: login: TychoVrahe
created_at: 2025-05-01T14:45:11Z
body: @kopecdav please approve if you see no issues
author_association: CONTRIBUTOR
user: login: kopecdav
created_at: 2025-05-01T15:07:33Z
body: Sorry, I wrote a review but forgot to submit, few minor comments and we should be good to go.
author_association: CONTRIBUTOR