mirror of
https://github.com/whowechina/aic_pico.git
synced 2026-09-22 22:48:04 +03:00
Auto PIN-entry feature
This commit is contained in:
Binary file not shown.
@@ -12,7 +12,7 @@ function(make_firmware board board_def)
|
||||
endif()
|
||||
|
||||
add_executable(${board}
|
||||
main.c save.c config.c commands.c light.c keypad.c
|
||||
main.c save.c config.c commands.c light.c keypad.c cardio.c
|
||||
cst816t.c st7789.c gui.c gfx.c rle.c
|
||||
cli.c usb_descriptors.c)
|
||||
target_compile_definitions(${board} PUBLIC ${board_def})
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Cardio processing and auto PIN-entry
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "tusb.h"
|
||||
#include "usb_descriptors.h"
|
||||
|
||||
#include "board_defs.h"
|
||||
|
||||
#include "nfc.h"
|
||||
#include "gui.h"
|
||||
#include "light.h"
|
||||
|
||||
#define LAST_CARD_TIMEOUT_US 30000000
|
||||
|
||||
static struct {
|
||||
uint8_t current[9];
|
||||
uint8_t reported[9];
|
||||
uint64_t report_time;
|
||||
} hid_cardio;
|
||||
|
||||
static struct {
|
||||
uint8_t uid_len;
|
||||
uint8_t uid[8];
|
||||
uint64_t time;
|
||||
} last_card;
|
||||
|
||||
static struct {
|
||||
bool active;
|
||||
uint8_t seq_len; // not pin length
|
||||
uint8_t index;
|
||||
struct {
|
||||
uint8_t key;
|
||||
uint8_t duration; // in cycles, most cases 1 cycle = 1ms
|
||||
} seq[32];
|
||||
uint64_t pin_time;
|
||||
} autopin_ctx;
|
||||
|
||||
void cardio_report_cardio()
|
||||
{
|
||||
if (!tud_hid_ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t now = time_us_64();
|
||||
|
||||
if ((memcmp(hid_cardio.current, hid_cardio.reported, 9) != 0) &&
|
||||
(now - hid_cardio.report_time > 1000000)) {
|
||||
|
||||
tud_hid_n_report(0x00, hid_cardio.current[0], hid_cardio.current + 1, 8);
|
||||
memcpy(hid_cardio.reported, hid_cardio.current, 9);
|
||||
hid_cardio.report_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
static void start_autopin(const char *pin, uint8_t delay)
|
||||
{
|
||||
const char *keymap = KEYPAD_NKRO_MAP;
|
||||
autopin_ctx.active = true;
|
||||
autopin_ctx.index = 0;
|
||||
autopin_ctx.seq_len = strlen(pin);
|
||||
autopin_ctx.pin_time = time_us_64() + delay * 1000000;
|
||||
|
||||
for (int i = 0; i < autopin_ctx.seq_len; i++) {
|
||||
int key = pin[i] - '0';
|
||||
int code = (key > 0) ? keymap[key - 1] : keymap[9];
|
||||
autopin_ctx.seq[i].key = code;
|
||||
autopin_ctx.seq[i].duration = 150;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_autopin()
|
||||
{
|
||||
if (!aic_cfg->autopin.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (aic_cfg->autopin.entries[i].uidlen != last_card.uid_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (memcmp(aic_cfg->autopin.entries[i].uid, last_card.uid, last_card.uid_len) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(" (Auto PIN-entry)");
|
||||
start_autopin(aic_cfg->autopin.entries[i].pin, aic_cfg->autopin.entries[i].delay);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int cardio_get_pin_key()
|
||||
{
|
||||
if (!aic_cfg->autopin.enabled || !autopin_ctx.active) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (time_us_64() < autopin_ctx.pin_time) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (autopin_ctx.index >= autopin_ctx.seq_len) {
|
||||
autopin_ctx.active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int key = autopin_ctx.seq[autopin_ctx.index].key;
|
||||
if (autopin_ctx.seq[autopin_ctx.index].duration > 0) {
|
||||
autopin_ctx.seq[autopin_ctx.index].duration--;
|
||||
} else {
|
||||
autopin_ctx.index++;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static void update_cardio(nfc_card_t *card)
|
||||
{
|
||||
switch (card->card_type) {
|
||||
case NFC_CARD_MIFARE:
|
||||
hid_cardio.current[0] = REPORT_ID_EAMU;
|
||||
hid_cardio.current[1] = 0xe0;
|
||||
hid_cardio.current[2] = 0x04;
|
||||
if (card->len == 4) {
|
||||
memcpy(hid_cardio.current + 3, card->uid, 4);
|
||||
memcpy(hid_cardio.current + 7, card->uid, 2);
|
||||
} else if (card->len == 7) {
|
||||
memcpy(hid_cardio.current + 3, card->uid + 1, 6);
|
||||
}
|
||||
break;
|
||||
case NFC_CARD_FELICA:
|
||||
hid_cardio.current[0] = REPORT_ID_FELICA;
|
||||
memcpy(hid_cardio.current + 1, card->uid, 8);
|
||||
break;
|
||||
case NFC_CARD_VICINITY:
|
||||
hid_cardio.current[0] = REPORT_ID_EAMU;
|
||||
memcpy(hid_cardio.current + 1, card->uid, 8);
|
||||
break;
|
||||
default:
|
||||
memset(hid_cardio.current, 0, 9);
|
||||
return;
|
||||
}
|
||||
|
||||
last_card.time = time_us_64();
|
||||
last_card.uid_len = card->len;
|
||||
memcpy(last_card.uid, card->uid, 8);
|
||||
|
||||
gui_report_card_id(hid_cardio.current + 1, 8, true);
|
||||
printf(" -> CardIO ");
|
||||
for (int i = 1; i < 9; i++) {
|
||||
printf("%02X", hid_cardio.current[i]);
|
||||
}
|
||||
|
||||
check_autopin();
|
||||
}
|
||||
|
||||
void cardio_run(bool hid_light)
|
||||
{
|
||||
static nfc_card_t old_card = { 0 };
|
||||
|
||||
nfc_rf_field(true);
|
||||
nfc_card_t card = nfc_detect_card();
|
||||
if (card.card_type != NFC_CARD_NONE) {
|
||||
nfc_identify_last_card();
|
||||
gui_report_card_id(card.uid, card.len, false);
|
||||
}
|
||||
nfc_rf_field(false);
|
||||
|
||||
if (memcmp(&old_card, &card, sizeof(old_card)) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
old_card = card;
|
||||
|
||||
if (!hid_light) {
|
||||
if (card.card_type != NFC_CARD_NONE) {
|
||||
light_rainbow(30, 0, aic_cfg->light.level_active);
|
||||
} else {
|
||||
light_rainbow(1, 3000, aic_cfg->light.level_idle);
|
||||
}
|
||||
}
|
||||
|
||||
display_card(&card);
|
||||
update_cardio(&card);
|
||||
}
|
||||
|
||||
void cardio_clear()
|
||||
{
|
||||
memset(hid_cardio.current, 0, 9);
|
||||
memset(&autopin_ctx, 0, sizeof(autopin_ctx));
|
||||
}
|
||||
|
||||
bool cardio_get_last(uint8_t uid[8], uint8_t *len)
|
||||
{
|
||||
if (time_us_64() - last_card.time > LAST_CARD_TIMEOUT_US) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (last_card.uid_len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*len = last_card.uid_len;
|
||||
memcpy(uid, last_card.uid, last_card.uid_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Cardio processing and auto PIN-entry
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef CARDIO_H
|
||||
#define CARDIO_H
|
||||
|
||||
void cardio_run(bool hid_light);
|
||||
void cardio_clear();
|
||||
void cardio_report_cardio();
|
||||
bool cardio_get_last(uint8_t uid[8], uint8_t *len);
|
||||
int cardio_get_pin_key();
|
||||
|
||||
#endif
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "bana.h"
|
||||
#include "nfc.h"
|
||||
|
||||
#include "cardio.h"
|
||||
|
||||
static int fps[2];
|
||||
void fps_count(int core)
|
||||
{
|
||||
@@ -71,6 +73,30 @@ static void display_reader()
|
||||
}
|
||||
}
|
||||
|
||||
static void disp_list()
|
||||
{
|
||||
for (int i = 0; i < 4; i++) {
|
||||
printf(" SLOT %d:", i);
|
||||
if (aic_cfg->autopin.entries[i].uidlen == 0) {
|
||||
printf(" Empty\n");
|
||||
continue;
|
||||
}
|
||||
printf(" UID: ");
|
||||
for (int j = 0; j < aic_cfg->autopin.entries[i].uidlen; j++) {
|
||||
printf("%02x", aic_cfg->autopin.entries[i].uid[j]);
|
||||
}
|
||||
printf(" PIN: %.15s", aic_cfg->autopin.entries[i].pin);
|
||||
printf(" Delay: %ds\n", aic_cfg->autopin.entries[i].delay);
|
||||
}
|
||||
}
|
||||
|
||||
static void display_autopin()
|
||||
{
|
||||
printf("[AUTO PIN-Entry]\n");
|
||||
printf(" Status: %s\n", aic_cfg->autopin.enabled ? "ON" : "OFF");
|
||||
disp_list();
|
||||
}
|
||||
|
||||
static void display_warning()
|
||||
{
|
||||
if (keypad_is_stuck()) {
|
||||
@@ -84,6 +110,7 @@ static void handle_display()
|
||||
display_light();
|
||||
display_lcd();
|
||||
display_reader();
|
||||
display_autopin();
|
||||
display_warning();
|
||||
}
|
||||
|
||||
@@ -253,6 +280,137 @@ static void handle_lcd(int argc, char *argv[])
|
||||
display_lcd();
|
||||
}
|
||||
|
||||
static void autopin_onoff(bool on)
|
||||
{
|
||||
aic_cfg->autopin.enabled = on;
|
||||
config_changed();
|
||||
printf("Auto Pin-entry: %s\n", on ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
static void autopin_delete(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: autopin delete <SLOT>\n"
|
||||
" SLOT: [0..3], all";
|
||||
if (argc != 1) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *slots[] = { "0", "1", "2", "3", "all" };
|
||||
int match = cli_match_prefix(slots, 5, argv[0]);
|
||||
if (match < 0) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
if (match == 4) {
|
||||
memset(&aic_cfg->autopin.entries, 0, sizeof(aic_cfg->autopin.entries));
|
||||
printf("All slots cleared.\n");
|
||||
} else {
|
||||
memset(&aic_cfg->autopin.entries[match], 0,
|
||||
sizeof(aic_cfg->autopin.entries[match]));
|
||||
printf("Slot %d cleared.\n", match);
|
||||
}
|
||||
config_changed();
|
||||
}
|
||||
|
||||
static int find_slot(const uint8_t uid[8], uint8_t uidlen)
|
||||
{
|
||||
int slot = -1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if ((aic_cfg->autopin.entries[i].uidlen == uidlen) &&
|
||||
(memcmp(aic_cfg->autopin.entries[i].uid, uid, uidlen) == 0)) {
|
||||
return i;
|
||||
}
|
||||
if ((slot < 0) && (aic_cfg->autopin.entries[i].uidlen == 0)) {
|
||||
slot = i;
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
static void autopin_add(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: autopin add <PIN> <DELAY>\n"
|
||||
" PIN: 4 digits\n"
|
||||
" DELAY: [0..99] seconds\n";
|
||||
if (argc != 2) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t uid[8];
|
||||
uint8_t uidlen;
|
||||
if (!cardio_get_last(uid, &uidlen)) {
|
||||
printf("No card detected.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int slot = find_slot(uid, uidlen);
|
||||
if (slot < 0) {
|
||||
printf("No empty slots available.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int delay = cli_extract_non_neg_int(argv[1], 0);
|
||||
if (delay < 0 || delay > 99) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(argv[0]) != 4) {
|
||||
printf("PIN must be 4 digits.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (isdigit((int)argv[0][i]) == 0) {
|
||||
printf("PIN must be 4 digits.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
strcpy(aic_cfg->autopin.entries[slot].pin, argv[0]);
|
||||
aic_cfg->autopin.entries[slot].delay = delay;
|
||||
|
||||
config_changed();
|
||||
printf("Added to slot %d.\n", slot);
|
||||
disp_list();
|
||||
}
|
||||
|
||||
static void handle_autopin(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: autopin <on|off>\n"
|
||||
" autopin list\n"
|
||||
" autopin delete <SLOT>\n"
|
||||
" autopin add <PIN> <DELAY>\n";
|
||||
if (argc < 1) {
|
||||
printf("%s", usage);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *commands[] = { "on", "off", "list", "delete", "add" };
|
||||
int match = cli_match_prefix(commands, 5, argv[0]);
|
||||
if ((match == 0) || (match == 1)) {
|
||||
if (argc == 1) {
|
||||
autopin_onoff(match == 0);
|
||||
return;
|
||||
}
|
||||
} else if (match == 2) {
|
||||
if (argc == 1) {
|
||||
disp_list();
|
||||
return;
|
||||
}
|
||||
} else if (match == 3) {
|
||||
autopin_delete(argc - 1, argv + 1);
|
||||
return;
|
||||
} else if (match == 4) {
|
||||
autopin_add(argc - 1, argv + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s", usage);
|
||||
}
|
||||
|
||||
static void handle_pn5180_tweak(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: pn5180_tweak <on|off>\n";
|
||||
@@ -290,6 +448,7 @@ void commands_init()
|
||||
cli_register("light", handle_light, "Turn on/off lights.");
|
||||
cli_register("level", handle_level, "Set light level.");
|
||||
cli_register("lcd", handle_lcd, "Touch LCD settings.");
|
||||
cli_register("autopin", handle_autopin, "Auto pin-entry.");
|
||||
cli_register("pn5180_tweak", handle_pn5180_tweak, "PN5180 TX tweak.");
|
||||
cli_register("debug", handle_debug, "Toggle debug.");
|
||||
}
|
||||
|
||||
@@ -27,7 +27,17 @@ typedef struct __attribute__((packed)) {
|
||||
} lcd;
|
||||
struct {
|
||||
bool pn5180_tx;
|
||||
uint8_t reserved[7];
|
||||
} tweak;
|
||||
struct {
|
||||
struct {
|
||||
uint8_t uidlen;
|
||||
uint8_t delay;
|
||||
uint8_t uid[8];
|
||||
char pin[16];
|
||||
} entries[4];
|
||||
bool enabled;
|
||||
} autopin;
|
||||
uint32_t reserved;
|
||||
} aic_cfg_t;
|
||||
|
||||
|
||||
+25
-103
@@ -33,35 +33,13 @@
|
||||
#include "config.h"
|
||||
#include "cli.h"
|
||||
#include "commands.h"
|
||||
#include "cardio.h"
|
||||
#include "light.h"
|
||||
#include "keypad.h"
|
||||
#include "gui.h"
|
||||
|
||||
#define DEBUG(...) if (aic_runtime.debug) printf(__VA_ARGS__)
|
||||
|
||||
static struct {
|
||||
uint8_t current[9];
|
||||
uint8_t reported[9];
|
||||
uint64_t report_time;
|
||||
} hid_cardio;
|
||||
|
||||
void report_hid_cardio()
|
||||
{
|
||||
if (!tud_hid_ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t now = time_us_64();
|
||||
|
||||
if ((memcmp(hid_cardio.current, hid_cardio.reported, 9) != 0) &&
|
||||
(now - hid_cardio.report_time > 1000000)) {
|
||||
|
||||
tud_hid_n_report(0x00, hid_cardio.current[0], hid_cardio.current + 1, 8);
|
||||
memcpy(hid_cardio.reported, hid_cardio.current, 9);
|
||||
hid_cardio.report_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t modifier;
|
||||
uint8_t keymap[15];
|
||||
@@ -69,6 +47,11 @@ struct __attribute__((packed)) {
|
||||
|
||||
static const char keymap[12] = KEYPAD_NKRO_MAP;
|
||||
|
||||
static inline void set_nkro_bit(uint8_t code)
|
||||
{
|
||||
hid_nkro.keymap[code / 8] |= (1 << (code % 8));
|
||||
}
|
||||
|
||||
void report_hid_key()
|
||||
{
|
||||
if (!tud_hid_ready()) {
|
||||
@@ -77,28 +60,30 @@ void report_hid_key()
|
||||
|
||||
uint16_t keys = aic_runtime.touch ? gui_keypad_read() : keypad_read();
|
||||
|
||||
memset(&hid_nkro, 0, sizeof(hid_nkro));
|
||||
for (int i = 0; i < keypad_key_num(); i++) {
|
||||
uint8_t code = keymap[i];
|
||||
uint8_t byte = code / 8;
|
||||
uint8_t bit = code % 8;
|
||||
if (keys & (1 << i)) {
|
||||
hid_nkro.keymap[byte] |= (1 << bit);
|
||||
} else {
|
||||
hid_nkro.keymap[byte] &= ~(1 << bit);
|
||||
if (keys & (1 << i)) {
|
||||
set_nkro_bit(keymap[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int auto_pin_key = cardio_get_pin_key();
|
||||
if (auto_pin_key > 0) {
|
||||
set_nkro_bit(auto_pin_key);
|
||||
}
|
||||
|
||||
tud_hid_n_report(1, 0, &hid_nkro, sizeof(hid_nkro));
|
||||
}
|
||||
|
||||
void report_usb_hid()
|
||||
{
|
||||
report_hid_cardio();
|
||||
cardio_report_cardio();
|
||||
report_hid_key();
|
||||
}
|
||||
|
||||
static uint64_t last_hid_time = 0;
|
||||
|
||||
static bool hid_is_active()
|
||||
static bool hid_light_is_active()
|
||||
{
|
||||
if (last_hid_time == 0) {
|
||||
return false;
|
||||
@@ -114,7 +99,7 @@ static bool reader_is_active()
|
||||
static void light_mode_update()
|
||||
{
|
||||
static bool was_cardio = true;
|
||||
bool cardio = !reader_is_active() && !hid_is_active();
|
||||
bool cardio = !reader_is_active() && !hid_light_is_active();
|
||||
static uint8_t last_level;
|
||||
bool level_changed = (last_level != aic_cfg->light.level_idle);
|
||||
|
||||
@@ -161,75 +146,6 @@ void card_name_update_cb(nfc_card_name card_name)
|
||||
gui_report_card_name(card_name);
|
||||
}
|
||||
|
||||
static void update_cardio(nfc_card_t *card)
|
||||
{
|
||||
switch (card->card_type) {
|
||||
case NFC_CARD_MIFARE:
|
||||
hid_cardio.current[0] = REPORT_ID_EAMU;
|
||||
hid_cardio.current[1] = 0xe0;
|
||||
hid_cardio.current[2] = 0x04;
|
||||
if (card->len == 4) {
|
||||
memcpy(hid_cardio.current + 3, card->uid, 4);
|
||||
memcpy(hid_cardio.current + 7, card->uid, 2);
|
||||
} else if (card->len == 7) {
|
||||
memcpy(hid_cardio.current + 3, card->uid + 1, 6);
|
||||
}
|
||||
break;
|
||||
case NFC_CARD_FELICA:
|
||||
hid_cardio.current[0] = REPORT_ID_FELICA;
|
||||
memcpy(hid_cardio.current + 1, card->uid, 8);
|
||||
break;
|
||||
case NFC_CARD_VICINITY:
|
||||
hid_cardio.current[0] = REPORT_ID_EAMU;
|
||||
memcpy(hid_cardio.current + 1, card->uid, 8);
|
||||
break;
|
||||
default:
|
||||
memset(hid_cardio.current, 0, 9);
|
||||
return;
|
||||
}
|
||||
|
||||
gui_report_card_id(hid_cardio.current + 1, 8, true);
|
||||
printf(" -> CardIO ");
|
||||
for (int i = 1; i < 9; i++) {
|
||||
printf("%02X", hid_cardio.current[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void cardio_run()
|
||||
{
|
||||
if (aime_is_active() || bana_is_active()) {
|
||||
memset(hid_cardio.current, 0, 9);
|
||||
return;
|
||||
}
|
||||
|
||||
static nfc_card_t old_card = { 0 };
|
||||
|
||||
nfc_rf_field(true);
|
||||
nfc_card_t card = nfc_detect_card();
|
||||
if (card.card_type != NFC_CARD_NONE) {
|
||||
nfc_identify_last_card();
|
||||
gui_report_card_id(card.uid, card.len, false);
|
||||
}
|
||||
nfc_rf_field(false);
|
||||
|
||||
if (memcmp(&old_card, &card, sizeof(old_card)) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
old_card = card;
|
||||
|
||||
if (!reader_is_active() && !hid_is_active()) {
|
||||
if (card.card_type != NFC_CARD_NONE) {
|
||||
light_rainbow(30, 0, aic_cfg->light.level_active);
|
||||
} else {
|
||||
light_rainbow(1, 3000, aic_cfg->light.level_idle);
|
||||
}
|
||||
}
|
||||
|
||||
display_card(&card);
|
||||
update_cardio(&card);
|
||||
}
|
||||
|
||||
const int reader_intf = 1;
|
||||
static struct {
|
||||
uint8_t buf[64];
|
||||
@@ -348,8 +264,14 @@ static void core0_loop()
|
||||
|
||||
cli_run();
|
||||
reader_run();
|
||||
cardio_run();
|
||||
|
||||
if (reader_is_active()) {
|
||||
cardio_clear();
|
||||
}
|
||||
else {
|
||||
cardio_run(hid_light_is_active());
|
||||
}
|
||||
|
||||
keypad_update();
|
||||
report_usb_hid();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user