From b443fc7723ba4531657b132d68516d6e7dba4002 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:20:35 +0200 Subject: [PATCH 1/7] Add 'hf.field.timeout' to prefs --- armsrc/appmain.c | 30 +++++++++++- armsrc/appmain.h | 1 + armsrc/fpgaloader.c | 26 +++++++++++ client/src/comms.c | 29 ++++++++++++ client/src/comms.h | 2 + client/src/pm3line_vocabulary.h | 2 + client/src/preferences.c | 81 +++++++++++++++++++++++++++++++++ client/src/ui.h | 1 + include/pm3_cmd.h | 1 + 9 files changed, 172 insertions(+), 1 deletion(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 4d7f74c8c..343001eca 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -91,10 +91,13 @@ int g_dbglevel = DBG_ERROR; uint8_t g_trigger = 0; bool g_hf_field_active = false; +bool g_hf_field_timeout_active = false; extern uint32_t _stack_start[], _stack_end[]; common_area_t g_common_area __attribute__((section(".commonarea"))); static int button_status = BUTTON_NO_CLICK; static bool allow_send_wtx = false; +static uint32_t g_hf_field_activity_timeout_ms = 0; +static uint32_t g_last_activity_ms = 0; uint16_t g_tearoff_delay_us = 0; bool g_tearoff_enabled = false; uint8_t g_tearoff_skip = 0; @@ -125,6 +128,7 @@ void hf_field_off(void) { FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); LEDsoff(); g_hf_field_active = false; + g_hf_field_timeout_active = false; } void send_wtx(uint16_t wtx) { @@ -1002,6 +1006,18 @@ static void PacketReceived(PacketCommandNG *packet) { reply_ng(CMD_SET_TEAROFF, PM3_SUCCESS, NULL, 0); break; } + case CMD_SET_HF_FIELD_TIMEOUT: { + if (packet->length != sizeof(uint32_t)) { + reply_ng(CMD_SET_HF_FIELD_TIMEOUT, PM3_EINVARG, NULL, 0); + break; + } + uint32_t timeout_ms = 0; + memcpy(&timeout_ms, packet->data.asBytes, sizeof(timeout_ms)); + g_hf_field_activity_timeout_ms = timeout_ms; + g_last_activity_ms = GetTickCount(); + reply_ng(CMD_SET_HF_FIELD_TIMEOUT, PM3_SUCCESS, NULL, 0); + break; + } // always available case CMD_HF_DROPFIELD: { hf_field_off(); @@ -2699,6 +2715,8 @@ static void PacketReceived(PacketCommandNG *packet) { } case CMD_FPGA_MAJOR_MODE_OFF: { // ## FPGA Control FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + g_hf_field_active = false; + g_hf_field_timeout_active = false; SpinDelay(200); LED_D_OFF(); // LED D indicates field ON or OFF break; @@ -3315,6 +3333,7 @@ void __attribute__((noreturn)) AppMain(void) { FpgaDownloadAndGo(FPGA_BITSTREAM_HF); StartTickCount(); + g_last_activity_ms = GetTickCount(); #ifdef WITH_LCD LCDInit(); @@ -3378,12 +3397,21 @@ void __attribute__((noreturn)) AppMain(void) { int ret = receive_ng(&rx); if (ret == PM3_SUCCESS) { PacketReceived(&rx); + g_last_activity_ms = GetTickCount(); } else if (ret != PM3_ENODATA) { - Dbprintf("Error in frame reception: %d %s", ret, (ret == PM3_EIO) ? "PM3_EIO" : ""); // TODO if error, shall we resync ? } + if (g_hf_field_activity_timeout_ms > 0 && g_hf_field_timeout_active) { + if (GetTickCountDelta(g_last_activity_ms) >= g_hf_field_activity_timeout_ms) { + if (g_dbglevel >= DBG_INFO) { + Dbprintf("HF field auto-off: inactivity timeout (%u ms)", g_hf_field_activity_timeout_ms); + } + hf_field_off(); + } + } + // Press button for one second to enter a possible standalone mode button_status = BUTTON_HELD(1000); if (button_status == BUTTON_HOLD) { diff --git a/armsrc/appmain.h b/armsrc/appmain.h index fdc520f5f..40cbdb52d 100644 --- a/armsrc/appmain.h +++ b/armsrc/appmain.h @@ -24,6 +24,7 @@ extern uint8_t g_trigger; extern bool g_hf_field_active; +extern bool g_hf_field_timeout_active; void hf_field_off(void); int tearoff_hook(void); diff --git a/armsrc/fpgaloader.c b/armsrc/fpgaloader.c index e1c313414..2d97129ec 100644 --- a/armsrc/fpgaloader.c +++ b/armsrc/fpgaloader.c @@ -562,12 +562,38 @@ void FpgaSendCommand(uint16_t cmd, uint16_t v) { AT91C_BASE_SPI->SPI_TDR = AT91C_SPI_LASTXFER | cmd | v; // send the data while (!(AT91C_BASE_SPI->SPI_SR & AT91C_SPI_RDRF)) {}; // wait till transfer is complete } + //----------------------------------------------------------------------------- // Write the FPGA setup word (that determines what mode the logic is in, read // vs. clone vs. etc.). This is now a special case of FpgaSendCommand() to // avoid changing this function's occurrence everywhere in the source code. //----------------------------------------------------------------------------- void FpgaWriteConfWord(uint16_t v) { + const int current = FpgaGetCurrent(); + + // Keep track of whether or not we should be monitoring the HF field timeout + if (current == FPGA_BITSTREAM_HF || current == FPGA_BITSTREAM_HF_15 || current == FPGA_BITSTREAM_HF_FELICA) { + const uint16_t major = v & FPGA_MAJOR_MODE_MASK; + const uint16_t minor = v & FPGA_MINOR_MODE_MASK; + + switch (major) { + case FPGA_MAJOR_MODE_HF_READER: + g_hf_field_timeout_active = true; + break; + case FPGA_MAJOR_MODE_HF_ISO14443A: + g_hf_field_timeout_active = (minor == FPGA_HF_ISO14443A_READER_LISTEN || minor == FPGA_HF_ISO14443A_READER_MOD); + break; + case FPGA_MAJOR_MODE_HF_ISO18092: + g_hf_field_timeout_active = (minor & FPGA_HF_ISO18092_FLAG_READER) != 0; + break; + default: + g_hf_field_timeout_active = false; + break; + } + } else { + g_hf_field_timeout_active = false; + } + FpgaSendCommand(FPGA_CMD_SET_CONFREG, v); } diff --git a/client/src/comms.c b/client/src/comms.c index 2ded55ebc..6dd6d457b 100644 --- a/client/src/comms.c +++ b/client/src/comms.c @@ -846,6 +846,28 @@ bool OpenProxmark(pm3_device_t **dev, const char *port, bool wait_for_port, int } } +int SetHfFieldTimeout(uint32_t timeout_ms, bool quiet) { + if (g_session.pm3_present == false) { + return PM3_ENOTTY; + } + + clearCommandBuffer(); + SendCommandNG(CMD_SET_HF_FIELD_TIMEOUT, (uint8_t *)&timeout_ms, sizeof(timeout_ms)); + + PacketResponseNG resp; + if (WaitForResponseTimeoutW(CMD_SET_HF_FIELD_TIMEOUT, &resp, 1000, false) == false) { + if (!quiet) { + PrintAndLogEx(WARNING, "timeout while setting HF field timeout"); + } + return PM3_ETIMEOUT; + } + + if (resp.status != PM3_SUCCESS && !quiet) { + PrintAndLogEx(WARNING, "HF field timeout command failed (%d)", resp.status); + } + return resp.status; +} + // check if we can communicate with Pm3 int TestProxmark(pm3_device_t *dev) { @@ -921,6 +943,13 @@ int TestProxmark(pm3_device_t *dev) { return res; } } + + if (g_session.hf_field_timeout > 0) { + int timeout_res = SetHfFieldTimeout(g_session.hf_field_timeout, true); + if (timeout_res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Failed to apply HF field timeout (" _YELLOW_("%u") " ms)", g_session.hf_field_timeout); + } + } return PM3_SUCCESS; } diff --git a/client/src/comms.h b/client/src/comms.h index 133474e5e..6485e8e81 100644 --- a/client/src/comms.h +++ b/client/src/comms.h @@ -120,6 +120,8 @@ bool WaitForResponseTimeoutW(uint32_t cmd, PacketResponseNG *response, size_t ms bool WaitForResponseTimeout(uint32_t cmd, PacketResponseNG *response, size_t ms_timeout); bool WaitForResponse(uint32_t cmd, PacketResponseNG *response); +int SetHfFieldTimeout(uint32_t timeout_ms, bool quiet); + //bool GetFromDevice(DeviceMemType_t memtype, uint8_t *dest, uint32_t bytes, uint32_t start_index, PacketResponseNG *response, size_t ms_timeout, bool show_warning); bool GetFromDevice(DeviceMemType_t memtype, uint8_t *dest, uint32_t bytes, uint32_t start_index, uint8_t *data, uint32_t datalen, PacketResponseNG *response, size_t ms_timeout, bool show_warning); diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 7a5fde2a0..120619498 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -45,6 +45,7 @@ const static vocabulary_t vocabulary[] = { { 1, "prefs get client.debug" }, { 1, "prefs get client.delay" }, { 1, "prefs get client.timeout" }, + { 1, "prefs get hf.field.timeout" }, { 1, "prefs get color" }, { 1, "prefs get savepaths" }, { 1, "prefs get emoji" }, @@ -57,6 +58,7 @@ const static vocabulary_t vocabulary[] = { { 1, "prefs set client.debug" }, { 1, "prefs set client.delay" }, { 1, "prefs set client.timeout" }, + { 1, "prefs set hf.field.timeout" }, { 1, "prefs set color" }, { 1, "prefs set emoji" }, { 1, "prefs set hints" }, diff --git a/client/src/preferences.c b/client/src/preferences.c index f654a8533..c42e73615 100644 --- a/client/src/preferences.c +++ b/client/src/preferences.c @@ -102,6 +102,7 @@ int preferences_load(void) { g_session.client_debug_level = cdbOFF; // g_session.device_debug_level = ddbOFF; g_session.timeout = uart_get_timeouts(); + g_session.hf_field_timeout = 0; g_session.window_changed = false; g_session.plot.x = 10; @@ -329,6 +330,7 @@ void preferences_save_callback(json_t *root) { */ JsonSaveInt(root, "client.exe.delay", g_session.client_exe_delay); JsonSaveInt(root, "client.timeout", g_session.timeout); + JsonSaveInt(root, "hf.field.timeout", g_session.hf_field_timeout); // MQTT JsonSaveStr(root, "mqtt.server", g_session.mqtt_server); @@ -440,6 +442,14 @@ void preferences_load_callback(json_t *root) { if (json_unpack_ex(root, &up_error, 0, "{s:i}", "client.timeout", &i1) == 0) g_session.timeout = i1; + // persistent HF field timeout + if (json_unpack_ex(root, &up_error, 0, "{s:i}", "hf.field.timeout", &i1) == 0) { + g_session.hf_field_timeout = i1; + } else if (json_unpack_ex(root, &up_error, 0, "{s:i}", "field.timeout", &i1) == 0) { + // Backward compatibility with earlier key name. + g_session.hf_field_timeout = i1; + } + // MQTT server if (json_unpack_ex(root, &up_error, 0, "{s:s}", "mqtt.server", &s1) == 0) setDefaultMqttServer(s1); @@ -671,6 +681,14 @@ static void showClientTimeoutState(void) { PrintAndLogEx(INFO, " communication timeout... " _GREEN_("%u") " ms", g_session.timeout); } +static void showFieldTimeoutState(void) { + if (g_session.hf_field_timeout == 0) { + PrintAndLogEx(INFO, " HF field timeout........ " _WHITE_("off")); + } else { + PrintAndLogEx(INFO, " HF field timeout........ " _GREEN_("%u") " ms", g_session.hf_field_timeout); + } +} + static void showMqttServer(prefShowOpt_t opt) { if ((g_session.mqtt_server == NULL) || (strcmp(g_session.mqtt_server, "") == 0)) { PrintAndLogEx(INFO, " MQTT server.............%s "_WHITE_("not set"), pref_show_status_msg(opt)); @@ -1067,6 +1085,50 @@ static int setCmdClientTimeout(const char *Cmd) { return PM3_SUCCESS; } +static int setCmdHfFieldTimeout(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "prefs set hf.field.timeout", + "Set persistent preference of PM3 HF field inactivity timeout", + "prefs set hf.field.timeout --ms 0 --> disable HF field auto timeout\n" + "prefs set hf.field.timeout --ms 5000 --> turn HF field off after 5s of inactivity\n" + "prefs set hf.field.timeout --ms 300000 --> turn HF field off after 5 minutes of inactivity\n" + "prefs set hf.field.timeout --ms 900000 --> turn HF field off after 15 minutes of inactivity\n"); + + void *argtable[] = { + arg_param_begin, + arg_int0("m", "ms", "", "HF field inactivity timeout in milliseconds"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + int32_t arg = arg_get_int_def(ctx, 1, -1); + CLIParserFree(ctx); + + if (arg < 0) { + showFieldTimeoutState(); + return PM3_SUCCESS; + } + + uint32_t new_value = (uint32_t)arg; + + if (g_session.hf_field_timeout != new_value) { + showFieldTimeoutState(); + g_session.hf_field_timeout = new_value; + showFieldTimeoutState(); + preferences_save(); + } else { + showFieldTimeoutState(); + } + + if (g_session.pm3_present) { + int res = SetHfFieldTimeout(g_session.hf_field_timeout, false); + if (res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Failed to apply HF field timeout to connected PM3"); + } + } + + return PM3_SUCCESS; +} + static int setCmdHint(const char *Cmd) { CLIParserContext *ctx; @@ -1517,6 +1579,22 @@ static int getCmdClientTimeout(const char *Cmd) { return PM3_SUCCESS; } +static int getCmdHfFieldTimeout(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "prefs get hf.field.timeout", + "Get preference of PM3 HF field inactivity timeout", + "prefs get hf.field.timeout" + ); + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + showFieldTimeoutState(); + return PM3_SUCCESS; +} + static int getCmdMqtt(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "prefs get mqtt", @@ -1540,6 +1618,7 @@ static command_t CommandTableGet[] = { {"client.debug", getCmdDebug, AlwaysAvailable, "Get client debug level preference"}, {"client.delay", getCmdExeDelay, AlwaysAvailable, "Get client execution delay preference"}, {"client.timeout", getCmdClientTimeout, AlwaysAvailable, "Get client execution delay preference"}, + {"hf.field.timeout", getCmdHfFieldTimeout, AlwaysAvailable, "Get PM3 HF field inactivity timeout preference"}, {"color", getCmdColor, AlwaysAvailable, "Get color support preference"}, {"savepaths", getCmdSavePaths, AlwaysAvailable, "Get file folder "}, // {"devicedebug", getCmdDeviceDebug, AlwaysAvailable, "Get device debug level"}, @@ -1557,6 +1636,7 @@ static command_t CommandTableSet[] = { {"client.debug", setCmdDebug, AlwaysAvailable, "Set client debug level"}, {"client.delay", setCmdExeDelay, AlwaysAvailable, "Set client execution delay"}, {"client.timeout", setCmdClientTimeout, AlwaysAvailable, "Set client communication timeout"}, + {"hf.field.timeout", setCmdHfFieldTimeout, AlwaysAvailable, "Set PM3 HF field inactivity timeout"}, {"color", setCmdColor, AlwaysAvailable, "Set color support"}, {"emoji", setCmdEmoji, AlwaysAvailable, "Set emoji display"}, @@ -1630,6 +1710,7 @@ static int CmdPrefShow(const char *Cmd) { showClientExeDelayState(); showOutputState(prefShowNone); showClientTimeoutState(); + showFieldTimeoutState(); showMqttServer(prefShowNone); showMqttPort(prefShowNone); showMqttTopic(prefShowNone); diff --git a/client/src/ui.h b/client/src/ui.h index eb8dc4a94..aafb515bb 100644 --- a/client/src/ui.h +++ b/client/src/ui.h @@ -63,6 +63,7 @@ typedef struct { char *history_path; pm3_device_t *current_device; uint32_t timeout; + uint32_t hf_field_timeout; char *mqtt_server; char *mqtt_port; char *mqtt_topic; diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 5d10a8b0c..8b8c2750b 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -531,6 +531,7 @@ typedef struct { #define CMD_TIA 0x0117 #define CMD_BREAK_LOOP 0x0118 #define CMD_SET_TEAROFF 0x0119 +#define CMD_SET_HF_FIELD_TIMEOUT 0x011A #define CMD_GET_DBGMODE 0x0120 // RDV40, Flash memory operations From 75a49716a99cf25704492b6327ace7009b2b1255 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:03:23 +0300 Subject: [PATCH 2/7] Update to 'hf.field.timeout_sec' --- client/src/comms.c | 10 +++++--- client/src/comms.h | 2 +- client/src/pm3line_vocabulary.h | 4 +-- client/src/preferences.c | 44 +++++++++++++++------------------ client/src/ui.h | 2 +- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/client/src/comms.c b/client/src/comms.c index 6dd6d457b..de8045930 100644 --- a/client/src/comms.c +++ b/client/src/comms.c @@ -846,11 +846,13 @@ bool OpenProxmark(pm3_device_t **dev, const char *port, bool wait_for_port, int } } -int SetHfFieldTimeout(uint32_t timeout_ms, bool quiet) { +int SetHfFieldTimeout(uint32_t timeout_sec, bool quiet) { if (g_session.pm3_present == false) { return PM3_ENOTTY; } + uint32_t timeout_ms = timeout_sec * 1000U; + clearCommandBuffer(); SendCommandNG(CMD_SET_HF_FIELD_TIMEOUT, (uint8_t *)&timeout_ms, sizeof(timeout_ms)); @@ -944,10 +946,10 @@ int TestProxmark(pm3_device_t *dev) { } } - if (g_session.hf_field_timeout > 0) { - int timeout_res = SetHfFieldTimeout(g_session.hf_field_timeout, true); + if (g_session.hf_field_timeout_sec > 0) { + int timeout_res = SetHfFieldTimeout(g_session.hf_field_timeout_sec, true); if (timeout_res != PM3_SUCCESS) { - PrintAndLogEx(WARNING, "Failed to apply HF field timeout (" _YELLOW_("%u") " ms)", g_session.hf_field_timeout); + PrintAndLogEx(WARNING, "Failed to apply HF field timeout (" _YELLOW_("%u") " s)", g_session.hf_field_timeout_sec); } } return PM3_SUCCESS; diff --git a/client/src/comms.h b/client/src/comms.h index 6485e8e81..67ed3fcbb 100644 --- a/client/src/comms.h +++ b/client/src/comms.h @@ -120,7 +120,7 @@ bool WaitForResponseTimeoutW(uint32_t cmd, PacketResponseNG *response, size_t ms bool WaitForResponseTimeout(uint32_t cmd, PacketResponseNG *response, size_t ms_timeout); bool WaitForResponse(uint32_t cmd, PacketResponseNG *response); -int SetHfFieldTimeout(uint32_t timeout_ms, bool quiet); +int SetHfFieldTimeout(uint32_t timeout_sec, bool quiet); //bool GetFromDevice(DeviceMemType_t memtype, uint8_t *dest, uint32_t bytes, uint32_t start_index, PacketResponseNG *response, size_t ms_timeout, bool show_warning); bool GetFromDevice(DeviceMemType_t memtype, uint8_t *dest, uint32_t bytes, uint32_t start_index, uint8_t *data, uint32_t datalen, PacketResponseNG *response, size_t ms_timeout, bool show_warning); diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 120619498..a1038be09 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -45,7 +45,7 @@ const static vocabulary_t vocabulary[] = { { 1, "prefs get client.debug" }, { 1, "prefs get client.delay" }, { 1, "prefs get client.timeout" }, - { 1, "prefs get hf.field.timeout" }, + { 1, "prefs get hf.field.timeout_sec" }, { 1, "prefs get color" }, { 1, "prefs get savepaths" }, { 1, "prefs get emoji" }, @@ -58,7 +58,7 @@ const static vocabulary_t vocabulary[] = { { 1, "prefs set client.debug" }, { 1, "prefs set client.delay" }, { 1, "prefs set client.timeout" }, - { 1, "prefs set hf.field.timeout" }, + { 1, "prefs set hf.field.timeout_sec" }, { 1, "prefs set color" }, { 1, "prefs set emoji" }, { 1, "prefs set hints" }, diff --git a/client/src/preferences.c b/client/src/preferences.c index c42e73615..1077b38d1 100644 --- a/client/src/preferences.c +++ b/client/src/preferences.c @@ -102,7 +102,7 @@ int preferences_load(void) { g_session.client_debug_level = cdbOFF; // g_session.device_debug_level = ddbOFF; g_session.timeout = uart_get_timeouts(); - g_session.hf_field_timeout = 0; + g_session.hf_field_timeout_sec = 0; g_session.window_changed = false; g_session.plot.x = 10; @@ -330,7 +330,7 @@ void preferences_save_callback(json_t *root) { */ JsonSaveInt(root, "client.exe.delay", g_session.client_exe_delay); JsonSaveInt(root, "client.timeout", g_session.timeout); - JsonSaveInt(root, "hf.field.timeout", g_session.hf_field_timeout); + JsonSaveInt(root, "hf.field.timeout_sec", g_session.hf_field_timeout_sec); // MQTT JsonSaveStr(root, "mqtt.server", g_session.mqtt_server); @@ -442,13 +442,9 @@ void preferences_load_callback(json_t *root) { if (json_unpack_ex(root, &up_error, 0, "{s:i}", "client.timeout", &i1) == 0) g_session.timeout = i1; - // persistent HF field timeout - if (json_unpack_ex(root, &up_error, 0, "{s:i}", "hf.field.timeout", &i1) == 0) { - g_session.hf_field_timeout = i1; - } else if (json_unpack_ex(root, &up_error, 0, "{s:i}", "field.timeout", &i1) == 0) { - // Backward compatibility with earlier key name. - g_session.hf_field_timeout = i1; - } + // persistent HF field timeout (seconds) + if (json_unpack_ex(root, &up_error, 0, "{s:i}", "hf.field.timeout_sec", &i1) == 0) + g_session.hf_field_timeout_sec = (i1 > 0) ? (uint32_t)i1 : 0; // MQTT server if (json_unpack_ex(root, &up_error, 0, "{s:s}", "mqtt.server", &s1) == 0) @@ -682,10 +678,10 @@ static void showClientTimeoutState(void) { } static void showFieldTimeoutState(void) { - if (g_session.hf_field_timeout == 0) { + if (g_session.hf_field_timeout_sec == 0) { PrintAndLogEx(INFO, " HF field timeout........ " _WHITE_("off")); } else { - PrintAndLogEx(INFO, " HF field timeout........ " _GREEN_("%u") " ms", g_session.hf_field_timeout); + PrintAndLogEx(INFO, " HF field timeout........ " _GREEN_("%u") " s", g_session.hf_field_timeout_sec); } } @@ -1087,16 +1083,16 @@ static int setCmdClientTimeout(const char *Cmd) { static int setCmdHfFieldTimeout(const char *Cmd) { CLIParserContext *ctx; - CLIParserInit(&ctx, "prefs set hf.field.timeout", + CLIParserInit(&ctx, "prefs set hf.field.timeout_sec", "Set persistent preference of PM3 HF field inactivity timeout", - "prefs set hf.field.timeout --ms 0 --> disable HF field auto timeout\n" - "prefs set hf.field.timeout --ms 5000 --> turn HF field off after 5s of inactivity\n" - "prefs set hf.field.timeout --ms 300000 --> turn HF field off after 5 minutes of inactivity\n" - "prefs set hf.field.timeout --ms 900000 --> turn HF field off after 15 minutes of inactivity\n"); + "prefs set hf.field.timeout_sec --sec 0 --> disable HF field auto timeout\n" + "prefs set hf.field.timeout_sec --sec 5 --> turn HF field off after 5 seconds of inactivity\n" + "prefs set hf.field.timeout_sec --sec 300 --> turn HF field off after 5 minutes of inactivity\n" + "prefs set hf.field.timeout_sec --sec 900 --> turn HF field off after 15 minutes of inactivity\n"); void *argtable[] = { arg_param_begin, - arg_int0("m", "ms", "", "HF field inactivity timeout in milliseconds"), + arg_int0("s", "sec", "", "HF field inactivity timeout in seconds"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -1110,9 +1106,9 @@ static int setCmdHfFieldTimeout(const char *Cmd) { uint32_t new_value = (uint32_t)arg; - if (g_session.hf_field_timeout != new_value) { + if (g_session.hf_field_timeout_sec != new_value) { showFieldTimeoutState(); - g_session.hf_field_timeout = new_value; + g_session.hf_field_timeout_sec = new_value; showFieldTimeoutState(); preferences_save(); } else { @@ -1120,7 +1116,7 @@ static int setCmdHfFieldTimeout(const char *Cmd) { } if (g_session.pm3_present) { - int res = SetHfFieldTimeout(g_session.hf_field_timeout, false); + int res = SetHfFieldTimeout(g_session.hf_field_timeout_sec, false); if (res != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Failed to apply HF field timeout to connected PM3"); } @@ -1581,9 +1577,9 @@ static int getCmdClientTimeout(const char *Cmd) { static int getCmdHfFieldTimeout(const char *Cmd) { CLIParserContext *ctx; - CLIParserInit(&ctx, "prefs get hf.field.timeout", + CLIParserInit(&ctx, "prefs get hf.field.timeout_sec", "Get preference of PM3 HF field inactivity timeout", - "prefs get hf.field.timeout" + "prefs get hf.field.timeout_sec" ); void *argtable[] = { arg_param_begin, @@ -1618,7 +1614,7 @@ static command_t CommandTableGet[] = { {"client.debug", getCmdDebug, AlwaysAvailable, "Get client debug level preference"}, {"client.delay", getCmdExeDelay, AlwaysAvailable, "Get client execution delay preference"}, {"client.timeout", getCmdClientTimeout, AlwaysAvailable, "Get client execution delay preference"}, - {"hf.field.timeout", getCmdHfFieldTimeout, AlwaysAvailable, "Get PM3 HF field inactivity timeout preference"}, + {"hf.field.timeout_sec", getCmdHfFieldTimeout, AlwaysAvailable, "Get PM3 HF field inactivity timeout preference"}, {"color", getCmdColor, AlwaysAvailable, "Get color support preference"}, {"savepaths", getCmdSavePaths, AlwaysAvailable, "Get file folder "}, // {"devicedebug", getCmdDeviceDebug, AlwaysAvailable, "Get device debug level"}, @@ -1636,7 +1632,7 @@ static command_t CommandTableSet[] = { {"client.debug", setCmdDebug, AlwaysAvailable, "Set client debug level"}, {"client.delay", setCmdExeDelay, AlwaysAvailable, "Set client execution delay"}, {"client.timeout", setCmdClientTimeout, AlwaysAvailable, "Set client communication timeout"}, - {"hf.field.timeout", setCmdHfFieldTimeout, AlwaysAvailable, "Set PM3 HF field inactivity timeout"}, + {"hf.field.timeout_sec", setCmdHfFieldTimeout, AlwaysAvailable, "Set PM3 HF field inactivity timeout"}, {"color", setCmdColor, AlwaysAvailable, "Set color support"}, {"emoji", setCmdEmoji, AlwaysAvailable, "Set emoji display"}, diff --git a/client/src/ui.h b/client/src/ui.h index aafb515bb..0c8298ecd 100644 --- a/client/src/ui.h +++ b/client/src/ui.h @@ -63,7 +63,7 @@ typedef struct { char *history_path; pm3_device_t *current_device; uint32_t timeout; - uint32_t hf_field_timeout; + uint32_t hf_field_timeout_sec; char *mqtt_server; char *mqtt_port; char *mqtt_topic; From 6c4b995e2100cfd4010a2988e0fc003403c4c3e0 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Tue, 31 Mar 2026 20:42:15 +0300 Subject: [PATCH 3/7] Introduce 'GetTickCountLabel' for detecting RTCC reconfigurations --- armsrc/appmain.c | 14 +++++++++----- common_arm/ticks.c | 14 ++++++++++++++ common_arm/ticks.h | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 343001eca..a7ec648c5 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -97,7 +97,6 @@ common_area_t g_common_area __attribute__((section(".commonarea"))); static int button_status = BUTTON_NO_CLICK; static bool allow_send_wtx = false; static uint32_t g_hf_field_activity_timeout_ms = 0; -static uint32_t g_last_activity_ms = 0; uint16_t g_tearoff_delay_us = 0; bool g_tearoff_enabled = false; uint8_t g_tearoff_skip = 0; @@ -1014,7 +1013,6 @@ static void PacketReceived(PacketCommandNG *packet) { uint32_t timeout_ms = 0; memcpy(&timeout_ms, packet->data.asBytes, sizeof(timeout_ms)); g_hf_field_activity_timeout_ms = timeout_ms; - g_last_activity_ms = GetTickCount(); reply_ng(CMD_SET_HF_FIELD_TIMEOUT, PM3_SUCCESS, NULL, 0); break; } @@ -3333,7 +3331,8 @@ void __attribute__((noreturn)) AppMain(void) { FpgaDownloadAndGo(FPGA_BITSTREAM_HF); StartTickCount(); - g_last_activity_ms = GetTickCount(); + uint32_t last_activity_tick = GetTickCount(); + uint32_t last_activity_label = GetTickCountLabel(); #ifdef WITH_LCD LCDInit(); @@ -3397,14 +3396,19 @@ void __attribute__((noreturn)) AppMain(void) { int ret = receive_ng(&rx); if (ret == PM3_SUCCESS) { PacketReceived(&rx); - g_last_activity_ms = GetTickCount(); + last_activity_label = GetTickCountLabel(); + last_activity_tick = GetTickCount(); } else if (ret != PM3_ENODATA) { Dbprintf("Error in frame reception: %d %s", ret, (ret == PM3_EIO) ? "PM3_EIO" : ""); // TODO if error, shall we resync ? } if (g_hf_field_activity_timeout_ms > 0 && g_hf_field_timeout_active) { - if (GetTickCountDelta(g_last_activity_ms) >= g_hf_field_activity_timeout_ms) { + uint32_t tickcount_label = GetTickCountLabel(); + if (tickcount_label != last_activity_label) { + last_activity_label = tickcount_label; + last_activity_tick = GetTickCount(); + } else if (GetTickCountDelta(last_activity_tick) >= g_hf_field_activity_timeout_ms) { if (g_dbglevel >= DBG_INFO) { Dbprintf("HF field auto-off: inactivity timeout (%u ms)", g_hf_field_activity_timeout_ms); } diff --git a/common_arm/ticks.c b/common_arm/ticks.c index 73182a11c..e609dfd83 100644 --- a/common_arm/ticks.c +++ b/common_arm/ticks.c @@ -97,7 +97,13 @@ void SpinDelay(int ms) { // SpinDelay(1000); // ti = GetTickCount() - ti; // Dbprintf("timer(1s): %d t=%d", ti, GetTickCount()); +// Increments whenever StartTickCount() reconfigures/resets RTTC. +// Callers can use this to detect that previously saved tick deltas are no longer valid. +static uint32_t g_tickcount_label = 0; + void StartTickCount(void) { + g_tickcount_label++; + // This timer is based on the slow clock. The slow clock frequency is between 22kHz and 40kHz. // We can determine the actual slow clock frequency by looking at the Main Clock Frequency Register. while ((AT91C_BASE_PMC->PMC_MCFR & AT91C_CKGR_MAINRDY) == 0); // Wait for MAINF value to become available... @@ -122,6 +128,14 @@ uint32_t RAMFUNC GetTickCountDelta(uint32_t start_ticks) { return (UINT32_MAX - start_ticks) + stop_ticks; } +/* +* Get current RTTC counter label. +* If counter config changes between calls, the value is incremented. +*/ +uint32_t GetTickCountLabel(void) { + return g_tickcount_label; +} + // ------------------------------------------------------------------------- // Timer for iso14443 commands. Uses ssp_clk from FPGA // ------------------------------------------------------------------------- diff --git a/common_arm/ticks.h b/common_arm/ticks.h index 67d8dceed..4ec730131 100644 --- a/common_arm/ticks.h +++ b/common_arm/ticks.h @@ -47,6 +47,7 @@ void SpinDelayUsPrecision(int us); // precision 0.6us , running for 43ms before void StartTickCount(void); uint32_t RAMFUNC GetTickCount(void); uint32_t RAMFUNC GetTickCountDelta(uint32_t start_ticks); +uint32_t GetTickCountLabel(void); void ResetUSClock(void); void SpinDelayCountUs(uint32_t us); From 753eb0737aef7d875bc39b593697a633a46bceaf Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Tue, 31 Mar 2026 20:49:03 +0300 Subject: [PATCH 4/7] Display a message when automatic field timeout is turned on --- armsrc/appmain.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index a7ec648c5..e00165d27 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -3409,10 +3409,8 @@ void __attribute__((noreturn)) AppMain(void) { last_activity_label = tickcount_label; last_activity_tick = GetTickCount(); } else if (GetTickCountDelta(last_activity_tick) >= g_hf_field_activity_timeout_ms) { - if (g_dbglevel >= DBG_INFO) { - Dbprintf("HF field auto-off: inactivity timeout (%u ms)", g_hf_field_activity_timeout_ms); - } hf_field_off(); + Dbprintf("HF field auto-off: inactivity timeout (%u ms). To disable, use 'prefs set hf.field.timeout_sec --sec 0'", g_hf_field_activity_timeout_ms); } } From 2e4d63dae6d2dc5da27002748d30d5c5fb513a7f Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Tue, 31 Mar 2026 20:56:06 +0300 Subject: [PATCH 5/7] Add docs on use of clocks for field timeout --- doc/clocks.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/clocks.md b/doc/clocks.md index 9e0384d23..2db11097b 100644 --- a/doc/clocks.md +++ b/doc/clocks.md @@ -118,7 +118,8 @@ Characteristics: * 1 kHz, 32b (49 days), if used with 16b: 65s * Configured at boot (or TIA) with `StartTickCount()` -* Time events with `GetTickCount()`/`GetTickCountDeltaDelta()`, see example +* Time events with `GetTickCount()`/`GetTickCountDelta()`, see example +* Each change in configuration of the clock increments the label value, retreivable through `GetTickCountLabel()` * Coarse, based on the ~32kHz RC slow clock with some adjustment factor computed by TIA * Maybe 2.5% error, can increase if temperature conditions change and no TIA is recomputed * If TimingIntervalAcquisition() is called later, StartTickCount() is called again and RTC is reset @@ -131,12 +132,24 @@ uint32_t ti = GetTickCount(); uint32_t delta = GetTickCountDelta(ti); ``` +If `StartTickCount()` may run between two reads (e.g. via TIA), pair tick with a label: + +``` +uint32_t label = GetTickCountLabel(); +uint32_t ti = GetTickCount(); +...do stuff... +if (label == GetTickCountLabel()) { + uint32_t delta = GetTickCountDelta(ti); +} +``` + Current usages: * cheap random for nonces, e.g. `prng_successor(GetTickCount(), 32)` * rough timing of some operations, only for informative purposes * timeouts * USB connection speed measure +* Optional HF field inactivity timeout ## Occasional PWM timer ^[Top](#top) From 078cee80d8e04b05cea7e685fa358032ef9aea3d Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 31 Mar 2026 21:19:06 +0200 Subject: [PATCH 6/7] typo --- doc/clocks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/clocks.md b/doc/clocks.md index 2db11097b..9a287c019 100644 --- a/doc/clocks.md +++ b/doc/clocks.md @@ -119,7 +119,7 @@ Characteristics: * 1 kHz, 32b (49 days), if used with 16b: 65s * Configured at boot (or TIA) with `StartTickCount()` * Time events with `GetTickCount()`/`GetTickCountDelta()`, see example -* Each change in configuration of the clock increments the label value, retreivable through `GetTickCountLabel()` +* Each change in configuration of the clock increments the label value, retrievable through `GetTickCountLabel()` * Coarse, based on the ~32kHz RC slow clock with some adjustment factor computed by TIA * Maybe 2.5% error, can increase if temperature conditions change and no TIA is recomputed * If TimingIntervalAcquisition() is called later, StartTickCount() is called again and RTC is reset From dfb6663990d88b0c564de054a71d03f100e0bacb Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 31 Mar 2026 21:21:41 +0200 Subject: [PATCH 7/7] commands list helper: compute dynamically column width if > 16 --- client/src/cmdparser.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/src/cmdparser.c b/client/src/cmdparser.c index 66ac21d79..5cfbf58b8 100644 --- a/client/src/cmdparser.c +++ b/client/src/cmdparser.c @@ -212,14 +212,26 @@ void CmdsHelp(const command_t Commands[]) { PrintAndLogEx(NORMAL, ""); int i = 0; + size_t max_name_len = 16; // minimum width for command name column + while (Commands[i].Name) { + if (Commands[i].IsAvailable()) { + size_t name_len = strlen(Commands[i].Name); + if (name_len > max_name_len) { + max_name_len = name_len; + } + } + ++i; + } + + i = 0; while (Commands[i].Name) { if (Commands[i].IsAvailable()) { uint8_t old_printAndLog = g_printAndLog; g_printAndLog &= PRINTANDLOG_PRINT; if (Commands[i].Name[0] == '-' || Commands[i].Name[0] == ' ') { - PrintAndLogEx(NORMAL, "%-16s %s", Commands[i].Name, Commands[i].Help); + PrintAndLogEx(NORMAL, "%-*s %s", (int)max_name_len, Commands[i].Name, Commands[i].Help); } else { - PrintAndLogEx(NORMAL, _GREEN_("%-16s")" %s", Commands[i].Name, Commands[i].Help); + PrintAndLogEx(NORMAL, _GREEN_("%-*s") " %s", (int)max_name_len, Commands[i].Name, Commands[i].Help); } g_printAndLog = old_printAndLog; }