#include "lfrfid/lfrfid_i.h" #include #include #include "lfrfid_worker_i.h" #include "tools/t5577.h" #include #include #include "tools/varint_pair.h" #include #define TAG "LfRfidWorker" /** * if READ_DEBUG_GPIO is defined: * gpio_ext_pa7 will repeat signal coming from the comparator * gpio_ext_pa6 will show load on the decoder */ // #define LFRFID_WORKER_READ_DEBUG_GPIO 1 #ifdef LFRFID_WORKER_READ_DEBUG_GPIO #define LFRFID_WORKER_READ_DEBUG_GPIO_VALUE &gpio_ext_pa7 #define LFRFID_WORKER_READ_DEBUG_GPIO_LOAD &gpio_ext_pa6 #endif #define LFRFID_WORKER_READ_AVERAGE_COUNT 64 #define LFRFID_WORKER_READ_MIN_TIME_US 16 #define LFRFID_WORKER_READ_DROP_TIME_MS 50 #define LFRFID_WORKER_READ_STABILIZE_TIME_MS 450 #define LFRFID_WORKER_READ_SWITCH_TIME_MS 2000 #define LFRFID_WORKER_WRITE_VERIFY_TIME_MS 2000 #define LFRFID_WORKER_WRITE_DROP_TIME_MS 50 #define LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS 10000 #define LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS 5 #define LFRFID_WORKER_READ_BUFFER_SIZE 512 #define LFRFID_WORKER_READ_BUFFER_COUNT 16 #define LFRFID_WORKER_EMULATE_BUFFER_SIZE 1024 #define LFRFID_WORKER_DELAY_QUANT 50 void lfrfid_worker_delay(LFRFIDWorker* worker, uint32_t milliseconds) { for(uint32_t i = 0; i < (milliseconds / LFRFID_WORKER_DELAY_QUANT); i++) { if(lfrfid_worker_check_for_stop(worker)) break; furi_delay_ms(LFRFID_WORKER_DELAY_QUANT); } } void t5577_trace(LFRFIDT5577 t5577, const char* message) { if(furi_log_get_level() == FuriLogLevelTrace) { FURI_LOG_T(TAG, "%s", message); for(uint8_t i = 0; i < 8; i++) FURI_LOG_T(TAG, "\nBlock %u %08lX", i, t5577.block[i]); FURI_LOG_T(TAG, "Mask: %u", t5577.mask); FURI_LOG_T(TAG, "Blocks to write: %lu", t5577.blocks_to_write); } } /**************************************************************************************************/ /********************************************** READ **********************************************/ /**************************************************************************************************/ typedef struct { BufferStream* stream; VarintPair* pair; bool ignore_next_pulse; } LFRFIDWorkerReadContext; static void lfrfid_worker_read_capture(bool level, uint32_t duration, void* context) { LFRFIDWorkerReadContext* ctx = context; // ignore pulse if last pulse was noise if(ctx->ignore_next_pulse) { ctx->ignore_next_pulse = false; return; } // ignore noise spikes if(duration <= LFRFID_WORKER_READ_MIN_TIME_US) { if(level) { ctx->ignore_next_pulse = true; } varint_pair_reset(ctx->pair); return; } #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, level); #endif bool need_to_send = varint_pair_pack(ctx->pair, level, duration); if(need_to_send) { buffer_stream_send_from_isr( ctx->stream, varint_pair_get_data(ctx->pair), varint_pair_get_size(ctx->pair)); varint_pair_reset(ctx->pair); } } typedef enum { LFRFIDWorkerReadOK, LFRFIDWorkerReadExit, LFRFIDWorkerReadTimeout, } LFRFIDWorkerReadState; static LFRFIDWorkerReadState lfrfid_worker_read_internal( LFRFIDWorker* worker, LFRFIDFeature feature, uint32_t timeout, ProtocolId* result_protocol) { LFRFIDWorkerReadState state = LFRFIDWorkerReadTimeout; if(feature & LFRFIDFeatureASK) { furi_hal_rfid_tim_read_start(125000, 0.5); FURI_LOG_D(TAG, "Start ASK"); if(worker->read_cb) { worker->read_cb(LFRFIDWorkerReadStartASK, PROTOCOL_NO, worker->cb_ctx); } } else { furi_hal_rfid_tim_read_start(62500, 0.25); FURI_LOG_D(TAG, "Start PSK"); if(worker->read_cb) { worker->read_cb(LFRFIDWorkerReadStartPSK, PROTOCOL_NO, worker->cb_ctx); } } // stabilize detector lfrfid_worker_delay(worker, LFRFID_WORKER_READ_STABILIZE_TIME_MS); protocol_dict_decoders_start(worker->protocols); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, GpioModeOutputPushPull); furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeOutputPushPull); furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, false); furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); #endif LFRFIDWorkerReadContext ctx; ctx.pair = varint_pair_alloc(); ctx.stream = buffer_stream_alloc(LFRFID_WORKER_READ_BUFFER_SIZE, LFRFID_WORKER_READ_BUFFER_COUNT); furi_hal_rfid_tim_read_capture_start(lfrfid_worker_read_capture, &ctx); *result_protocol = PROTOCOL_NO; ProtocolId last_protocol = PROTOCOL_NO; size_t last_size = protocol_dict_get_max_data_size(worker->protocols); uint8_t* last_data = malloc(last_size); uint8_t* protocol_data = malloc(last_size); size_t last_read_count = 0; uint32_t switch_os_tick_last = furi_get_tick(); uint32_t average_duration = 0; uint32_t average_pulse = 0; size_t average_index = 0; bool card_detected = false; FURI_LOG_D(TAG, "Read started"); while(true) { if(lfrfid_worker_check_for_stop(worker)) { state = LFRFIDWorkerReadExit; break; } Buffer* buffer = buffer_stream_receive(ctx.stream, 100); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, true); #endif if(buffer_stream_get_overrun_count(ctx.stream) > 0) { FURI_LOG_E(TAG, "Read overrun, recovering"); buffer_stream_reset(ctx.stream); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); #endif continue; } if(buffer == NULL) { #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); #endif continue; } size_t size = buffer_get_size(buffer); uint8_t* data = buffer_get_data(buffer); size_t index = 0; while(index < size) { uint32_t duration; uint32_t pulse; size_t tmp_size; if(!varint_pair_unpack(&data[index], size - index, &pulse, &duration, &tmp_size)) { FURI_LOG_E(TAG, "can't unpack varint pair"); break; } else { index += tmp_size; average_duration += duration; average_pulse += pulse; average_index++; if(average_index >= LFRFID_WORKER_READ_AVERAGE_COUNT) { float average = (float)average_pulse / (float)average_duration; average_pulse = 0; average_duration = 0; average_index = 0; if(worker->read_cb) { if(average > 0.2f && average < 0.8f) { if(!card_detected) { card_detected = true; worker->read_cb( LFRFIDWorkerReadSenseStart, PROTOCOL_NO, worker->cb_ctx); } } else { if(card_detected) { card_detected = false; worker->read_cb( LFRFIDWorkerReadSenseEnd, PROTOCOL_NO, worker->cb_ctx); } } } } ProtocolId protocol = PROTOCOL_NO; protocol = protocol_dict_decoders_feed_by_feature( worker->protocols, feature, true, pulse); if(protocol == PROTOCOL_NO) { protocol = protocol_dict_decoders_feed_by_feature( worker->protocols, feature, false, duration - pulse); } if(protocol != PROTOCOL_NO) { // reset switch timer switch_os_tick_last = furi_get_tick(); size_t protocol_data_size = protocol_dict_get_data_size(worker->protocols, protocol); protocol_dict_get_data( worker->protocols, protocol, protocol_data, protocol_data_size); // validate protocol if(protocol == last_protocol && memcmp(last_data, protocol_data, protocol_data_size) == 0) { last_read_count = last_read_count + 1; size_t validation_count = protocol_dict_get_validate_count(worker->protocols, protocol); if(last_read_count >= validation_count) { state = LFRFIDWorkerReadOK; *result_protocol = protocol; break; } } else { if(last_protocol == PROTOCOL_NO && worker->read_cb) { worker->read_cb( LFRFIDWorkerReadSenseCardStart, protocol, worker->cb_ctx); } last_protocol = protocol; memcpy(last_data, protocol_data, protocol_data_size); last_read_count = 0; } if(furi_log_get_level() >= FuriLogLevelDebug) { FuriString* string_info; string_info = furi_string_alloc(); for(uint8_t i = 0; i < protocol_data_size; i++) { if(i != 0) { furi_string_cat_printf(string_info, " "); } furi_string_cat_printf(string_info, "%02X", protocol_data[i]); } FURI_LOG_D( TAG, "%s, %zu, [%s]", protocol_dict_get_name(worker->protocols, protocol), last_read_count, furi_string_get_cstr(string_info)); furi_string_free(string_info); } protocol_dict_decoders_start(worker->protocols); } } } buffer_reset(buffer); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); #endif if(*result_protocol != PROTOCOL_NO) { break; } if((furi_get_tick() - switch_os_tick_last) > timeout) { state = LFRFIDWorkerReadTimeout; break; } } FURI_LOG_D(TAG, "Read stopped"); if(last_protocol != PROTOCOL_NO && worker->read_cb) { worker->read_cb(LFRFIDWorkerReadSenseCardEnd, last_protocol, worker->cb_ctx); } if(card_detected && worker->read_cb) { worker->read_cb(LFRFIDWorkerReadSenseEnd, last_protocol, worker->cb_ctx); } furi_hal_rfid_tim_read_capture_stop(); furi_hal_rfid_tim_read_stop(); furi_hal_rfid_pins_reset(); varint_pair_free(ctx.pair); buffer_stream_free(ctx.stream); free(protocol_data); free(last_data); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, false); furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_VALUE, GpioModeAnalog); furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeAnalog); #endif return state; } static void lfrfid_worker_mode_read_process(LFRFIDWorker* worker) { ProtocolId read_result = PROTOCOL_NO; LFRFIDWorkerReadState state; LFRFIDFeature feature; if(worker->read_type == LFRFIDWorkerReadTypePSKOnly) { feature = LFRFIDFeaturePSK; } else { feature = LFRFIDFeatureASK; } if(worker->read_type == LFRFIDWorkerReadTypeAuto) { while(1) { // read for a while state = lfrfid_worker_read_internal( worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { break; } // switch to next feature if(feature == LFRFIDFeatureASK) { feature = LFRFIDFeaturePSK; } else { feature = LFRFIDFeatureASK; } lfrfid_worker_delay(worker, LFRFID_WORKER_READ_DROP_TIME_MS); } } else { while(1) { if(worker->read_type == LFRFIDWorkerReadTypeASKOnly) { state = lfrfid_worker_read_internal(worker, feature, UINT32_MAX, &read_result); } else { state = lfrfid_worker_read_internal( worker, feature, LFRFID_WORKER_READ_SWITCH_TIME_MS, &read_result); } if(state == LFRFIDWorkerReadOK || state == LFRFIDWorkerReadExit) { break; } lfrfid_worker_delay(worker, LFRFID_WORKER_READ_DROP_TIME_MS); } } if(state == LFRFIDWorkerReadOK && worker->read_cb) { worker->read_cb(LFRFIDWorkerReadDone, read_result, worker->cb_ctx); } } /**************************************************************************************************/ /******************************************** EMULATE *********************************************/ /**************************************************************************************************/ typedef struct { uint32_t duration[LFRFID_WORKER_EMULATE_BUFFER_SIZE]; uint32_t pulse[LFRFID_WORKER_EMULATE_BUFFER_SIZE]; } LFRFIDWorkerEmulateBuffer; typedef enum { HalfTransfer, TransferComplete, } LFRFIDWorkerEmulateDMAEvent; static void lfrfid_worker_emulate_dma_isr(bool half, void* context) { FuriStreamBuffer* stream = context; uint32_t flag = half ? HalfTransfer : TransferComplete; furi_stream_buffer_send(stream, &flag, sizeof(uint32_t), 0); } static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { LFRFIDWorkerEmulateBuffer* buffer = malloc(sizeof(LFRFIDWorkerEmulateBuffer)); FuriStreamBuffer* stream = furi_stream_buffer_alloc(sizeof(uint32_t), sizeof(uint32_t)); LFRFIDProtocol protocol = worker->protocol; PulseGlue* pulse_glue = pulse_glue_alloc(); protocol_dict_encoder_start(worker->protocols, protocol); for(size_t i = 0; i < LFRFID_WORKER_EMULATE_BUFFER_SIZE; i++) { bool pulse_pop = false; while(!pulse_pop) { LevelDuration level_duration = protocol_dict_encoder_yield(worker->protocols, protocol); pulse_pop = pulse_glue_push( pulse_glue, level_duration_get_level(level_duration), level_duration_get_duration(level_duration)); } uint32_t duration, pulse; pulse_glue_pop(pulse_glue, &duration, &pulse); buffer->duration[i] = duration - 1; buffer->pulse[i] = pulse; } #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeOutputPushPull); #endif furi_hal_rfid_tim_emulate_dma_start( buffer->duration, buffer->pulse, LFRFID_WORKER_EMULATE_BUFFER_SIZE, lfrfid_worker_emulate_dma_isr, stream); while(true) { uint32_t flag = 0; size_t size = furi_stream_buffer_receive(stream, &flag, sizeof(uint32_t), 100); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, true); #endif if(size == sizeof(uint32_t)) { size_t start = 0; if(flag == HalfTransfer) { start = 0; } else if(flag == TransferComplete) { start = (LFRFID_WORKER_EMULATE_BUFFER_SIZE / 2); } for(size_t i = 0; i < (LFRFID_WORKER_EMULATE_BUFFER_SIZE / 2); i++) { bool pulse_pop = false; while(!pulse_pop) { LevelDuration level_duration = protocol_dict_encoder_yield(worker->protocols, protocol); pulse_pop = pulse_glue_push( pulse_glue, level_duration_get_level(level_duration), level_duration_get_duration(level_duration)); } uint32_t duration, pulse; pulse_glue_pop(pulse_glue, &duration, &pulse); buffer->duration[start + i] = duration - 1; buffer->pulse[start + i] = pulse; } } if(lfrfid_worker_check_for_stop(worker)) { break; } #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_write(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, false); #endif } furi_hal_rfid_tim_emulate_dma_stop(); #ifdef LFRFID_WORKER_READ_DEBUG_GPIO furi_hal_gpio_init_simple(LFRFID_WORKER_READ_DEBUG_GPIO_LOAD, GpioModeAnalog); #endif free(buffer); furi_stream_buffer_free(stream); pulse_glue_free(pulse_glue); } /**************************************************************************************************/ /********************************************* WRITE **********************************************/ /**************************************************************************************************/ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { LFRFIDProtocol protocol = worker->protocol; LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest)); request->write_type = LFRFIDWriteTypeT5577; bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request); uint32_t write_start_time = furi_get_tick(); bool too_long = false; size_t unsuccessful_reads = 0; size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol); uint8_t* verify_data = malloc(data_size); uint8_t* read_data = malloc(data_size); protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size); if(can_be_written) { while(!lfrfid_worker_check_for_stop(worker)) { FURI_LOG_D(TAG, "Data write"); t5577_write(&request->t5577); ProtocolId read_result = PROTOCOL_NO; LFRFIDWorkerReadState state = lfrfid_worker_read_internal( worker, protocol_dict_get_features(worker->protocols, protocol), LFRFID_WORKER_WRITE_VERIFY_TIME_MS, &read_result); if(state == LFRFIDWorkerReadOK) { bool read_success = false; if(read_result == protocol) { protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); if(memcmp(read_data, verify_data, data_size) == 0) { read_success = true; } } if(read_success) { if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); } break; } else { unsuccessful_reads++; if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); } } } } else if(state == LFRFIDWorkerReadExit) { break; } if(!too_long && (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { too_long = true; if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); } } lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); } } else { if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); } } free(request); free(verify_data); free(read_data); } static void lfrfid_worker_mode_write_and_set_pass_process(LFRFIDWorker* worker) { LFRFIDProtocol protocol = worker->protocol; LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest)); request->write_type = LFRFIDWriteTypeT5577; bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request); uint32_t write_start_time = furi_get_tick(); bool too_long = false; size_t unsuccessful_reads = 0; size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol); uint8_t* verify_data = malloc(data_size); uint8_t* read_data = malloc(data_size); protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size); if(can_be_written) { while(!lfrfid_worker_check_for_stop(worker)) { FURI_LOG_D(TAG, "Data write with pass"); LfRfid* app = worker->cb_ctx; uint32_t pass = bit_lib_bytes_to_num_be(app->password, 4); request->t5577.mask = 0b10000001; for(uint8_t i = 0; i < request->t5577.blocks_to_write; i++) request->t5577.mask |= (1 << i); request->t5577.block[0] |= (1 << 4); request->t5577.block[7] = pass; t5577_trace(request->t5577, "Write with password"); t5577_write_with_mask(&request->t5577, 0, true, 0); ProtocolId read_result = PROTOCOL_NO; LFRFIDWorkerReadState state = lfrfid_worker_read_internal( worker, protocol_dict_get_features(worker->protocols, protocol), LFRFID_WORKER_WRITE_VERIFY_TIME_MS, &read_result); if(state == LFRFIDWorkerReadOK) { bool read_success = false; if(read_result == protocol) { protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); if(memcmp(read_data, verify_data, data_size) == 0) { read_success = true; } } if(read_success) { FURI_LOG_D(TAG, "Write with password %08lX success", pass); if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); } break; } else { unsuccessful_reads++; if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); } } } } else if(state == LFRFIDWorkerReadExit) { break; } if(!too_long && (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { too_long = true; if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); } } lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); } } else { if(worker->write_cb) { worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); } } free(request); free(verify_data); free(read_data); } /**************************************************************************************************/ /******************************************* READ RAW *********************************************/ /**************************************************************************************************/ static void lfrfid_worker_mode_read_raw_process(LFRFIDWorker* worker) { LFRFIDRawWorker* raw_worker = lfrfid_raw_worker_alloc(); switch(worker->read_type) { case LFRFIDWorkerReadTypePSKOnly: lfrfid_raw_worker_start_read( raw_worker, worker->raw_filename, 62500, 0.25, worker->read_raw_cb, worker->cb_ctx); break; case LFRFIDWorkerReadTypeASKOnly: lfrfid_raw_worker_start_read( raw_worker, worker->raw_filename, 125000, 0.5, worker->read_raw_cb, worker->cb_ctx); break; default: furi_crash("RAW can be only PSK or ASK"); break; } while(!lfrfid_worker_check_for_stop(worker)) { furi_delay_ms(100); } lfrfid_raw_worker_stop(raw_worker); lfrfid_raw_worker_free(raw_worker); } /**************************************************************************************************/ /***************************************** EMULATE RAW ********************************************/ /**************************************************************************************************/ static void lfrfid_worker_mode_emulate_raw_process(LFRFIDWorker* worker) { LFRFIDRawWorker* raw_worker = lfrfid_raw_worker_alloc(); lfrfid_raw_worker_start_emulate( raw_worker, worker->raw_filename, worker->emulate_raw_cb, worker->cb_ctx); while(!lfrfid_worker_check_for_stop(worker)) { furi_delay_ms(100); } lfrfid_raw_worker_stop(raw_worker); lfrfid_raw_worker_free(raw_worker); } /**************************************************************************************************/ /******************************************** MODES ***********************************************/ /**************************************************************************************************/ const LFRFIDWorkerModeType lfrfid_worker_modes[] = { [LFRFIDWorkerIdle] = {.process = NULL}, [LFRFIDWorkerRead] = {.process = lfrfid_worker_mode_read_process}, [LFRFIDWorkerWrite] = {.process = lfrfid_worker_mode_write_process}, [LFRFIDWorkerWriteAndSetPass] = {.process = lfrfid_worker_mode_write_and_set_pass_process}, [LFRFIDWorkerEmulate] = {.process = lfrfid_worker_mode_emulate_process}, [LFRFIDWorkerReadRaw] = {.process = lfrfid_worker_mode_read_raw_process}, [LFRFIDWorkerEmulateRaw] = {.process = lfrfid_worker_mode_emulate_raw_process}, };