diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e070f3..c5ba688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added support for long-press of buttons (@nemanjan00) - Changed `hw slot delete`, now it can always delete from slot. (@augustozanellato) - Refactor CI pipeline. (@augustozanellato) - Added offline copy EM card uid for btnpress.(@nemanjan00) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 9699c63..396d17f 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -146,6 +146,29 @@ data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t st return data_frame_make(cmd, status, 0, NULL); } +data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t button_press_config; + if (length == 1 && is_settings_button_type_valid(data[0])) { + button_press_config = settings_get_long_button_press_config(data[0]); + status = STATUS_DEVICE_SUCCESS; + } else { + length = 0; + status = STATUS_PAR_ERR; + } + return data_frame_make(cmd, status, length, (uint8_t *)(&button_press_config)); +} + +data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 2 && is_settings_button_type_valid(data[0])) { + settings_set_long_button_press_config(data[0], data[1]); + status = STATUS_DEVICE_SUCCESS; + } else { + length = 0; + status = STATUS_PAR_ERR; + } + return data_frame_make(cmd, status, 0, NULL); +} + #if defined(PROJECT_CHAMELEON_ULTRA) data_frame_tx_t *cmd_processor_14a_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { @@ -783,6 +806,8 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_GET_BATTERY_INFO, NULL, cmd_processor_get_battery_info, NULL }, { DATA_CMD_GET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_button_press_config, NULL }, { DATA_CMD_SET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_button_press_config, NULL }, + { DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_long_button_press_config, NULL }, + { DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_long_button_press_config, NULL }, #if defined(PROJECT_CHAMELEON_ULTRA) diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index ec240b0..fa56dbf 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "nordic_common.h" #include "nrf.h" @@ -41,9 +42,17 @@ NRF_LOG_MODULE_REGISTER(); // Defining soft timers APP_TIMER_DEF(m_button_check_timer); // Timer for button debounce + +static uint32_t m_last_btn_press = 0; + +static bool m_is_btn_long_press = false; + static bool m_is_b_btn_press = false; static bool m_is_a_btn_press = false; +static bool m_is_b_btn_release = false; +static bool m_is_a_btn_release = false; + // cpu reset reason static uint32_t m_reset_source; static uint32_t m_gpregret_val; @@ -148,16 +157,51 @@ static void timer_button_event_handle(void *arg) { // Check here if the current GPIO is at the pressed level if (nrf_gpio_pin_read(pin) == 1) { if (pin == BUTTON_1) { - // If button is disable, we can didn't dispatch key event. + // If button is disabled, we can't dispatch key event. if (settings_get_button_press_config('b') != SettingsButtonDisable) { - NRF_LOG_INFO("BUTTON_LEFT"); // Button B? + NRF_LOG_INFO("BUTTON_B_PRESS"); m_is_b_btn_press = true; + m_last_btn_press = app_timer_cnt_get(); } } if (pin == BUTTON_2) { if (settings_get_button_press_config('a') != SettingsButtonDisable) { - NRF_LOG_INFO("BUTTON_RIGHT"); // Button A? + NRF_LOG_INFO("BUTTON_A_PRESS"); m_is_a_btn_press = true; + m_last_btn_press = app_timer_cnt_get(); + } + } + } + + if (nrf_gpio_pin_read(pin) == 0) { + uint32_t now = app_timer_cnt_get(); + uint32_t ticks = app_timer_cnt_diff_compute(now, m_last_btn_press); + + bool is_long_press = ticks > APP_TIMER_TICKS(1000); + + if (pin == BUTTON_1 && m_is_b_btn_press == true) { + // If button is disabled, we can't dispatch key event. + if (settings_get_button_press_config('b') != SettingsButtonDisable) { + m_is_b_btn_release = true; + m_is_b_btn_press = false; + if (!is_long_press) { + NRF_LOG_INFO("BUTTON_B_RELEASE_SHORT"); + } else { + NRF_LOG_INFO("BUTTON_B_RELEASE_LONG"); + } + m_is_btn_long_press = is_long_press; + } + } + if (pin == BUTTON_2 && m_is_a_btn_press == true) { + if (settings_get_button_press_config('a') != SettingsButtonDisable) { + m_is_a_btn_release = true; + m_is_a_btn_press = false; + if (!is_long_press) { + NRF_LOG_INFO("BUTTON_A_RELEASE_SHORT"); + } else { + NRF_LOG_INFO("BUTTON_A_RELEASE_LONG"); + } + m_is_btn_long_press = is_long_press; } } } @@ -173,7 +217,7 @@ static void button_init(void) { APP_ERROR_CHECK(err_code); // Configure SENSE mode, select false for sense configuration - nrf_drv_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(false); + nrf_drv_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(false); in_config.pull = NRF_GPIO_PIN_PULLDOWN; // Pulldown // Configure key binding POTR @@ -635,14 +679,22 @@ static void run_button_function_by_settings(settings_button_function_t sbf) { extern bool g_usb_led_marquee_enable; static void button_press_process(void) { // Make sure that one of the AB buttons has a click event - if (m_is_b_btn_press || m_is_a_btn_press) { - if (m_is_a_btn_press) { - run_button_function_by_settings(settings_get_button_press_config('a')); - m_is_a_btn_press = false; + if (m_is_b_btn_release || m_is_a_btn_release) { + if (m_is_a_btn_release) { + if(!m_is_btn_long_press) { + run_button_function_by_settings(settings_get_button_press_config('a')); + } else { + run_button_function_by_settings(settings_get_long_button_press_config('a')); + } + m_is_a_btn_release = false; } - if (m_is_b_btn_press) { - run_button_function_by_settings(settings_get_button_press_config('b')); - m_is_b_btn_press = false; + if (m_is_b_btn_release) { + if(!m_is_btn_long_press) { + run_button_function_by_settings(settings_get_button_press_config('b')); + } else { + run_button_function_by_settings(settings_get_long_button_press_config('b')); + } + m_is_b_btn_release = false; } // Disable led marquee for usb at button pressed. g_usb_led_marquee_enable = false; diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 98c9681..2737beb 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -33,6 +33,8 @@ #define DATA_CMD_GET_BATTERY_INFO (1025) #define DATA_CMD_GET_BUTTON_PRESS_CONFIG (1026) #define DATA_CMD_SET_BUTTON_PRESS_CONFIG (1027) +#define DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG (1028) +#define DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG (1029) // // ****************************************************************** diff --git a/firmware/application/src/settings.c b/firmware/application/src/settings.c index 18bbe15..5879f68 100644 --- a/firmware/application/src/settings.c +++ b/firmware/application/src/settings.c @@ -51,6 +51,10 @@ void settings_migrate(void) { settings_update_version_for_config(); break; + case 2: + config.button_a_long_press = SettingsButtonCloneIcUid; + config.button_b_long_press = SettingsButtonCloneIcUid; + /* * When needed migrations can be implemented like this: * @@ -167,6 +171,31 @@ uint8_t settings_get_button_press_config(char which) { return SettingsButtonDisable; } +/** + * @brief Get the long button press config + * + * @param which 'a' or 'b' + * @return uint8_t @link{ settings_button_function_t } + */ +uint8_t settings_get_long_button_press_config(char which) { + switch (which) { + case 'a': + case 'A': + return config.button_a_long_press; + + case 'b': + case 'B': + return config.button_b_long_press; + + default: + // can't to here. + APP_ERROR_CHECK_BOOL(false); + break; + } + // can't to here. + return SettingsButtonDisable; +} + /** * @brief Set the button press config * @@ -191,3 +220,28 @@ void settings_set_button_press_config(char which, uint8_t value) { break; } } + +/** + * @brief Set the long button press config + * + * @param which 'a' or 'b' + * @param value @link{ settings_button_function_t } + */ +void settings_set_long_button_press_config(char which, uint8_t value) { + switch (which) { + case 'a': + case 'A': + config.button_a_long_press = value; + break; + + case 'b': + case 'B': + config.button_b_long_press = value; + break; + + default: + // can't to here. + APP_ERROR_CHECK_BOOL(false); + break; + } +} diff --git a/firmware/application/src/settings.h b/firmware/application/src/settings.h index d1775f2..1713374 100644 --- a/firmware/application/src/settings.h +++ b/firmware/application/src/settings.h @@ -5,7 +5,7 @@ #include "utils.h" -#define SETTINGS_CURRENT_VERSION 2 +#define SETTINGS_CURRENT_VERSION 3 typedef enum { SettingsAnimationModeFull = 0U, @@ -36,6 +36,10 @@ typedef struct ALIGN_U32 { uint8_t button_a_press : 4; uint8_t button_b_press : 4; + // 1 byte + uint8_t button_a_long_press : 4; + uint8_t button_b_long_press : 4; + // 8 byte uint32_t reserved1; uint32_t reserved2; @@ -48,7 +52,9 @@ uint8_t settings_save_config(void); uint8_t settings_get_animation_config(void); void settings_set_animation_config(uint8_t value); uint8_t settings_get_button_press_config(char which); +uint8_t settings_get_long_button_press_config(char which); void settings_set_button_press_config(char which, uint8_t value); +void settings_set_long_button_press_config(char which, uint8_t value); bool is_settings_button_type_valid(char type); #endif diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 9f99ec9..a18690c 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1318,9 +1318,13 @@ class HWButtonSettingsGet(DeviceRequiredUnit): print("") for button in button_list: resp = self.cmd.get_button_press_fun(button) + resp_long = self.cmd.get_long_button_press_fun(button) button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp.data[0]) - print(f" - {colorama.Fore.GREEN}{button}{colorama.Style.RESET_ALL}: {button_fn}") + button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long.data[0]) + print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}short{colorama.Style.RESET_ALL}: {button_fn}") print(f" usage: {button_fn.usage()}") + print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}long {colorama.Style.RESET_ALL}: {button_long_fn}") + print(f" usage: {button_long_fn.usage()}") print("") print(" - Successfully get button function from settings") @@ -1330,6 +1334,8 @@ class HWButtonSettingsSet(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: parser = ArgumentParserNoExit() + parser.add_argument('-l', '--long', action='store_true', default=False, + help="set keybinding for long-press") parser.add_argument('-b', type=str, required=True, help="Change the function of the pressed button(?).", choices=chameleon_cmd.ButtonType.list_str()) @@ -1344,5 +1350,9 @@ class HWButtonSettingsSet(DeviceRequiredUnit): def on_exec(self, args: argparse.Namespace): button = chameleon_cmd.ButtonType.from_str(args.b) function = chameleon_cmd.ButtonPressFunction.from_int(args.f) - self.cmd.set_button_press_fun(button, function) + long = args.long == True + if long: + self.cmd.set_long_button_press_fun(button, function) + else: + self.cmd.set_button_press_fun(button, function) print(" - Successfully set button function to settings") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 33f4e39..a3ef8c7 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -42,6 +42,9 @@ DATA_CMD_GET_BATTERY_INFO = 1025 DATA_CMD_GET_BUTTON_PRESS_CONFIG = 1026 DATA_CMD_SET_BUTTON_PRESS_CONFIG = 1027 +DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG = 1028 +DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG = 1029 + DATA_CMD_SCAN_14A_TAG = 2000 DATA_CMD_MF1_SUPPORT_DETECT = 2001 DATA_CMD_MF1_NT_LEVEL_DETECT = 2002 @@ -769,6 +772,24 @@ class ChameleonCMD: bytearray([button, function]) ) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_long_button_press_fun(self, button: ButtonType): + """ + Get config of button press function + """ + return self.device.send_cmd_sync(DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, 0x00, bytearray([button])) + + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def set_long_button_press_fun(self, button: ButtonType, function: ButtonPressFunction): + """ + Set config of button press function + """ + return self.device.send_cmd_sync( + DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, + 0x00, + bytearray([button, function]) + ) + if __name__ == '__main__': # connect to chameleon dev = chameleon_com.ChameleonCom()