diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b72d9ea6136..cf86fb91698 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,68 +1,68 @@ # Who owns all the fish by default -* @skotopes @DrZlo13 @hedger +* @skotopes @DrZlo13 @hedger @gsurkov # Apps -/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gornekich -/applications/debug/accessor/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/debug/battery_test_app/ @skotopes @DrZlo13 @hedger @gornekich -/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gornekich -/applications/debug/file_browser_test/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/debug/lfrfid_debug/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/debug/text_box_test/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/debug/uart_echo/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/debug/usb_mouse/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/debug/usb_test/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich +/applications/debug/accessor/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/debug/battery_test_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich +/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich +/applications/debug/file_browser_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/debug/lfrfid_debug/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/debug/text_box_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/debug/uart_echo/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/debug/usb_mouse/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/debug/usb_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov -/applications/main/archive/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/main/bad_usb/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/main/gpio/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/main/archive/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/main/bad_usb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/main/gpio/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov /applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov -/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra -/applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm -/applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra +/applications/main/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm +/applications/main/u2f/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov -/applications/services/bt/ @skotopes @DrZlo13 @hedger @gornekich -/applications/services/cli/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/services/crypto/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/services/desktop/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/services/dolphin/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/services/power/ @skotopes @DrZlo13 @hedger @gornekich -/applications/services/rpc/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/services/bt/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich +/applications/services/cli/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/services/crypto/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/services/desktop/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/services/dolphin/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/services/power/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich +/applications/services/rpc/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov -/applications/services/bt_settings_app/ @skotopes @DrZlo13 @hedger @gornekich -/applications/services/desktop_settings/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/services/dolphin_passport/ @skotopes @DrZlo13 @hedger @nminaylov -/applications/services/power_settings_app/ @skotopes @DrZlo13 @hedger @gornekich +/applications/services/bt_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich +/applications/services/desktop_settings/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/services/dolphin_passport/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/services/power_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich -/applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @nminaylov +/applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov -/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm +/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Astrrra @Skorpionm /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov # Firmware targets -/targets/ @skotopes @DrZlo13 @hedger @nminaylov +/targets/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov # Assets /applications/main/infrared/resources/ @skotopes @DrZlo13 @hedger @gsurkov # Documentation -/documentation/ @skotopes @DrZlo13 @hedger @drunkbatya -/scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya +/documentation/ @skotopes @DrZlo13 @hedger @gsurkov @drunkbatya +/scripts/toolchain/ @skotopes @DrZlo13 @hedger @gsurkov @drunkbatya # Lib -/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gornekich -/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich +/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich +/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov -/lib/lfrfid/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/mbedtls/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/nanopb/ @skotopes @DrZlo13 @hedger @nminaylov -/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra +/lib/lfrfid/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/lib/mbedtls/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/lib/micro-ecc/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/lib/nanopb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/lib/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra /lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov -/lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm +/lib/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm # CI/CD -/.github/workflows/ @skotopes @DrZlo13 @hedger @drunkbatya +/.github/workflows/ @skotopes @DrZlo13 @hedger @gsurkov @drunkbatya diff --git a/SConstruct b/SConstruct index b42218a579c..6d24da920a6 100644 --- a/SConstruct +++ b/SConstruct @@ -369,7 +369,7 @@ vscode_dist = distenv.Install( ) distenv.Precious(vscode_dist) distenv.NoClean(vscode_dist) -distenv.Alias("vscode_dist", vscode_dist) +distenv.Alias("vscode_dist", (vscode_dist, firmware_env["FW_CDB"])) # Configure shell with build tools distenv.PhonyTarget( diff --git a/applications/debug/expansion_test/application.fam b/applications/debug/expansion_test/application.fam new file mode 100644 index 00000000000..9bc4b2fc293 --- /dev/null +++ b/applications/debug/expansion_test/application.fam @@ -0,0 +1,12 @@ +App( + appid="expansion_test", + name="Expansion Module Test", + apptype=FlipperAppType.DEBUG, + entry_point="expansion_test_app", + requires=["expansion_start"], + fap_libs=["assets"], + stack_size=1 * 1024, + order=20, + fap_category="Debug", + fap_file_assets="assets", +) diff --git a/applications/debug/expansion_test/assets/test.txt b/applications/debug/expansion_test/assets/test.txt new file mode 100644 index 00000000000..e39b1eec5cd --- /dev/null +++ b/applications/debug/expansion_test/assets/test.txt @@ -0,0 +1,9 @@ +"Did you ever hear the tragedy of Darth Plagueis the Wise?" +"No." +"I thought not. It's not a story the Jedi would tell you. It's a Sith legend. Darth Plagueis... was a Dark Lord of the Sith so powerful and so wise, he could use the Force to influence the midi-chlorians... to create... life. He had such a knowledge of the dark side, he could even keep the ones he cared about... from dying." +"He could actually... save people from death?" +"The dark side of the Force is a pathway to many abilities... some consider to be unnatural." +"Wh– What happened to him?" +"He became so powerful, the only thing he was afraid of was... losing his power. Which eventually, of course, he did. Unfortunately, he taught his apprentice everything he knew. Then his apprentice killed him in his sleep. It's ironic. He could save others from death, but not himself." +"Is it possible to learn this power?" +"Not from a Jedi." diff --git a/applications/debug/expansion_test/expansion_test.c b/applications/debug/expansion_test/expansion_test.c new file mode 100644 index 00000000000..a0b8b42e8bd --- /dev/null +++ b/applications/debug/expansion_test/expansion_test.c @@ -0,0 +1,457 @@ +/** + * @file expansion_test.c + * @brief Expansion module support testing application. + * + * Before running, connect pins using the following scheme: + * 13 -> 16 (USART TX to LPUART RX) + * 14 -> 15 (USART RX to LPUART TX) + * + * What this application does: + * + * - Enables module support and emulates the module on a single device + * (hence the above connection), + * - Connects to the expansion module service, sets baud rate, + * - Starts the RPC session, + * - Creates a directory at `/ext/ExpansionTest` and writes a file + * named `test.txt` under it, + * - Plays an audiovisual alert (sound and blinking display), + * - Waits 10 cycles of idle loop, + * - Stops the RPC session, + * - Waits another 10 cycles of idle loop, + * - Exits (plays a sound if any of the above steps failed). + */ +#include + +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define TAG "ExpansionTest" + +#define TEST_DIR_PATH EXT_PATH(TAG) +#define TEST_FILE_NAME "test.txt" +#define TEST_FILE_PATH EXT_PATH(TAG "/" TEST_FILE_NAME) + +#define HOST_SERIAL_ID (FuriHalSerialIdLpuart) +#define MODULE_SERIAL_ID (FuriHalSerialIdUsart) + +#define RECEIVE_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) + +typedef enum { + ExpansionTestAppFlagData = 1U << 0, + ExpansionTestAppFlagExit = 1U << 1, +} ExpansionTestAppFlag; + +#define EXPANSION_TEST_APP_ALL_FLAGS (ExpansionTestAppFlagData | ExpansionTestAppFlagExit) + +typedef struct { + FuriThreadId thread_id; + Expansion* expansion; + FuriHalSerialHandle* handle; + FuriStreamBuffer* buf; + ExpansionFrame frame; + PB_Main msg; + Storage* storage; +} ExpansionTestApp; + +static void expansion_test_app_serial_rx_callback( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + void* context) { + furi_assert(handle); + furi_assert(context); + ExpansionTestApp* app = context; + + if(event == FuriHalSerialRxEventData) { + const uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(app->buf, &data, sizeof(data), 0); + furi_thread_flags_set(app->thread_id, ExpansionTestAppFlagData); + } +} + +static ExpansionTestApp* expansion_test_app_alloc() { + ExpansionTestApp* instance = malloc(sizeof(ExpansionTestApp)); + instance->buf = furi_stream_buffer_alloc(RECEIVE_BUFFER_SIZE, 1); + return instance; +} + +static void expansion_test_app_free(ExpansionTestApp* instance) { + furi_stream_buffer_free(instance->buf); + free(instance); +} + +static void expansion_test_app_start(ExpansionTestApp* instance) { + instance->thread_id = furi_thread_get_current_id(); + instance->expansion = furi_record_open(RECORD_EXPANSION); + instance->handle = furi_hal_serial_control_acquire(MODULE_SERIAL_ID); + furi_check(instance->handle); + // Configure the serial port + furi_hal_serial_init(instance->handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); + // Start waiting for the initial pulse + expansion_set_listen_serial(instance->expansion, HOST_SERIAL_ID); + + furi_hal_serial_async_rx_start( + instance->handle, expansion_test_app_serial_rx_callback, instance, false); +} + +static void expansion_test_app_stop(ExpansionTestApp* instance) { + // Disable expansion module support + expansion_disable(instance->expansion); + // Give back the module handle + furi_hal_serial_control_release(instance->handle); + // Restore expansion user settings + expansion_enable(instance->expansion); + furi_record_close(RECORD_EXPANSION); +} + +static inline bool expansion_test_app_is_success_response(const ExpansionFrame* response) { + return response->header.type == ExpansionFrameTypeStatus && + response->content.status.error == ExpansionFrameErrorNone; +} + +static inline bool expansion_test_app_is_success_rpc_message(const PB_Main* message) { + return (message->command_status == PB_CommandStatus_OK || + message->command_status == PB_CommandStatus_ERROR_STORAGE_EXIST) && + (message->which_content == PB_Main_empty_tag); +} + +static size_t expansion_test_app_receive_callback(uint8_t* data, size_t data_size, void* context) { + ExpansionTestApp* instance = context; + + size_t received_size = 0; + + while(true) { + received_size += furi_stream_buffer_receive( + instance->buf, data + received_size, data_size - received_size, 0); + if(received_size == data_size) break; + + const uint32_t flags = furi_thread_flags_wait( + EXPANSION_TEST_APP_ALL_FLAGS, FuriFlagWaitAny, EXPANSION_PROTOCOL_TIMEOUT_MS); + + // Exit on any error + if(flags & FuriFlagError) break; + } + + return received_size; +} + +static size_t + expansion_test_app_send_callback(const uint8_t* data, size_t data_size, void* context) { + ExpansionTestApp* instance = context; + + furi_hal_serial_tx(instance->handle, data, data_size); + furi_hal_serial_tx_wait_complete(instance->handle); + + return data_size; +} + +static bool expansion_test_app_receive_frame(ExpansionTestApp* instance, ExpansionFrame* frame) { + return expansion_protocol_decode(frame, expansion_test_app_receive_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool + expansion_test_app_send_status_response(ExpansionTestApp* instance, ExpansionFrameError error) { + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeStatus, + .content.status.error = error, + }; + return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_test_app_send_heartbeat(ExpansionTestApp* instance) { + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeHeartbeat, + .content.heartbeat = {}, + }; + return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool + expansion_test_app_send_baud_rate_request(ExpansionTestApp* instance, uint32_t baud_rate) { + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeBaudRate, + .content.baud_rate.baud = baud_rate, + }; + return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_test_app_send_control_request( + ExpansionTestApp* instance, + ExpansionFrameControlCommand command) { + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeControl, + .content.control.command = command, + }; + return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_test_app_send_data_request( + ExpansionTestApp* instance, + const uint8_t* data, + size_t data_size) { + furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); + + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeData, + .content.data.size = data_size, + }; + + memcpy(frame.content.data.bytes, data, data_size); + return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_test_app_rpc_encode_callback( + pb_ostream_t* stream, + const pb_byte_t* data, + size_t data_size) { + ExpansionTestApp* instance = stream->state; + + size_t size_sent = 0; + + while(size_sent < data_size) { + const size_t current_size = MIN(data_size - size_sent, EXPANSION_PROTOCOL_MAX_DATA_SIZE); + if(!expansion_test_app_send_data_request(instance, data + size_sent, current_size)) break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_is_success_response(&instance->frame)) break; + size_sent += current_size; + } + + return size_sent == data_size; +} + +static bool expansion_test_app_send_rpc_request(ExpansionTestApp* instance, PB_Main* message) { + pb_ostream_t stream = { + .callback = expansion_test_app_rpc_encode_callback, + .state = instance, + .max_size = SIZE_MAX, + .bytes_written = 0, + .errmsg = NULL, + }; + + const bool success = pb_encode_ex(&stream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); + pb_release(&PB_Main_msg, message); + return success; +} + +static bool expansion_test_app_receive_rpc_request(ExpansionTestApp* instance, PB_Main* message) { + bool success = false; + + do { + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_send_status_response(instance, ExpansionFrameErrorNone)) break; + if(instance->frame.header.type != ExpansionFrameTypeData) break; + pb_istream_t stream = pb_istream_from_buffer( + instance->frame.content.data.bytes, instance->frame.content.data.size); + if(!pb_decode_ex(&stream, &PB_Main_msg, message, PB_DECODE_DELIMITED)) break; + success = true; + } while(false); + + return success; +} + +static bool expansion_test_app_send_presence(ExpansionTestApp* instance) { + // Send pulses to emulate module insertion + const uint8_t init = 0xAA; + furi_hal_serial_tx(instance->handle, &init, sizeof(init)); + furi_hal_serial_tx_wait_complete(instance->handle); + return true; +} + +static bool expansion_test_app_wait_ready(ExpansionTestApp* instance) { + bool success = false; + + do { + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(instance->frame.header.type != ExpansionFrameTypeHeartbeat) break; + success = true; + } while(false); + + return success; +} + +static bool expansion_test_app_handshake(ExpansionTestApp* instance) { + bool success = false; + + do { + if(!expansion_test_app_send_baud_rate_request(instance, 230400)) break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_is_success_response(&instance->frame)) break; + furi_hal_serial_set_br(instance->handle, 230400); + furi_delay_ms(EXPANSION_PROTOCOL_BAUD_CHANGE_DT_MS); + success = true; + } while(false); + + return success; +} + +static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) { + bool success = false; + + do { + if(!expansion_test_app_send_control_request(instance, ExpansionFrameControlCommandStartRpc)) + break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_is_success_response(&instance->frame)) break; + success = true; + } while(false); + + return success; +} + +static bool expansion_test_app_rpc_mkdir(ExpansionTestApp* instance) { + bool success = false; + + instance->msg.command_id++; + instance->msg.command_status = PB_CommandStatus_OK; + instance->msg.which_content = PB_Main_storage_mkdir_request_tag; + instance->msg.has_next = false; + instance->msg.content.storage_mkdir_request.path = TEST_DIR_PATH; + + do { + if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_is_success_rpc_message(&instance->msg)) break; + success = true; + } while(false); + + return success; +} + +static bool expansion_test_app_rpc_write(ExpansionTestApp* instance) { + bool success = false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + do { + if(!storage_file_open(file, APP_ASSETS_PATH(TEST_FILE_NAME), FSAM_READ, FSOM_OPEN_EXISTING)) + break; + + const uint64_t file_size = storage_file_size(file); + + instance->msg.command_id++; + instance->msg.command_status = PB_CommandStatus_OK; + instance->msg.which_content = PB_Main_storage_write_request_tag; + instance->msg.has_next = false; + instance->msg.content.storage_write_request.path = TEST_FILE_PATH; + instance->msg.content.storage_write_request.has_file = true; + instance->msg.content.storage_write_request.file.data = + malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(file_size)); + instance->msg.content.storage_write_request.file.data->size = file_size; + + const size_t bytes_read = storage_file_read( + file, instance->msg.content.storage_write_request.file.data->bytes, file_size); + + if(bytes_read != file_size) { + pb_release(&PB_Main_msg, &instance->msg); + break; + } + + if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_is_success_rpc_message(&instance->msg)) break; + success = true; + } while(false); + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return success; +} + +static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) { + bool success = false; + + instance->msg.command_id++; + instance->msg.command_status = PB_CommandStatus_OK; + instance->msg.which_content = PB_Main_system_play_audiovisual_alert_request_tag; + instance->msg.has_next = false; + + do { + if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; + if(instance->msg.which_content != PB_Main_empty_tag) break; + if(instance->msg.command_status != PB_CommandStatus_OK) break; + success = true; + } while(false); + + return success; +} + +static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) { + uint32_t num_cycles_done; + for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) { + if(!expansion_test_app_send_heartbeat(instance)) break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(instance->frame.header.type != ExpansionFrameTypeHeartbeat) break; + furi_delay_ms(EXPANSION_PROTOCOL_TIMEOUT_MS - 50); + } + + return num_cycles_done == num_cycles; +} + +static bool expansion_test_app_stop_rpc(ExpansionTestApp* instance) { + bool success = false; + + do { + if(!expansion_test_app_send_control_request(instance, ExpansionFrameControlCommandStopRpc)) + break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_is_success_response(&instance->frame)) break; + success = true; + } while(false); + + return success; +} + +int32_t expansion_test_app(void* p) { + UNUSED(p); + + ExpansionTestApp* instance = expansion_test_app_alloc(); + expansion_test_app_start(instance); + + bool success = false; + + do { + if(!expansion_test_app_send_presence(instance)) break; + if(!expansion_test_app_wait_ready(instance)) break; + if(!expansion_test_app_handshake(instance)) break; + if(!expansion_test_app_start_rpc(instance)) break; + if(!expansion_test_app_rpc_mkdir(instance)) break; + if(!expansion_test_app_rpc_write(instance)) break; + if(!expansion_test_app_rpc_alert(instance)) break; + if(!expansion_test_app_idle(instance, 10)) break; + if(!expansion_test_app_stop_rpc(instance)) break; + if(!expansion_test_app_idle(instance, 10)) break; + success = true; + } while(false); + + expansion_test_app_stop(instance); + expansion_test_app_free(instance); + + if(!success) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_error); + furi_record_close(RECORD_NOTIFICATION); + } + + return 0; +} diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 4bede9ab45f..0291c9e79d4 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -1,13 +1,14 @@ #include +#include + #include -#include -#include #include -#include -#include #include #include +#include +#include + #define LINES_ON_SCREEN 6 #define COLUMNS_ON_SCREEN 21 #define TAG "UartEcho" @@ -22,6 +23,7 @@ typedef struct { View* view; FuriThread* worker_thread; FuriStreamBuffer* rx_stream; + FuriHalSerialHandle* serial_handle; } UartEchoApp; typedef struct { @@ -39,10 +41,16 @@ struct UartDumpModel { typedef enum { WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event WorkerEventStop = (1 << 1), - WorkerEventRx = (1 << 2), + WorkerEventRxData = (1 << 2), + WorkerEventRxIdle = (1 << 3), + WorkerEventRxOverrunError = (1 << 4), + WorkerEventRxFramingError = (1 << 5), + WorkerEventRxNoiseError = (1 << 6), } WorkerEventFlags; -#define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) +#define WORKER_EVENTS_MASK \ + (WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \ + WorkerEventRxFramingError | WorkerEventRxNoiseError) const NotificationSequence sequence_notification = { &message_display_backlight_on, @@ -91,14 +99,39 @@ static uint32_t uart_echo_exit(void* context) { return VIEW_NONE; } -static void uart_echo_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { +static void + uart_echo_on_irq_cb(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { furi_assert(context); + UNUSED(handle); UartEchoApp* app = context; + volatile FuriHalSerialRxEvent event_copy = event; + UNUSED(event_copy); - if(ev == UartIrqEventRXNE) { + WorkerEventFlags flag = 0; + + if(event & FuriHalSerialRxEventData) { + uint8_t data = furi_hal_serial_async_rx(handle); furi_stream_buffer_send(app->rx_stream, &data, 1, 0); - furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx); + flag |= WorkerEventRxData; + } + + if(event & FuriHalSerialRxEventIdle) { + //idle line detected, packet transmission may have ended + flag |= WorkerEventRxIdle; + } + + //error detected + if(event & FuriHalSerialRxEventFrameError) { + flag |= WorkerEventRxFramingError; + } + if(event & FuriHalSerialRxEventNoiseError) { + flag |= WorkerEventRxNoiseError; } + if(event & FuriHalSerialRxEventOverrunError) { + flag |= WorkerEventRxOverrunError; + } + + furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag); } static void uart_echo_push_to_list(UartDumpModel* model, const char data) { @@ -153,13 +186,13 @@ static int32_t uart_echo_worker(void* context) { furi_check((events & FuriFlagError) == 0); if(events & WorkerEventStop) break; - if(events & WorkerEventRx) { + if(events & WorkerEventRxData) { size_t length = 0; do { uint8_t data[64]; length = furi_stream_buffer_receive(app->rx_stream, data, 64, 0); if(length > 0) { - furi_hal_uart_tx(FuriHalUartIdUSART1, data, length); + furi_hal_serial_tx(app->serial_handle, data, length); with_view_model( app->view, UartDumpModel * model, @@ -176,6 +209,23 @@ static int32_t uart_echo_worker(void* context) { with_view_model( app->view, UartDumpModel * model, { UNUSED(model); }, true); } + + if(events & WorkerEventRxIdle) { + furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect IDLE\r\n", 15); + } + + if(events & + (WorkerEventRxOverrunError | WorkerEventRxFramingError | WorkerEventRxNoiseError)) { + if(events & WorkerEventRxOverrunError) { + furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect ORE\r\n", 14); + } + if(events & WorkerEventRxFramingError) { + furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect FE\r\n", 13); + } + if(events & WorkerEventRxNoiseError) { + furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13); + } + } } return 0; @@ -221,9 +271,11 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { furi_thread_start(app->worker_thread); // Enable uart listener - furi_hal_console_disable(); - furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate); - furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); + app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart); + furi_check(app->serial_handle); + furi_hal_serial_init(app->serial_handle, baudrate); + + furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true); return app; } @@ -231,12 +283,13 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { static void uart_echo_app_free(UartEchoApp* app) { furi_assert(app); - furi_hal_console_enable(); // this will also clear IRQ callback so thread is no longer referenced - furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop); furi_thread_join(app->worker_thread); furi_thread_free(app->worker_thread); + furi_hal_serial_deinit(app->serial_handle); + furi_hal_serial_control_release(app->serial_handle); + // Free views view_dispatcher_remove_view(app->view_dispatcher, 0); diff --git a/applications/debug/unit_tests/expansion/expansion_test.c b/applications/debug/unit_tests/expansion/expansion_test.c new file mode 100644 index 00000000000..50fe1b9f4d7 --- /dev/null +++ b/applications/debug/unit_tests/expansion/expansion_test.c @@ -0,0 +1,200 @@ +#include "../minunit.h" + +#include +#include + +#include + +#define EXPANSION_TEST_GARBAGE_MAGIC (0xB19AF) +#define EXPANSION_TEST_GARBAGE_BUF_SIZE (0x100U) +#define EXPANSION_TEST_GARBAGE_ITERATIONS (100U) + +MU_TEST(test_expansion_encoded_size) { + ExpansionFrame frame = {}; + + frame.header.type = ExpansionFrameTypeHeartbeat; + mu_assert_int_eq(1, expansion_frame_get_encoded_size(&frame)); + + frame.header.type = ExpansionFrameTypeStatus; + mu_assert_int_eq(2, expansion_frame_get_encoded_size(&frame)); + + frame.header.type = ExpansionFrameTypeBaudRate; + mu_assert_int_eq(5, expansion_frame_get_encoded_size(&frame)); + + frame.header.type = ExpansionFrameTypeControl; + mu_assert_int_eq(2, expansion_frame_get_encoded_size(&frame)); + + frame.header.type = ExpansionFrameTypeData; + for(size_t i = 0; i <= EXPANSION_PROTOCOL_MAX_DATA_SIZE; ++i) { + frame.content.data.size = i; + mu_assert_int_eq(i + 2, expansion_frame_get_encoded_size(&frame)); + } +} + +MU_TEST(test_expansion_remaining_size) { + ExpansionFrame frame = {}; + + size_t remaining_size; + mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + + frame.header.type = ExpansionFrameTypeHeartbeat; + mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + + frame.header.type = ExpansionFrameTypeStatus; + mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 2, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + + frame.header.type = ExpansionFrameTypeBaudRate; + mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size)); + mu_assert_int_eq(4, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 5, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + + frame.header.type = ExpansionFrameTypeControl; + mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 2, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size)); + mu_assert_int_eq(0, remaining_size); + + frame.header.type = ExpansionFrameTypeData; + frame.content.data.size = EXPANSION_PROTOCOL_MAX_DATA_SIZE; + mu_check(expansion_frame_get_remaining_size(&frame, 0, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 1, &remaining_size)); + mu_assert_int_eq(1, remaining_size); + mu_check(expansion_frame_get_remaining_size(&frame, 2, &remaining_size)); + mu_assert_int_eq(EXPANSION_PROTOCOL_MAX_DATA_SIZE, remaining_size); + for(size_t i = 0; i <= EXPANSION_PROTOCOL_MAX_DATA_SIZE; ++i) { + mu_check(expansion_frame_get_remaining_size(&frame, i + 2, &remaining_size)); + mu_assert_int_eq(EXPANSION_PROTOCOL_MAX_DATA_SIZE - i, remaining_size); + } + mu_check(expansion_frame_get_remaining_size(&frame, 100, &remaining_size)); + mu_assert_int_eq(0, remaining_size); +} + +typedef struct { + void* data_out; + size_t size_available; + size_t size_sent; +} TestExpansionSendStream; + +static size_t test_expansion_send_callback(const uint8_t* data, size_t data_size, void* context) { + TestExpansionSendStream* stream = context; + const size_t size_sent = MIN(data_size, stream->size_available); + + memcpy(stream->data_out + stream->size_sent, data, size_sent); + + stream->size_available -= size_sent; + stream->size_sent += size_sent; + + return size_sent; +} + +typedef struct { + const void* data_in; + size_t size_available; + size_t size_received; +} TestExpansionReceiveStream; + +static size_t test_expansion_receive_callback(uint8_t* data, size_t data_size, void* context) { + TestExpansionReceiveStream* stream = context; + const size_t size_received = MIN(data_size, stream->size_available); + + memcpy(data, stream->data_in + stream->size_received, size_received); + + stream->size_available -= size_received; + stream->size_received += size_received; + + return size_received; +} + +MU_TEST(test_expansion_encode_decode_frame) { + const ExpansionFrame frame_in = { + .header.type = ExpansionFrameTypeData, + .content.data.size = 8, + .content.data.bytes = {0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xca, 0xfe}, + }; + + uint8_t encoded_data[sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)]; + memset(encoded_data, 0, sizeof(encoded_data)); + + TestExpansionSendStream send_stream = { + .data_out = &encoded_data, + .size_available = sizeof(encoded_data), + .size_sent = 0, + }; + + const size_t encoded_size = expansion_frame_get_encoded_size(&frame_in); + + mu_assert_int_eq( + expansion_protocol_encode(&frame_in, test_expansion_send_callback, &send_stream), + ExpansionProtocolStatusOk); + mu_assert_int_eq(encoded_size + sizeof(ExpansionFrameChecksum), send_stream.size_sent); + mu_assert_int_eq( + expansion_protocol_get_checksum((const uint8_t*)&frame_in, encoded_size), + encoded_data[encoded_size]); + mu_assert_mem_eq(&frame_in, &encoded_data, encoded_size); + + TestExpansionReceiveStream stream = { + .data_in = encoded_data, + .size_available = send_stream.size_sent, + .size_received = 0, + }; + + ExpansionFrame frame_out; + + mu_assert_int_eq( + expansion_protocol_decode(&frame_out, test_expansion_receive_callback, &stream), + ExpansionProtocolStatusOk); + mu_assert_int_eq(encoded_size + sizeof(ExpansionFrameChecksum), stream.size_received); + mu_assert_mem_eq(&frame_in, &frame_out, encoded_size); +} + +MU_TEST(test_expansion_garbage_input) { + uint8_t garbage_data[EXPANSION_TEST_GARBAGE_BUF_SIZE]; + for(uint32_t i = 0; i < EXPANSION_TEST_GARBAGE_ITERATIONS; ++i) { + furi_hal_random_fill_buf(garbage_data, sizeof(garbage_data)); + size_t remaining_size = EXPANSION_TEST_GARBAGE_MAGIC; + if(expansion_frame_get_remaining_size( + (ExpansionFrame*)garbage_data, sizeof(garbage_data), &remaining_size)) { + // If by chance the garbage data is a valid frame, then the result + // must be 0 because the amount of data provided is more than enough + mu_assert_int_eq(0, remaining_size); + } else { + // If the frame is invalid, the remaining_size parameter should be untouched + mu_assert_int_eq(EXPANSION_TEST_GARBAGE_MAGIC, remaining_size); + } + } +} + +MU_TEST_SUITE(test_expansion_suite) { + MU_RUN_TEST(test_expansion_encoded_size); + MU_RUN_TEST(test_expansion_remaining_size); + MU_RUN_TEST(test_expansion_encode_decode_frame); + MU_RUN_TEST(test_expansion_garbage_input); +} + +int run_minunit_test_expansion() { + MU_RUN_SUITE(test_expansion_suite); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c index 2dbaa4d8689..e3e44291faa 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c +++ b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c @@ -1,8 +1,11 @@ +#include "furi_hal_rtc.h" +#include #include #include #include #include #include "../minunit.h" +#include #define DATA_SIZE 4 #define EEPROM_ADDRESS 0b10101000 @@ -211,6 +214,37 @@ MU_TEST(furi_hal_i2c_ext_eeprom) { } } +MU_TEST(furi_hal_rtc_timestamp2datetime_min) { + uint32_t test_value = 0; + FuriHalRtcDateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; + + FuriHalRtcDateTime result = {0}; + furi_hal_rtc_timestamp_to_datetime(test_value, &result); + + mu_assert_mem_eq(&min_datetime_expected, &result, sizeof(result)); +} + +MU_TEST(furi_hal_rtc_timestamp2datetime_max) { + uint32_t test_value = UINT32_MAX; + FuriHalRtcDateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; + + FuriHalRtcDateTime result = {0}; + furi_hal_rtc_timestamp_to_datetime(test_value, &result); + + mu_assert_mem_eq(&max_datetime_expected, &result, sizeof(result)); +} + +MU_TEST(furi_hal_rtc_timestamp2datetime2timestamp) { + uint32_t test_value = random(); + + FuriHalRtcDateTime datetime = {0}; + furi_hal_rtc_timestamp_to_datetime(test_value, &datetime); + + uint32_t result = furi_hal_rtc_datetime_to_timestamp(&datetime); + + mu_assert_int_eq(test_value, result); +} + MU_TEST_SUITE(furi_hal_i2c_int_suite) { MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); MU_RUN_TEST(furi_hal_i2c_int_1b); @@ -224,8 +258,15 @@ MU_TEST_SUITE(furi_hal_i2c_ext_suite) { MU_RUN_TEST(furi_hal_i2c_ext_eeprom); } +MU_TEST_SUITE(furi_hal_rtc_datetime_suite) { + MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_min); + MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_max); + MU_RUN_TEST(furi_hal_rtc_timestamp2datetime2timestamp); +} + int run_minunit_test_furi_hal() { MU_RUN_SUITE(furi_hal_i2c_int_suite); MU_RUN_SUITE(furi_hal_i2c_ext_suite); + MU_RUN_SUITE(furi_hal_rtc_datetime_suite); return MU_EXIT_CODE; } diff --git a/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c b/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c index 4401cbb4d3c..d5c2433ba04 100644 --- a/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c +++ b/applications/debug/unit_tests/lfrfid/lfrfid_protocols.c @@ -209,6 +209,25 @@ const int8_t indala26_test_timings[INDALA26_EMULATION_TIMINGS_COUNT] = { -1, 1, -1, 1, -1, 1, -1, 1, }; +#define FDXB_TEST_DATA \ + { 0x44, 0x88, 0x23, 0xF2, 0x5A, 0x6F, 0x00, 0x01, 0x00, 0x00, 0x00 } +#define FDXB_TEST_DATA_SIZE 11 +#define FDXB_TEST_EMULATION_TIMINGS_COUNT (206) + +const int8_t fdxb_test_timings[FDXB_TEST_EMULATION_TIMINGS_COUNT] = { + 32, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, + -16, 16, -32, 16, -16, 32, -16, 16, -16, 16, -16, 16, -32, 16, -16, 16, -16, 32, -32, + 16, -16, 16, -16, 16, -16, 32, -16, 16, -16, 16, -16, 16, -32, 16, -16, 16, -16, 32, + -16, 16, -16, 16, -16, 16, -32, 32, -32, 32, -32, 32, -32, 16, -16, 16, -16, 32, -16, + 16, -32, 16, -16, 32, -16, 16, -32, 32, -16, 16, -32, 16, -16, 32, -16, 16, -32, 32, + -16, 16, -32, 32, -32, 32, -32, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, + 16, -16, 16, -16, 32, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, + -32, 32, -32, 32, -32, 32, -32, 16, -16, 32, -32, 32, -16, 16, -16, 16, -32, 32, -32, + 32, -32, 32, -32, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, + -16, 32, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -32, + 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, 16, -16, +}; + MU_TEST(test_lfrfid_protocol_em_read_simple) { ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); mu_assert_int_eq(EM_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolEM4100)); @@ -445,6 +464,73 @@ MU_TEST(test_lfrfid_protocol_inadala26_emulate_simple) { protocol_dict_free(dict); } +MU_TEST(test_lfrfid_protocol_fdxb_emulate_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq(FDXB_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolFDXB)); + mu_assert_string_eq("FDX-B", protocol_dict_get_name(dict, LFRFIDProtocolFDXB)); + mu_assert_string_eq("ISO", protocol_dict_get_manufacturer(dict, LFRFIDProtocolFDXB)); + + const uint8_t data[FDXB_TEST_DATA_SIZE] = FDXB_TEST_DATA; + + protocol_dict_set_data(dict, LFRFIDProtocolFDXB, data, FDXB_TEST_DATA_SIZE); + mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolFDXB)); + + for(size_t i = 0; i < FDXB_TEST_EMULATION_TIMINGS_COUNT; i++) { + LevelDuration level_duration = protocol_dict_encoder_yield(dict, LFRFIDProtocolFDXB); + + if(level_duration_get_level(level_duration)) { + mu_assert_int_eq(fdxb_test_timings[i], level_duration_get_duration(level_duration)); + } else { + mu_assert_int_eq(fdxb_test_timings[i], -level_duration_get_duration(level_duration)); + } + } + + protocol_dict_free(dict); +} + +MU_TEST(test_lfrfid_protocol_fdxb_read_simple) { + ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + mu_assert_int_eq(FDXB_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolFDXB)); + mu_assert_string_eq("FDX-B", protocol_dict_get_name(dict, LFRFIDProtocolFDXB)); + mu_assert_string_eq("ISO", protocol_dict_get_manufacturer(dict, LFRFIDProtocolFDXB)); + + const uint8_t data[FDXB_TEST_DATA_SIZE] = FDXB_TEST_DATA; + + protocol_dict_decoders_start(dict); + + ProtocolId protocol = PROTOCOL_NO; + PulseGlue* pulse_glue = pulse_glue_alloc(); + + for(size_t i = 0; i < FDXB_TEST_EMULATION_TIMINGS_COUNT * 10; i++) { + bool pulse_pop = pulse_glue_push( + pulse_glue, + fdxb_test_timings[i % FDXB_TEST_EMULATION_TIMINGS_COUNT] >= 0, + abs(fdxb_test_timings[i % FDXB_TEST_EMULATION_TIMINGS_COUNT]) * + LF_RFID_READ_TIMING_MULTIPLIER); + + if(pulse_pop) { + uint32_t length, period; + pulse_glue_pop(pulse_glue, &length, &period); + + protocol = protocol_dict_decoders_feed(dict, true, period); + if(protocol != PROTOCOL_NO) break; + + protocol = protocol_dict_decoders_feed(dict, false, length - period); + if(protocol != PROTOCOL_NO) break; + } + } + + pulse_glue_free(pulse_glue); + + mu_assert_int_eq(LFRFIDProtocolFDXB, protocol); + uint8_t received_data[FDXB_TEST_DATA_SIZE] = {0}; + protocol_dict_get_data(dict, protocol, received_data, FDXB_TEST_DATA_SIZE); + + mu_assert_mem_eq(data, received_data, FDXB_TEST_DATA_SIZE); + + protocol_dict_free(dict); +} + MU_TEST_SUITE(test_lfrfid_protocols_suite) { MU_RUN_TEST(test_lfrfid_protocol_em_read_simple); MU_RUN_TEST(test_lfrfid_protocol_em_emulate_simple); @@ -456,6 +542,9 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) { MU_RUN_TEST(test_lfrfid_protocol_ioprox_xsf_emulate_simple); MU_RUN_TEST(test_lfrfid_protocol_inadala26_emulate_simple); + + MU_RUN_TEST(test_lfrfid_protocol_fdxb_read_simple); + MU_RUN_TEST(test_lfrfid_protocol_fdxb_emulate_simple); } int run_minunit_test_lfrfid_protocols() { diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index 60c7abd0323..53894c5514e 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -231,17 +231,17 @@ typedef struct { size_t pos; } SubGhzHalAsyncTxTest; -#define SUBGHZ_HAL_TEST_DURATION 1 +#define SUBGHZ_HAL_TEST_DURATION 3 static LevelDuration subghz_hal_async_tx_test_yield(void* context) { SubGhzHalAsyncTxTest* test = context; bool is_odd = test->pos % 2; if(test->type == SubGhzHalAsyncTxTestTypeNormal) { - if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_reset(); } else { @@ -251,36 +251,36 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) { if(test->pos == 0) { test->pos++; return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + } else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_reset(); } else { furi_crash("Yield after reset"); } } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidMid) { - if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { + if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { test->pos++; return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + } else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_reset(); } else { furi_crash("Yield after reset"); } } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidEnd) { - if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { + if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { test->pos++; return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + } else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { + } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { test->pos++; return level_duration_reset(); } else { @@ -294,20 +294,20 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) { furi_crash("Yield after reset"); } } else if(test->type == SubGhzHalAsyncTxTestTypeResetMid) { - if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { + if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { test->pos++; return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { + } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { test->pos++; return level_duration_reset(); } else { furi_crash("Yield after reset"); } } else if(test->type == SubGhzHalAsyncTxTestTypeResetEnd) { - if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { + if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) { test->pos++; return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); - } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { + } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) { test->pos++; return level_duration_reset(); } else { @@ -334,6 +334,8 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { while(!furi_hal_subghz_is_async_tx_complete()) { if(furi_hal_cortex_timer_is_expired(timer)) { + furi_hal_subghz_stop_async_tx(); + furi_hal_subghz_sleep(); return false; } furi_delay_ms(10); diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index d7afaa3c4f3..7ae9ca03d56 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -29,6 +29,7 @@ int run_minunit_test_bit_lib(); int run_minunit_test_float_tools(); int run_minunit_test_bt(); int run_minunit_test_dialogs_file_browser_options(); +int run_minunit_test_expansion(); typedef int (*UnitTestEntry)(); @@ -60,6 +61,7 @@ const UnitTest unit_tests[] = { {.name = "bt", .entry = run_minunit_test_bt}, {.name = "dialogs_file_browser_options", .entry = run_minunit_test_dialogs_file_browser_options}, + {.name = "expansion", .entry = run_minunit_test_expansion}, }; void minunit_print_progress() { diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index c7083162889..f8ce82bf311 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -18,14 +18,14 @@ #define TAG "SubGhzDeviceCc1101Ext" -#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 +#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO (&gpio_ext_pb2) /* DMA Channels definition */ -#define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2 -#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL LL_DMA_CHANNEL_3 -#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL LL_DMA_CHANNEL_4 -#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL LL_DMA_CHANNEL_5 -#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ FuriHalInterruptIdDma2Ch3 +#define SUBGHZ_DEVICE_CC1101_EXT_DMA (DMA2) +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL (LL_DMA_CHANNEL_3) +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL (LL_DMA_CHANNEL_4) +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL (LL_DMA_CHANNEL_5) +#define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ (FuriHalInterruptIdDma2Ch3) #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF \ SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF \ @@ -34,10 +34,10 @@ SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL /** Low level buffer dimensions and guard times */ -#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256) +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256u) #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF \ (SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL / 2) -#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 << 1 +#define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME (999u >> 1) /** SubGhz state */ typedef enum { @@ -45,7 +45,6 @@ typedef enum { SubGhzDeviceCC1101ExtStateIdle, /**< Idle, energy save mode */ SubGhzDeviceCC1101ExtStateAsyncRx, /**< Async RX started */ SubGhzDeviceCC1101ExtStateAsyncTx, /**< Async TX started, DMA and timer is on */ - SubGhzDeviceCC1101ExtStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ } SubGhzDeviceCC1101ExtState; /** SubGhz regulation, receive transmission on the current frequency for the @@ -55,13 +54,25 @@ typedef enum { SubGhzDeviceCC1101ExtRegulationTxRx, /**TxRx*/ } SubGhzDeviceCC1101ExtRegulation; +typedef enum { + SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle, + SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset, + SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun, +} SubGhzDeviceCC1101ExtAsyncTxMiddlewareState; + +typedef struct { + SubGhzDeviceCC1101ExtAsyncTxMiddlewareState state; + bool is_odd_level; + uint32_t adder_duration; +} SubGhzDeviceCC1101ExtAsyncTxMiddleware; + typedef struct { uint32_t* buffer; - LevelDuration carry_ld; SubGhzDeviceCC1101ExtCallback callback; void* callback_context; uint32_t gpio_tx_buff[2]; uint32_t debug_gpio_buff[2]; + SubGhzDeviceCC1101ExtAsyncTxMiddleware middleware; } SubGhzDeviceCC1101ExtAsyncTx; typedef struct { @@ -259,8 +270,8 @@ void subghz_device_cc1101_ext_dump_state() { void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) { //load config + subghz_device_cc1101_ext_reset(); furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); - cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); uint32_t i = 0; uint8_t pa[8] = {0}; while(preset_data[i]) { @@ -289,8 +300,8 @@ void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) { } void subghz_device_cc1101_ext_load_registers(const uint8_t* data) { + subghz_device_cc1101_ext_reset(); furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); - cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); uint32_t i = 0; while(data[i]) { cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, data[i], data[i + 1]); @@ -371,6 +382,7 @@ void subghz_device_cc1101_ext_reset() { furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + // Warning: push pull cc1101 clock output on GD0 cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); @@ -379,12 +391,18 @@ void subghz_device_cc1101_ext_reset() { void subghz_device_cc1101_ext_idle() { furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); + //waiting for the chip to switch to IDLE mode + furi_check(cc1101_wait_status_state( + subghz_device_cc1101_ext->spi_bus_handle, CC1101StateIDLE, 10000)); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } void subghz_device_cc1101_ext_rx() { furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); cc1101_switch_to_rx(subghz_device_cc1101_ext->spi_bus_handle); + //waiting for the chip to switch to Rx mode + furi_check( + cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateRX, 10000)); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -392,6 +410,9 @@ bool subghz_device_cc1101_ext_tx() { if(subghz_device_cc1101_ext->regulation != SubGhzDeviceCC1101ExtRegulationTxRx) return false; furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); cc1101_switch_to_tx(subghz_device_cc1101_ext->spi_bus_handle); + //waiting for the chip to switch to Tx mode + furi_check( + cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateTX, 10000)); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); return true; } @@ -563,50 +584,90 @@ void subghz_device_cc1101_ext_stop_async_rx() { furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } +void subghz_device_cc1101_ext_async_tx_middleware_idle( + SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware) { + middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle; + middleware->is_odd_level = false; + middleware->adder_duration = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; +} + +static inline uint32_t subghz_device_cc1101_ext_async_tx_middleware_get_duration( + SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware, + SubGhzDeviceCC1101ExtCallback callback) { + uint32_t ret = 0; + bool is_level = false; + + if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset) return 0; + + while(1) { + LevelDuration ld = callback(subghz_device_cc1101_ext->async_tx.callback_context); + if(level_duration_is_reset(ld)) { + middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset; + if(!middleware->is_odd_level) { + return 0; + } else { + return middleware->adder_duration; + } + } else if(level_duration_is_wait(ld)) { + middleware->is_odd_level = !middleware->is_odd_level; + ret = middleware->adder_duration + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; + middleware->adder_duration = 0; + return ret; + } + + is_level = level_duration_get_level(ld); + + if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle) { + if(is_level != middleware->is_odd_level) { + middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun; + middleware->is_odd_level = is_level; + middleware->adder_duration = level_duration_get_duration(ld); + return SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; + } else { + continue; + } + } + + if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun) { + if(is_level == middleware->is_odd_level) { + middleware->adder_duration += level_duration_get_duration(ld); + continue; + } else { + middleware->is_odd_level = is_level; + ret = middleware->adder_duration; + middleware->adder_duration = level_duration_get_duration(ld); + return ret; + } + } + } +} + static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) { furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); - while(samples > 0) { - bool is_odd = samples % 2; - LevelDuration ld; - if(level_duration_is_reset(subghz_device_cc1101_ext->async_tx.carry_ld)) { - ld = subghz_device_cc1101_ext->async_tx.callback( - subghz_device_cc1101_ext->async_tx.callback_context); - } else { - ld = subghz_device_cc1101_ext->async_tx.carry_ld; - subghz_device_cc1101_ext->async_tx.carry_ld = level_duration_reset(); - } - if(level_duration_is_wait(ld)) { - *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; - buffer++; - samples--; - } else if(level_duration_is_reset(ld)) { + while(samples > 0) { + volatile uint32_t duration = subghz_device_cc1101_ext_async_tx_middleware_get_duration( + &subghz_device_cc1101_ext->async_tx.middleware, + subghz_device_cc1101_ext->async_tx.callback); + if(duration == 0) { *buffer = 0; buffer++; samples--; LL_DMA_DisableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); LL_DMA_DisableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); - LL_TIM_EnableIT_UPDATE(TIM17); + if(LL_DMA_IsActiveFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { + LL_DMA_ClearFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA); + } + if(LL_DMA_IsActiveFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { + LL_DMA_ClearFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA); + } break; } else { - bool level = level_duration_get_level(ld); - - // Inject guard time if level is incorrect - if(is_odd != level) { - *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; - buffer++; - samples--; - - // Special case: prevent buffer overflow if sample is last - if(samples == 0) { - subghz_device_cc1101_ext->async_tx.carry_ld = ld; - break; - } - } - - uint32_t duration = level_duration_get_duration(ld); - furi_assert(duration > 0); - *buffer = duration >> 1; + // Lowest possible value is 4us + if(duration < 4) duration = 4; + // Divide by 2 since timer resolution is 2us + // Subtract 1 since we counting from 0 + *buffer = (duration >> 1) - 1; buffer++; samples--; } @@ -635,20 +696,6 @@ static void subghz_device_cc1101_ext_async_tx_dma_isr() { #endif } -static void subghz_device_cc1101_ext_async_tx_timer_isr() { - if(LL_TIM_IsActiveFlag_UPDATE(TIM17)) { - if(LL_TIM_GetAutoReload(TIM17) == 0) { - LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); - furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); - if(subghz_device_cc1101_ext->async_mirror_pin != NULL) - furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false); - LL_TIM_DisableCounter(TIM17); - subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTxEnd; - } - LL_TIM_ClearFlag_UPDATE(TIM17); - } -} - bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context) { furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); furi_assert(callback); @@ -677,7 +724,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | - LL_DMA_MODE_NORMAL); + LL_DMA_PRIORITY_VERYHIGH); LL_DMA_SetDataLength( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMAMUX_REQ_TIM17_UP); @@ -693,16 +740,15 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb // Configure TIM // Set the timer resolution to 2 us - LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); - LL_TIM_SetAutoReload(TIM17, 0xFFFF); LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetAutoReload(TIM17, 500); + LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); LL_TIM_DisableARRPreload(TIM17); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdTim1TrgComTim17, subghz_device_cc1101_ext_async_tx_timer_isr, NULL); - + subghz_device_cc1101_ext_async_tx_middleware_idle( + &subghz_device_cc1101_ext->async_tx.middleware); subghz_device_cc1101_ext_async_tx_refill( subghz_device_cc1101_ext->async_tx.buffer, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); @@ -748,7 +794,6 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb // Start counter LL_TIM_EnableDMAReq_UPDATE(TIM17); - LL_TIM_GenerateEvent_UPDATE(TIM17); subghz_device_cc1101_ext_tx(); @@ -759,19 +804,22 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb } bool subghz_device_cc1101_ext_is_async_tx_complete() { - return subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd; + return ( + (subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx) && + (LL_TIM_GetAutoReload(TIM17) == 0)); } void subghz_device_cc1101_ext_stop_async_tx() { - furi_assert( - subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx || - subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd); + furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); // Shutdown radio subghz_device_cc1101_ext_idle(); + // Deinitialize GPIO + furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); + furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + // Deinitialize Timer - FURI_CRITICAL_ENTER(); furi_hal_bus_disable(FuriHalBusTIM17); furi_hal_interrupt_set_isr(FuriHalInterruptIdTim1TrgComTim17, NULL, NULL); @@ -780,17 +828,11 @@ void subghz_device_cc1101_ext_stop_async_tx() { LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF); furi_hal_interrupt_set_isr(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, NULL, NULL); - // Deinitialize GPIO - furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); - furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - // Stop debug if(subghz_device_cc1101_ext_stop_debug()) { LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF); } - FURI_CRITICAL_EXIT(); - free(subghz_device_cc1101_ext->async_tx.buffer); subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; diff --git a/applications/main/gpio/application.fam b/applications/main/gpio/application.fam index 76391992178..607d97a278a 100644 --- a/applications/main/gpio/application.fam +++ b/applications/main/gpio/application.fam @@ -3,7 +3,7 @@ App( name="GPIO", apptype=FlipperAppType.MENUEXTERNAL, entry_point="gpio_app", - stack_size=1 * 1024, + stack_size=2 * 1024, icon="A_GPIO_14", order=50, fap_libs=["assets"], diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c index 020fbf79a12..06d377d39ff 100644 --- a/applications/main/gpio/gpio_app.c +++ b/applications/main/gpio/gpio_app.c @@ -24,6 +24,9 @@ static void gpio_app_tick_event_callback(void* context) { GpioApp* gpio_app_alloc() { GpioApp* app = malloc(sizeof(GpioApp)); + app->expansion = furi_record_open(RECORD_EXPANSION); + expansion_disable(app->expansion); + app->gui = furi_record_open(RECORD_GUI); app->gpio_items = gpio_items_alloc(); @@ -99,6 +102,9 @@ void gpio_app_free(GpioApp* app) { furi_record_close(RECORD_GUI); furi_record_close(RECORD_NOTIFICATION); + expansion_enable(app->expansion); + furi_record_close(RECORD_EXPANSION); + gpio_items_free(app->gpio_items); free(app); } diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index d54ffd36826..ce4cb6f550b 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -17,8 +17,10 @@ #include "views/gpio_test.h" #include "views/gpio_usb_uart.h" #include +#include struct GpioApp { + Expansion* expansion; Gui* gui; NotificationApp* notifications; ViewDispatcher* view_dispatcher; diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c index 8fcacd40396..f8b142c630a 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart_config.c @@ -46,7 +46,7 @@ void line_ensure_flow_invariant(GpioApp* app) { // selected. This function enforces that invariant by resetting flow_pins // to None if it is configured to 16,15 when LPUART is selected. - uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; + uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalSerialIdLpuart ? 3 : 4; VariableItem* item = app->var_item_flow; variable_item_set_values_count(item, available_flow_pins); @@ -77,9 +77,9 @@ static void line_port_cb(VariableItem* item) { variable_item_set_current_value_text(item, uart_ch[index]); if(index == 0) - app->usb_uart_cfg->uart_ch = FuriHalUartIdUSART1; + app->usb_uart_cfg->uart_ch = FuriHalSerialIdUsart; else if(index == 1) - app->usb_uart_cfg->uart_ch = FuriHalUartIdLPUART1; + app->usb_uart_cfg->uart_ch = FuriHalSerialIdLpuart; line_ensure_flow_invariant(app); view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index 366c5cdc4ef..8dff09cb808 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -29,17 +29,18 @@ typedef enum { WorkerEvtTxStop = (1 << 2), WorkerEvtCdcRx = (1 << 3), + WorkerEvtCdcTxComplete = (1 << 4), - WorkerEvtCfgChange = (1 << 4), + WorkerEvtCfgChange = (1 << 5), - WorkerEvtLineCfgSet = (1 << 5), - WorkerEvtCtrlLineSet = (1 << 6), + WorkerEvtLineCfgSet = (1 << 6), + WorkerEvtCtrlLineSet = (1 << 7), } WorkerEvtFlags; #define WORKER_ALL_RX_EVENTS \ (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \ - WorkerEvtCtrlLineSet) + WorkerEvtCtrlLineSet | WorkerEvtCdcTxComplete) #define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx) struct UsbUartBridge { @@ -50,6 +51,7 @@ struct UsbUartBridge { FuriThread* tx_thread; FuriStreamBuffer* rx_stream; + FuriHalSerialHandle* serial_handle; FuriMutex* usb_mutex; @@ -80,11 +82,23 @@ static const CdcCallbacks cdc_cb = { static int32_t usb_uart_tx_thread(void* context); -static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { +static void usb_uart_on_irq_rx_dma_cb( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent ev, + size_t size, + void* context) { UsbUartBridge* usb_uart = (UsbUartBridge*)context; - if(ev == UartIrqEventRXNE) { - furi_stream_buffer_send(usb_uart->rx_stream, &data, 1, 0); + if(ev & (FuriHalSerialRxEventData | FuriHalSerialRxEventIdle)) { + uint8_t data[FURI_HAL_SERIAL_DMA_BUFFER_SIZE] = {0}; + while(size) { + size_t ret = furi_hal_serial_dma_rx( + handle, + data, + (size > FURI_HAL_SERIAL_DMA_BUFFER_SIZE) ? FURI_HAL_SERIAL_DMA_BUFFER_SIZE : size); + furi_stream_buffer_send(usb_uart->rx_stream, data, ret, 0); + size -= ret; + }; furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtRxDone); } } @@ -116,32 +130,33 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { } static void usb_uart_serial_init(UsbUartBridge* usb_uart, uint8_t uart_ch) { - if(uart_ch == FuriHalUartIdUSART1) { - furi_hal_console_disable(); - } else if(uart_ch == FuriHalUartIdLPUART1) { - furi_hal_uart_init(uart_ch, 115200); - } - furi_hal_uart_set_irq_cb(uart_ch, usb_uart_on_irq_cb, usb_uart); + furi_assert(!usb_uart->serial_handle); + + usb_uart->serial_handle = furi_hal_serial_control_acquire(uart_ch); + furi_assert(usb_uart->serial_handle); + + furi_hal_serial_init(usb_uart->serial_handle, 115200); + furi_hal_serial_dma_rx_start( + usb_uart->serial_handle, usb_uart_on_irq_rx_dma_cb, usb_uart, false); } -static void usb_uart_serial_deinit(UsbUartBridge* usb_uart, uint8_t uart_ch) { - UNUSED(usb_uart); - furi_hal_uart_set_irq_cb(uart_ch, NULL, NULL); - if(uart_ch == FuriHalUartIdUSART1) - furi_hal_console_enable(); - else if(uart_ch == FuriHalUartIdLPUART1) - furi_hal_uart_deinit(uart_ch); +static void usb_uart_serial_deinit(UsbUartBridge* usb_uart) { + furi_assert(usb_uart->serial_handle); + + furi_hal_serial_deinit(usb_uart->serial_handle); + furi_hal_serial_control_release(usb_uart->serial_handle); + usb_uart->serial_handle = NULL; } static void usb_uart_set_baudrate(UsbUartBridge* usb_uart, uint32_t baudrate) { if(baudrate != 0) { - furi_hal_uart_set_br(usb_uart->cfg.uart_ch, baudrate); + furi_hal_serial_set_br(usb_uart->serial_handle, baudrate); usb_uart->st.baudrate_cur = baudrate; } else { struct usb_cdc_line_coding* line_cfg = furi_hal_cdc_get_port_settings(usb_uart->cfg.vcp_ch); if(line_cfg->dwDTERate > 0) { - furi_hal_uart_set_br(usb_uart->cfg.uart_ch, line_cfg->dwDTERate); + furi_hal_serial_set_br(usb_uart->serial_handle, line_cfg->dwDTERate); usb_uart->st.baudrate_cur = line_cfg->dwDTERate; } } @@ -191,7 +206,7 @@ static int32_t usb_uart_worker(void* context) { furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); furi_check(!(events & FuriFlagError)); if(events & WorkerEvtStop) break; - if(events & WorkerEvtRxDone) { + if(events & (WorkerEvtRxDone | WorkerEvtCdcTxComplete)) { size_t len = furi_stream_buffer_receive( usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); if(len > 0) { @@ -223,7 +238,7 @@ static int32_t usb_uart_worker(void* context) { furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); furi_thread_join(usb_uart->tx_thread); - usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); + usb_uart_serial_deinit(usb_uart); usb_uart_serial_init(usb_uart, usb_uart->cfg_new.uart_ch); usb_uart->cfg.uart_ch = usb_uart->cfg_new.uart_ch; @@ -274,7 +289,7 @@ static int32_t usb_uart_worker(void* context) { } } usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); - usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); + usb_uart_serial_deinit(usb_uart); furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); @@ -320,18 +335,10 @@ static int32_t usb_uart_tx_thread(void* context) { if(usb_uart->cfg.software_de_re != 0) furi_hal_gpio_write(USB_USART_DE_RE_PIN, false); - furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, len); + furi_hal_serial_tx(usb_uart->serial_handle, data, len); if(usb_uart->cfg.software_de_re != 0) { - //TODO: FL-3276 port to new USART API - if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) { - while(!LL_USART_IsActiveFlag_TC(USART1)) - ; - } else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) { - while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) - ; - } - + furi_hal_serial_tx_wait_complete(usb_uart->serial_handle); furi_hal_gpio_write(USB_USART_DE_RE_PIN, true); } } @@ -345,6 +352,7 @@ static int32_t usb_uart_tx_thread(void* context) { static void vcp_on_cdc_tx_complete(void* context) { UsbUartBridge* usb_uart = (UsbUartBridge*)context; furi_semaphore_release(usb_uart->tx_sem); + furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCdcTxComplete); } static void vcp_on_cdc_rx(void* context) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_delete_success.c b/applications/main/ibutton/scenes/ibutton_scene_delete_success.c index 9ff165e4a31..6d4ca24c091 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_success.c @@ -9,9 +9,8 @@ void ibutton_scene_delete_success_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); - + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_callback(popup, ibutton_scene_delete_success_popup_callback); popup_set_context(popup, ibutton); popup_set_timeout(popup, 1500); diff --git a/applications/main/ibutton/scenes/ibutton_scene_save_success.c b/applications/main/ibutton/scenes/ibutton_scene_save_success.c index 8b16d2929a5..6652ff7c5f2 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_save_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_save_success.c @@ -9,9 +9,8 @@ void ibutton_scene_save_success_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); - + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_callback(popup, ibutton_scene_save_success_popup_callback); popup_set_context(popup, ibutton); popup_set_timeout(popup, 1500); diff --git a/applications/main/ibutton/scenes/ibutton_scene_write_success.c b/applications/main/ibutton/scenes/ibutton_scene_write_success.c index 17cd53d08d6..b36bccfbcbf 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write_success.c @@ -10,7 +10,7 @@ void ibutton_scene_write_success_on_enter(void* context) { iButton* ibutton = context; Popup* popup = ibutton->popup; - popup_set_icon(popup, 0, 12, &I_iButtonDolphinVerySuccess_108x52); + popup_set_icon(popup, 0, 9, &I_iButtonDolphinVerySuccess_92x55); popup_set_text(popup, "Successfully written!", 40, 12, AlignLeft, AlignBottom); popup_set_callback(popup, ibutton_scene_write_success_popup_callback); diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 201a2930570..64d473deff1 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -712,3 +712,79 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 8425 4291 542 1401 544 538 542 1404 518 1427 544 534 570 540 516 1434 542 1399 544 539 544 539 541 1403 541 1402 516 570 517 1427 545 566 511 1406 517 1431 542 534 544 1406 543 536 517 567 543 1404 542 1400 595 525 544 1405 542 534 516 1433 516 1428 544 537 544 535 543 1403 542 505 488 21084 8424 4298 566 535 569 1402 567 533 568 537 570 1403 570 1403 565 534 566 537 566 1402 569 1398 569 533 569 538 592 1446 569 535 570 1400 569 567 535 535 568 1437 568 533 568 1398 569 1402 565 533 567 534 569 1403 568 536 569 1402 568 535 567 534 571 1403 568 1415 566 536 571 1362 489 21084 8403 4313 516 1439 517 574 515 1442 515 1441 518 573 516 574 567 1397 514 1440 515 573 516 575 516 1443 515 1439 518 574 516 1440 517 608 535 1396 517 1441 517 579 515 1438 515 576 517 578 568 1390 569 1391 516 575 518 1439 516 573 517 1445 566 1391 516 571 517 572 516 1441 514 543 487 +# +# Model: Samsung DB93 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17837 3089 8903 555 445 578 1411 583 442 554 442 529 468 528 468 554 443 554 442 554 444 552 1441 552 472 523 475 522 1473 522 1473 522 475 547 1447 548 1446 548 1446 548 1446 548 1446 548 449 548 450 547 451 546 474 523 476 521 476 521 476 522 476 521 475 546 451 547 450 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 450 547 451 546 474 523 474 523 476 521 477 520 477 520 477 521 476 521 476 546 450 547 450 547 450 546 450 547 451 546 451 546 1448 546 1472 522 2982 3005 8963 522 1499 495 502 495 502 495 502 496 501 496 501 521 476 522 475 522 475 522 1472 522 475 522 475 522 1473 521 475 522 1473 522 1499 495 1500 494 1500 496 1499 521 1473 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 502 495 503 494 503 495 502 495 501 496 501 521 476 522 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 503 494 503 494 503 493 504 494 502 495 502 495 502 520 477 520 2984 3003 8966 519 1475 520 477 520 477 520 477 520 478 519 477 520 478 519 479 518 504 493 1502 493 504 493 503 494 503 494 1501 519 1476 518 1475 519 478 519 1476 518 1476 519 1477 517 1501 493 1503 491 1503 493 1502 493 1502 518 479 518 479 518 479 518 1476 518 1477 517 1477 517 504 493 504 493 504 493 531 466 507 490 1529 467 506 491 529 468 1527 492 1502 492 505 493 1502 492 1502 492 505 492 504 493 504 492 505 492 506 491 532 465 532 465 531 467 530 467 530 467 1528 467 1527 492 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17829 3090 8902 556 444 579 1410 584 441 529 468 529 468 554 443 554 442 555 443 553 444 551 1442 551 474 522 475 522 1473 522 475 522 475 547 1448 547 1447 547 1447 547 1447 548 1447 547 449 548 450 547 474 523 474 523 476 521 476 521 476 521 477 521 475 522 476 546 450 547 449 548 449 548 450 547 449 548 450 547 450 547 450 547 451 546 474 523 474 523 474 523 479 518 479 518 479 518 501 496 501 496 477 545 475 522 452 545 474 523 474 523 1472 522 1472 522 1472 522 1472 523 2981 3005 8990 494 1499 495 502 496 501 496 501 496 501 522 475 522 475 522 475 522 475 522 1473 521 475 522 475 522 1473 521 475 522 1473 521 1500 494 1500 495 1499 520 1474 522 1473 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 476 521 476 521 477 520 503 494 503 494 503 495 502 495 502 520 477 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 504 493 503 494 503 494 503 495 502 495 502 495 502 520 477 520 477 520 2984 3003 8966 519 1475 519 477 520 478 519 478 519 478 519 479 518 503 494 505 492 505 492 1503 493 504 493 504 493 504 518 1477 518 1476 518 1476 518 479 518 1477 517 1477 517 1501 493 1504 490 1528 468 1527 468 1527 493 1501 493 504 493 504 493 504 493 1501 493 1502 492 1502 492 504 493 505 491 532 440 556 466 531 467 530 468 530 467 530 467 1527 492 1503 491 505 492 504 493 504 493 505 491 1503 492 505 467 530 492 506 466 557 464 533 464 532 466 1528 467 1528 467 1528 466 1528 491 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17831 3089 8903 581 420 578 1411 583 442 529 468 528 468 554 443 554 442 554 443 553 445 551 1443 550 475 521 475 522 1473 522 475 547 449 548 1447 548 1446 548 1446 548 1446 548 1446 548 449 548 449 548 450 547 473 524 476 521 475 522 476 520 476 522 475 522 475 547 449 549 449 548 449 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 474 523 474 523 476 521 476 521 476 520 477 521 476 546 451 547 450 547 450 547 449 548 450 547 1447 547 1448 546 1448 546 1472 523 2957 3029 8967 518 1477 517 502 495 501 496 501 521 475 522 475 522 475 522 475 522 475 522 1472 522 474 523 475 522 1473 521 475 522 1472 522 1499 495 1500 495 1499 520 1474 522 1473 521 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 502 495 502 495 503 494 502 496 501 496 501 521 476 522 476 521 475 522 475 522 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 503 494 503 494 503 494 503 495 502 495 502 520 477 520 476 521 476 521 2984 3003 8965 520 1474 521 477 520 477 520 477 520 477 520 477 520 478 519 504 493 504 493 1502 494 503 494 502 495 1500 520 1475 519 1475 519 1475 519 478 519 1475 519 1476 518 1501 493 1502 492 1503 493 1501 494 1501 518 1476 518 478 519 478 519 478 519 1476 518 1476 518 1477 517 504 493 506 491 506 491 506 491 506 492 505 492 504 493 504 518 481 516 1501 493 504 493 504 493 480 517 1501 493 504 493 504 493 504 493 505 491 532 466 531 466 532 466 1528 467 1527 468 1527 492 1502 492 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 667 17832 3088 8903 575 448 555 1411 583 441 529 468 529 468 554 443 554 442 554 443 553 444 551 1443 550 474 522 475 522 1473 522 475 522 475 547 1447 548 1446 548 1446 548 1446 548 1447 547 449 548 449 548 474 523 474 523 476 521 476 521 476 521 477 521 475 522 475 547 450 548 449 548 450 547 449 548 449 548 450 547 450 547 449 547 451 546 452 545 474 523 474 523 477 520 478 519 477 519 478 520 477 545 452 545 451 547 451 546 474 523 474 523 1450 544 1472 522 1472 522 1472 523 2981 3005 8990 494 1499 495 502 496 501 496 501 521 476 521 475 522 475 522 475 522 475 522 1473 521 475 522 475 522 1473 521 476 521 1473 521 1500 494 1500 495 1499 495 1499 521 1473 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 476 521 476 521 477 520 503 494 503 494 503 495 502 495 502 520 477 520 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 477 520 478 519 503 493 504 493 504 494 503 494 502 519 478 520 477 520 477 520 2984 3003 8966 519 1475 519 477 520 477 520 478 519 478 519 478 519 503 494 504 493 505 492 1503 493 504 493 504 493 503 519 478 519 1476 518 1476 518 478 519 1476 518 1477 517 1501 493 1504 490 1504 490 1503 493 1502 493 1502 517 480 517 504 493 480 517 1477 517 1502 492 1502 492 504 493 504 493 504 493 531 466 531 466 1529 467 1527 467 1527 492 504 493 1502 492 505 492 504 493 505 492 1503 492 505 492 505 492 505 467 557 464 532 441 556 466 532 466 1528 466 1528 491 1503 491 1503 466 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 665 17827 3090 8902 556 444 579 1412 583 441 530 467 529 468 554 442 555 442 554 442 554 444 551 1443 550 473 523 475 522 1472 521 475 523 474 523 1471 549 1446 548 1445 549 1446 548 1445 549 448 549 448 549 449 548 473 523 473 524 475 522 475 522 475 523 475 522 474 548 449 548 449 549 448 549 448 549 448 549 448 549 448 549 449 548 449 548 449 548 450 547 473 524 474 523 476 521 476 521 476 521 476 522 475 547 450 547 450 548 449 548 449 548 1447 547 1447 547 1447 547 1448 546 2957 3030 8962 522 1475 519 501 496 501 497 478 519 500 522 475 522 475 523 474 523 474 523 1472 522 474 523 474 523 1472 522 475 522 1472 522 1499 495 1499 496 1499 496 1498 522 1473 522 474 522 475 522 475 522 474 523 475 522 475 522 475 522 475 522 475 522 475 522 502 495 502 495 502 495 501 496 501 496 501 521 476 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 476 521 502 495 502 495 502 495 503 495 502 495 501 521 476 521 476 521 2983 3004 8964 521 1474 520 476 521 476 521 477 520 476 521 477 520 477 520 503 494 503 494 1501 494 502 495 502 495 502 520 477 521 1474 520 1474 520 477 520 1474 520 1475 519 1475 519 1500 494 1502 492 1502 493 1501 494 1500 519 478 519 477 520 477 520 1475 519 1475 519 1475 519 478 519 478 519 503 494 505 492 505 492 505 492 1503 493 1502 493 1502 517 1476 518 479 518 479 518 479 518 480 517 479 518 1501 493 504 493 504 493 530 467 531 466 531 467 1527 468 1527 491 1502 493 1501 493 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 668 17822 3064 8902 582 442 555 1413 581 442 528 468 529 494 528 469 502 495 527 470 526 471 525 1468 552 446 550 448 549 1446 548 448 549 448 549 1446 548 1448 546 1471 523 1473 521 1473 521 476 521 475 522 475 547 449 548 449 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 474 523 474 523 474 523 477 520 477 520 477 520 477 521 476 521 476 546 450 548 450 547 474 523 474 523 450 547 474 523 474 523 452 545 474 523 474 523 474 523 1500 494 1499 495 1499 496 1498 522 2955 3032 8963 521 1472 522 475 522 474 523 475 522 475 522 475 522 475 522 475 522 475 522 1499 495 502 495 502 495 1499 496 501 521 1473 522 1473 522 1472 522 1472 522 1473 521 1473 522 475 522 475 522 502 495 502 495 502 495 503 495 501 496 501 496 501 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 504 493 503 494 503 494 503 495 502 495 502 495 502 520 477 521 476 521 476 521 476 521 477 520 477 520 477 520 476 521 477 520 2984 3003 8967 518 1502 492 504 493 504 494 503 494 503 494 503 519 478 519 478 519 478 519 1476 518 478 518 479 518 478 519 478 519 1501 493 1501 493 506 491 1504 491 1527 468 1503 492 1526 493 1501 493 1501 494 1501 493 1501 493 504 493 504 493 504 493 1528 466 1529 466 1528 467 529 468 529 493 504 493 504 493 504 493 1502 492 1502 492 1502 467 529 493 1502 491 533 465 532 465 532 465 531 467 530 467 1528 467 530 467 530 467 530 467 530 467 530 467 1527 467 1528 466 1528 466 1529 492 +# +# Model: Samsung AR-EH04 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 663 17769 3057 8901 529 492 527 1434 556 465 529 464 529 464 555 439 555 438 556 438 554 440 553 1435 552 444 549 470 524 1465 524 1466 522 473 522 1467 522 1466 549 1440 549 1440 548 1440 548 445 549 445 549 446 548 447 547 471 523 470 524 471 523 472 522 472 521 473 522 472 522 472 523 472 548 446 549 445 548 446 548 447 547 447 547 446 548 447 547 470 524 471 523 471 523 471 523 471 523 498 496 475 519 498 496 497 498 497 522 472 523 471 524 470 523 471 523 1443 546 1442 547 2947 3023 8935 522 1466 522 471 523 472 522 474 520 498 496 498 496 498 497 497 497 497 523 1466 523 471 523 471 523 1466 523 471 523 1466 522 1467 522 1467 522 1493 495 1493 496 1493 497 497 522 472 523 471 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 499 495 499 495 499 495 499 495 499 496 498 522 472 523 472 523 471 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 473 521 499 495 500 494 499 495 499 496 499 521 2947 3024 8937 521 1467 521 473 521 473 521 472 521 473 521 473 521 473 521 473 521 474 520 1469 520 500 494 500 494 1495 495 500 495 499 520 474 521 1468 520 1468 521 1468 521 1468 521 1468 521 1469 519 1470 518 1495 493 1496 493 500 495 499 520 474 521 1468 520 1469 520 1469 520 473 521 474 520 474 520 475 519 476 518 500 494 502 492 502 491 1497 493 1495 495 499 495 499 520 474 520 475 519 475 519 475 519 475 519 475 519 500 494 501 493 501 493 501 493 501 493 1498 491 1497 519 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 665 17760 3083 8875 553 468 527 1432 584 438 555 439 529 464 555 439 555 438 556 438 555 439 553 1435 552 443 550 470 524 1466 523 471 522 473 521 1467 523 1466 549 1439 549 1440 549 1439 549 445 549 445 549 445 549 446 548 470 524 470 524 471 523 472 522 472 522 473 521 472 522 472 547 447 548 445 549 445 549 445 549 445 549 446 548 445 549 445 549 447 547 470 524 471 523 471 523 471 523 473 521 473 521 473 521 473 521 472 523 472 548 446 548 1441 547 1441 548 1441 547 1441 547 2947 3023 8935 522 1466 522 472 522 474 519 475 519 475 519 474 521 473 546 448 547 448 546 1465 523 449 545 448 546 1466 522 471 523 1467 522 1466 522 1493 495 1493 495 1493 496 1493 522 471 523 471 523 471 523 471 523 471 523 471 523 471 523 472 522 472 522 472 522 472 522 472 522 499 495 498 495 499 496 498 496 498 496 498 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 499 495 500 494 500 495 499 495 499 495 498 522 2947 3023 8937 520 1467 521 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1495 494 500 494 500 495 500 494 1494 520 1468 521 1467 521 473 521 1468 521 1468 520 1469 519 1469 519 1471 517 1495 494 1495 494 1494 520 474 521 473 521 473 520 1468 521 1468 521 1468 520 474 520 475 519 475 519 500 493 500 494 501 493 501 493 501 493 1495 495 1494 520 474 520 474 520 474 520 475 519 1470 518 475 519 476 518 500 493 501 493 501 493 501 493 1497 492 1497 492 1496 493 1495 518 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 666 17765 3056 8901 529 492 526 1434 556 465 529 464 530 464 555 438 556 438 555 439 554 440 552 1436 551 443 550 470 524 1465 524 471 523 472 522 1467 521 1467 548 1441 549 1440 548 1440 548 445 549 445 549 445 549 446 548 447 547 470 524 471 523 471 523 473 521 472 522 473 521 473 521 473 522 472 548 446 549 446 548 446 548 447 547 447 547 470 524 447 547 471 523 471 523 471 523 471 523 471 523 475 519 498 496 498 496 498 496 497 498 497 523 1466 524 1443 545 1441 548 1442 546 2947 3023 8935 522 1466 522 472 522 472 522 498 496 498 496 498 496 498 496 498 522 472 523 1466 522 471 523 471 523 1466 523 471 523 1466 523 1467 522 1467 521 1493 496 1493 495 1493 497 497 522 472 523 471 523 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 496 498 522 472 522 472 523 472 521 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 474 520 500 494 500 494 500 495 500 495 499 521 2947 3024 8937 520 1467 521 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1469 520 500 494 500 493 1496 494 1494 495 1494 520 1468 520 473 521 1469 519 1468 521 1469 519 1470 519 1470 519 1495 493 1496 493 1495 495 499 520 474 520 474 520 1469 520 1469 519 1469 519 474 520 475 519 500 494 500 494 500 494 502 492 502 492 502 493 501 493 1496 494 500 519 475 520 475 518 1470 519 475 518 476 518 475 519 476 494 525 493 501 493 501 493 1498 491 1498 491 1497 493 1496 518 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 664 17763 3082 8874 553 444 552 1433 583 438 529 464 530 464 555 439 555 438 556 438 555 440 553 1435 552 443 550 470 524 1466 523 472 521 472 522 1467 523 1466 549 1439 549 1439 549 1439 550 444 550 445 549 445 549 447 547 470 524 471 523 471 523 472 522 472 522 473 521 472 523 472 522 471 549 446 549 445 549 446 548 446 548 446 548 446 548 446 548 448 546 471 523 471 523 471 523 471 523 475 519 497 497 474 520 475 520 497 497 496 524 471 524 1465 523 1442 547 1442 547 1442 547 2946 3024 8935 522 1466 522 471 523 474 520 474 520 498 496 474 521 497 522 471 523 471 523 1465 523 471 523 471 523 1466 522 471 523 1466 523 1466 523 1493 495 1493 495 1493 496 1492 523 471 523 471 523 471 523 471 523 471 523 471 523 471 523 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 496 498 497 498 497 497 523 472 522 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 499 495 499 495 499 496 499 496 498 496 498 522 2946 3024 8937 521 1467 521 472 522 472 522 473 521 473 521 473 521 473 521 474 520 474 520 1495 493 500 494 499 495 499 495 499 496 1493 521 1468 520 473 521 1468 520 1468 521 1468 520 1469 519 1470 518 1495 494 1495 494 1494 495 499 520 473 521 473 521 1468 520 1469 520 1468 521 474 520 474 520 475 519 475 519 476 518 1496 492 1496 494 1495 495 499 520 1469 520 474 520 474 520 474 519 1470 520 474 520 476 518 476 519 477 517 500 494 500 494 503 491 1497 492 1496 493 1495 519 1470 519 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 666 17758 3056 8901 528 493 526 1434 580 441 554 439 529 465 554 439 555 439 555 439 554 440 553 1435 552 443 549 470 524 1465 524 472 522 472 521 1467 523 1467 548 1440 549 1440 548 1440 549 445 549 445 549 446 548 446 548 471 523 471 523 471 523 471 523 473 521 473 521 473 521 473 522 472 547 446 549 446 549 445 548 446 548 446 548 446 548 446 548 447 547 471 523 471 523 471 523 471 523 473 521 474 520 473 521 474 521 473 522 473 546 447 548 1441 548 1442 547 1442 547 1442 546 2948 3021 8936 521 1466 522 472 522 498 496 499 494 476 519 476 519 497 497 497 523 472 522 1466 523 471 523 472 522 1466 522 472 522 1466 522 1467 522 1467 522 1493 494 1495 495 1493 522 472 522 472 522 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 499 495 499 495 499 495 499 496 498 521 473 522 472 523 472 522 472 522 472 522 473 521 472 522 472 522 472 522 473 521 473 521 474 520 473 521 499 495 500 494 500 495 499 495 499 521 2947 3023 8938 520 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1495 494 500 494 500 494 500 495 500 494 1494 520 1468 521 474 520 1468 520 1469 520 1468 520 1469 520 1470 518 1496 493 1496 493 1495 495 499 519 475 520 474 520 1468 520 1469 519 1469 519 474 520 475 519 475 519 476 518 500 494 500 494 1497 492 1497 492 1496 493 1495 519 475 519 475 519 475 519 475 519 475 519 1471 518 476 518 500 494 501 493 501 493 501 493 1498 491 1498 491 1496 518 1472 517 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 664 17757 3057 8901 528 469 550 1434 556 465 553 440 529 465 554 439 555 439 555 438 555 440 553 1435 552 443 550 470 524 1466 522 472 522 472 521 1467 523 1466 549 1440 549 1440 548 1440 548 445 549 445 549 445 549 446 548 470 524 471 523 471 523 471 523 472 522 472 522 473 522 472 523 472 548 446 549 445 550 445 548 446 548 446 548 446 548 446 548 447 547 470 524 471 523 471 523 471 523 471 523 474 519 474 521 474 521 473 522 473 546 447 547 1441 548 1442 546 1442 547 1442 546 2947 3023 8935 522 1466 522 472 522 498 496 498 495 499 496 498 496 498 521 473 522 471 523 1466 522 471 523 471 522 1466 523 471 523 1467 522 1467 522 1467 521 1493 495 1494 496 1493 521 473 522 472 522 471 523 472 522 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 522 473 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 499 495 500 494 500 495 499 496 498 522 2947 3023 8937 521 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1470 518 500 494 500 494 500 494 500 494 1494 521 1468 521 473 521 1468 520 1468 520 1469 520 1469 520 1470 518 1495 493 1496 493 1495 494 500 519 475 520 474 520 1469 519 1469 520 1469 519 474 520 474 520 475 519 477 517 500 494 1496 493 1496 493 1496 493 500 495 1495 519 475 518 475 519 475 518 476 518 475 519 1470 519 500 494 500 494 501 493 501 493 501 493 1499 490 1497 493 1497 492 1496 518 diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c index 9205db4c4e7..6515834537f 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete_done.c @@ -4,9 +4,8 @@ void infrared_scene_edit_delete_done_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); - + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_callback(popup, infrared_popup_closed_callback); popup_set_context(popup, context); popup_set_timeout(popup, 1500); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c index 35f51598941..d7332c151c5 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename_done.c @@ -4,9 +4,8 @@ void infrared_scene_edit_rename_done_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); - + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_callback(popup, infrared_popup_closed_callback); popup_set_context(popup, context); popup_set_timeout(popup, 1500); diff --git a/applications/main/infrared/scenes/infrared_scene_learn_done.c b/applications/main/infrared/scenes/infrared_scene_learn_done.c index b4eb38331d0..a0c605b0b5a 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_done.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_done.c @@ -4,12 +4,12 @@ void infrared_scene_learn_done_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - if(infrared->app_state.is_learning_new_remote) { + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop); } else { - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); } popup_set_callback(popup, infrared_popup_closed_callback); diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index ce3e987e80e..af340008a81 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -25,10 +25,13 @@ void lfrfid_on_system_start() { static void lfrfid_cli_print_usage() { printf("Usage:\r\n"); - printf("rfid read \r\n"); - printf("rfid \r\n"); - printf("rfid raw_read \r\n"); - printf("rfid raw_emulate \r\n"); + printf("rfid read - read in ASK/PSK mode\r\n"); + printf("rfid - write or emulate a card\r\n"); + printf("rfid raw_read - read and save raw data to a file\r\n"); + printf( + "rfid raw_emulate - emulate raw data (not very useful, but helps debug protocols)\r\n"); + printf( + "rfid raw_analyze - outputs raw data to the cli and tries to decode it (useful for protocol development)\r\n"); }; typedef struct { diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c index f940b9bd436..abb173a73d7 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_delete_success.c @@ -4,8 +4,8 @@ void lfrfid_scene_delete_success_on_enter(void* context) { LfRfid* app = context; Popup* popup = app->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_context(popup, app); popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_timeout(popup, 1500); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c index 52aefa84899..2f5d5ae9ff8 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_save_success.c @@ -7,8 +7,8 @@ void lfrfid_scene_save_success_on_enter(void* context) { // Clear state of data enter scene scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0); - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_context(popup, app); popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_timeout(popup, 1500); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_write.c b/applications/main/lfrfid/scenes/lfrfid_scene_write.c index b7faed69ff4..f6e762e4d8d 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_write.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_write.c @@ -57,7 +57,7 @@ bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess); consumed = true; } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) { - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop); popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop); notification_message(app->notifications, &sequence_blink_start_red); @@ -65,7 +65,7 @@ bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { } else if( (event.event == LfRfidEventWriteFobCannotBeWritten) || (event.event == LfRfidEventWriteTooLongToWrite)) { - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop); popup_set_text( popup, diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c index 52e30d6b666..78ba4813701 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_write_success.c @@ -4,8 +4,8 @@ void lfrfid_scene_write_success_on_enter(void* context) { LfRfid* app = context; Popup* popup = app->popup; - popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); + popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); popup_set_context(popup, app); popup_set_callback(popup, lfrfid_popup_timeout_callback); popup_set_timeout(popup, 1500); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index ecb61fe60d2..b92d0ebf1ab 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -65,6 +65,15 @@ App( sources=["plugins/supported_cards/troika.c"], ) +App( + appid="washcity_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="washcity_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/washcity.c"], +) + App( appid="plantain_parser", apptype=FlipperAppType.PLUGIN, @@ -101,6 +110,15 @@ App( sources=["plugins/supported_cards/umarsh.c"], ) +App( + appid="hid_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hid_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hid.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c index 92bdb22dc97..bb2ab92d39f 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c @@ -18,20 +18,20 @@ void nfc_render_iso15693_3_info( } void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) { - furi_string_cat_printf(str, "UID:"); + furi_string_cat_printf(str, "UID:\n"); size_t uid_len; const uint8_t* uid = iso15693_3_get_uid(data, &uid_len); for(size_t i = 0; i < uid_len; i++) { - furi_string_cat_printf(str, " %02X", uid[i]); + furi_string_cat_printf(str, "%02X ", uid[i]); } if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { const uint16_t block_count = iso15693_3_get_block_count(data); const uint8_t block_size = iso15693_3_get_block_size(data); - furi_string_cat_printf(str, "Memory: %u bytes\n", block_count * block_size); + furi_string_cat_printf(str, "\nMemory: %u bytes\n", block_count * block_size); furi_string_cat_printf(str, "(%u blocks x %u bytes)", block_count, block_size); } } diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 3e0468cd91e..7feeccf22e9 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -23,6 +23,8 @@ static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { FuriString* temp_str = furi_string_alloc(); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + furi_string_replace(temp_str, "Mifare", "MIFARE"); + nfc_render_mf_classic_info(data, NfcProtocolFormatTypeFull, temp_str); widget_add_text_scroll_element( @@ -119,13 +121,15 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { } } -static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { +static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { //-V524 const NfcDevice* device = instance->nfc_device; const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); FuriString* temp_str = furi_string_alloc(); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + furi_string_replace(temp_str, "Mifare", "MIFARE"); + nfc_render_mf_classic_info(data, NfcProtocolFormatTypeShort, temp_str); widget_add_text_scroll_element( @@ -168,7 +172,7 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { if(event == SubmenuIndexDetectReader) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); dolphin_deed(DolphinDeedNfcDetectReader); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3e27fc539e6..4a8d4d74475 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -52,9 +52,15 @@ static NfcCommand if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + uint32_t event = (data->pages_read == data->pages_total) ? NfcCustomEventPollerSuccess : + NfcCustomEventPollerIncomplete; + view_dispatcher_send_custom_event(instance->view_dispatcher, event); return NfcCommandStop; } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); nfc_device_set_data( instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); const MfUltralightData* data = @@ -90,10 +96,55 @@ static NfcCommand return NfcCommandContinue; } +enum { + NfcSceneMfUltralightReadMenuStateCardSearch, + NfcSceneMfUltralightReadMenuStateCardFound, +}; + +static void nfc_scene_read_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); + + if(state == NfcSceneMfUltralightReadMenuStateCardSearch) { + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + } else { + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 20, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { + bool unlocking = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); + + uint32_t state = unlocking ? NfcSceneMfUltralightReadMenuStateCardSearch : + NfcSceneMfUltralightReadMenuStateCardFound; + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); + + nfc_scene_read_setup_view(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); } +bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { + if(event == NfcCustomEventCardDetected) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); + } else if((event == NfcCustomEventPollerIncomplete)) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + return true; +} + static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { Submenu* submenu = instance->submenu; @@ -179,7 +230,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { .scene_read = { .on_enter = nfc_scene_read_on_enter_mf_ultralight, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_read_on_event_mf_ultralight, }, .scene_read_menu = { diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 38ead0ac3fa..c87ee613f56 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -146,8 +146,7 @@ static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) { // SceneRead static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { - popup_set_header( - instance->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 12, 23, &A_Loading_24); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); @@ -162,7 +161,7 @@ static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { // Start poller with the appropriate callback nfc_protocol_support[protocol]->scene_read.on_enter(instance); - nfc_blink_detect_start(instance); + nfc_blink_read_start(instance); } static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneManagerEvent event) { @@ -200,6 +199,10 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana instance->scene_manager, NfcSceneDetect); } consumed = true; + } else if(event.event == NfcCustomEventCardDetected) { + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); } } else if(event.type == SceneManagerEventTypeBack) { nfc_poller_stop(instance->poller); @@ -388,12 +391,15 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance); // Trailer submenu items - submenu_add_item( - submenu, - "Info", - SubmenuIndexCommonInfo, - nfc_protocol_support_common_submenu_callback, - instance); + if(nfc_has_shadow_file(instance)) { + submenu_add_item( + submenu, + "Restore to Original State", + SubmenuIndexCommonRestore, + nfc_protocol_support_common_submenu_callback, + instance); + } + submenu_add_item( submenu, "Rename", @@ -406,15 +412,12 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { SubmenuIndexCommonDelete, nfc_protocol_support_common_submenu_callback, instance); - - if(nfc_has_shadow_file(instance)) { - submenu_add_item( - submenu, - "Restore Data Changes", - SubmenuIndexCommonRestore, - nfc_protocol_support_common_submenu_callback, - instance); - } + submenu_add_item( + submenu, + "Info", + SubmenuIndexCommonInfo, + nfc_protocol_support_common_submenu_callback, + instance); submenu_set_selected_item( instance->submenu, @@ -579,8 +582,14 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { } else { widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating"); - furi_string_set( - temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + if(!furi_string_empty(instance->file_name)) { + furi_string_set(temp_str, instance->file_name); + } else { + furi_string_printf( + temp_str, + "Unsaved\n%s", + nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + } } widget_add_text_box_element( diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index bf15161aa08..183f498951c 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -223,7 +223,7 @@ void nfc_text_store_clear(NfcApp* nfc) { } void nfc_blink_read_start(NfcApp* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_cyan); + notification_message(nfc->notifications, &sequence_blink_start_yellow); } void nfc_blink_emulate_start(NfcApp* nfc) { @@ -231,7 +231,7 @@ void nfc_blink_emulate_start(NfcApp* nfc) { } void nfc_blink_detect_start(NfcApp* nfc) { - notification_message(nfc->notifications, &sequence_blink_start_yellow); + notification_message(nfc->notifications, &sequence_blink_start_cyan); } void nfc_blink_stop(NfcApp* nfc) { diff --git a/applications/main/nfc/plugins/supported_cards/hid.c b/applications/main/nfc/plugins/supported_cards/hid.c new file mode 100644 index 00000000000..66ced4d0c51 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hid.c @@ -0,0 +1,153 @@ +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include + +#define TAG "HID" + +static const uint64_t hid_key = 0x484944204953; + +bool hid_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t verify_sector = 1; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key = {}; + nfc_util_num2bytes(hid_key, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); + + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool hid_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static uint8_t get_bit_length(const uint8_t* half_block) { + uint8_t bitLength = 0; + uint32_t* halves = (uint32_t*)half_block; + if(halves[0] == 0) { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); + bitLength = 31 - leading0s; + } else { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); + bitLength = 63 - leading0s; + } + + return bitLength; +} + +static uint64_t get_pacs_bits(const uint8_t* block, uint8_t bitLength) { + // Remove sentinel bit from credential. Byteswapping to handle array of bytes vs 64bit value + uint64_t sentinel = __builtin_bswap64(1ULL << bitLength); + uint64_t swapped = 0; + memcpy(&swapped, block, sizeof(uint64_t)); + swapped = __builtin_bswap64(swapped ^ sentinel); + FURI_LOG_D(TAG, "PACS: (%d) %016llx", bitLength, swapped); + return swapped; +} + +static bool hid_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // verify key + const uint8_t verify_sector = 1; + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + if(key != hid_key) break; + + // Currently doesn't support bit length > 63 + const uint8_t* credential_block = data->block[5].data + 8; + + uint8_t bitLength = get_bit_length(credential_block); + if(bitLength == 0) break; + + uint64_t credential = get_pacs_bits(credential_block, bitLength); + if(credential == 0) break; + + furi_string_printf(parsed_data, "\e#HID Card\n%dbit\n%llx", bitLength, credential); + + parsed = true; + + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hid_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hid_verify, + .read = hid_read, + .parse = hid_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hid_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hid_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hid_plugin_ep() { + return &hid_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/mykey.c b/applications/main/nfc/plugins/supported_cards/mykey.c index a0e206f9c8e..1b7493307cd 100644 --- a/applications/main/nfc/plugins/supported_cards/mykey.c +++ b/applications/main/nfc/plugins/supported_cards/mykey.c @@ -16,6 +16,35 @@ static bool mykey_has_lockid(const St25tbData* data) { return (data->blocks[5] & 0xFF) == 0x7F; } +static bool check_invalid_low_nibble(uint8_t value) { + uint8_t value_lo = value & 0xF; + return value_lo >= 0xA; +} + +static bool mykey_get_production_date( + const St25tbData* data, + uint16_t* year_ptr, + uint8_t* month_ptr, + uint8_t* day_ptr) { + uint32_t date_block = data->blocks[8]; + uint8_t year = date_block >> 16 & 0xFF; + uint8_t month = date_block >> 8 & 0xFF; + uint8_t day = date_block & 0xFF; + // dates are coded in a peculiar way, the hexadecimal value should in fact be interpreted as a decimal value + // so anything in range A-F is invalid. + if(day > 0x31 || month > 0x12 || day == 0 || month == 0 || year == 0) { + return false; + } + if(check_invalid_low_nibble(day) || check_invalid_low_nibble(month) || + check_invalid_low_nibble(year) || check_invalid_low_nibble(year >> 4)) { + return false; + } + *year_ptr = year + 0x2000; + *month_ptr = month; + *day_ptr = day; + return true; +} + static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); furi_assert(parsed_data); @@ -34,7 +63,10 @@ static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { } } - if((data->blocks[8] >> 16 & 0xFF) > 0x31 || (data->blocks[8] >> 8 & 0xFF) > 0x12) { + uint16_t mfg_year; + uint8_t mfg_month, mfg_day; + + if(!mykey_get_production_date(data, &mfg_year, &mfg_month, &mfg_day)) { FURI_LOG_D(TAG, "bad mfg date"); return false; } @@ -56,13 +88,8 @@ static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_cat_printf(parsed_data, "Blank: %s\n", is_blank ? "yes" : "no"); furi_string_cat_printf(parsed_data, "LockID: %s\n", mykey_has_lockid(data) ? "maybe" : "no"); - uint32_t block8 = data->blocks[8]; furi_string_cat_printf( - parsed_data, - "Prod. date: %02lX/%02lX/%04lX", - block8 >> 16 & 0xFF, - block8 >> 8 & 0xFF, - 0x2000 + (block8 & 0xFF)); + parsed_data, "Prod. date: %02X/%02X/%04X", mfg_day, mfg_month, mfg_year); if(!is_blank) { furi_string_cat_printf( diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index a85c056f6b1..bf643d21c15 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -32,8 +32,7 @@ #include #include #include -#include -#include + #include #define TAG "Umarsh" diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c new file mode 100644 index 00000000000..c90c8abbcd1 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -0,0 +1,193 @@ +/* + * Parser for WashCity MarkItaly Card (Europe). + * + * Copyright 2023 Filipe Polido (YaBaPT) + * + * Based on MetroMoney by Leptoptilos + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include + +#define TAG "WashCity" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static const MfClassicKeyPair washcity_1k_keys[] = { + {.a = 0xA0A1A2A3A4A5, .b = 0x010155010100}, // Sector 00 + {.a = 0xC78A3D0E1BCD, .b = 0xFFFFFFFFFFFF}, // Sector 01 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 02 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 03 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 04 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 05 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 06 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 07 + {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 08 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 09 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 10 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 11 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 12 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 13 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 14 + {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 15 +}; + +static bool washcity_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t ticket_sector_number = 0; + const uint8_t ticket_block_number = + mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; + FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); + + MfClassicKey key = {0}; + nfc_util_num2bytes(washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = mf_classic_poller_sync_auth( + nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + break; + } + + verified = true; + } while(false); + + return verified; +} + +static bool washcity_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + if(type != MfClassicType1k) break; + + MfClassicDeviceKeys keys = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(washcity_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(washcity_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify key + const uint8_t ticket_sector_number = 1; + const uint8_t ticket_block_number = 0; + + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); + + const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + if(key != washcity_1k_keys[ticket_sector_number].a) break; + + // Parse data + const uint8_t start_block_num = + mf_classic_get_first_block_num_of_sector(ticket_sector_number); + + const uint8_t* block_start_ptr = + &data->block[start_block_num + ticket_block_number].data[0]; + + uint32_t balance = nfc_util_bytes2num(block_start_ptr + 2, 2); + + uint32_t balance_eur = balance / 100; + uint8_t balance_cents = balance % 100; + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + + // Card Number is printed in HEX (equal to UID) + uint64_t card_number = nfc_util_bytes2num(uid, uid_len); + + furi_string_printf( + parsed_data, + "\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u EUR", + uid_len * 2, + card_number, + balance_eur, + balance_cents); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin washcity_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = washcity_verify, + .read = washcity_read, + .parse = washcity_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor washcity_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &washcity_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* washcity_plugin_ep() { + return &washcity_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 85f7193cd4f..25f750102f7 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1315,4 +1315,39 @@ CE99FBC8BD26 # Volgograd (Russia) Volna transport cards keys 2B787A063D5D -D37C8F1793F7 \ No newline at end of file +D37C8F1793F7 + +# Bandai Namco Passport [fka Banapassport] / Sega Aime Card +6090D00632F5 +019761AA8082 +574343467632 +A99164400748 +62742819AD7C +CC5075E42BA1 +B9DF35A0814C +8AF9C718F23D +58CD5C3673CB +FC80E88EB88C +7A3CDAD7C023 +30424C029001 +024E4E44001F +ECBBFA57C6AD +4757698143BD +1D30972E6485 +F8526D1A8D6D +1300EC8C7E80 +F80A65A87FFA +DEB06ED4AF8E +4AD96BF28190 +000390014D41 +0800F9917CB0 +730050555253 +4146D4A956C4 +131157FBB126 +E69DD9015A43 +337237F254D5 +9A8389F32FBF +7B8FB4A7100B +C8382A233993 +7B304F2A12A6 +FC9418BF788B diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a9887996d6d..70e7c3d468e 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -23,6 +23,7 @@ ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) +ADD_SCENE(nfc, save_confirm, SaveConfirm) ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index f0c22eec4d5..73856c292a1 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -10,8 +10,8 @@ void nfc_scene_delete_success_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, nfc); popup_set_callback(popup, nfc_scene_delete_success_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c index 593c67aabe1..34c552aba50 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect.c +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -17,8 +17,9 @@ void nfc_scene_detect_on_enter(void* context) { // Setup view popup_reset(instance->popup); + popup_set_header(instance->popup, "Reading", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); + instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index c024d31295a..16593cc89da 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -12,9 +12,8 @@ void nfc_scene_exit_confirm_on_enter(void* context) { dialog_ex_set_left_button_text(dialog_ex, "Exit"); dialog_ex_set_right_button_text(dialog_ex, "Stay"); - dialog_ex_set_header(dialog_ex, "Exit to NFC Menu?", 64, 11, AlignCenter, AlignTop); - dialog_ex_set_text( - dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Exit to NFC Menu?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_exit_confirm_dialog_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 721919d2b1a..d14f80b6247 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -24,7 +24,7 @@ void nfc_scene_extra_actions_on_enter(void* context) { instance); submenu_add_item( submenu, - "Mifare Classic Keys", + "MIFARE Classic Keys", SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, instance); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c index 987f81837a5..e2d3e6d72f5 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_detect_reader.c @@ -134,6 +134,13 @@ bool nfc_scene_mf_classic_detect_reader_on_event(void* context, SceneManagerEven instance->listener = NULL; } mfkey32_logger_free(instance->mfkey32_logger); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSaveSuccess)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } else if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneReadSuccess)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneReadSuccess); + } } return consumed; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index b6ba1c119fc..328e39132fc 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -175,6 +175,16 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); } +static void nfc_scene_mf_classic_dict_attack_notify_read(NfcApp* instance) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + bool is_card_fully_read = mf_classic_is_card_read(mfc_data); + if(is_card_fully_read) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } +} + bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { NfcApp* instance = context; bool consumed = false; @@ -196,7 +206,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); consumed = true; } else { - notification_message(instance->notifications, &sequence_success); + nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; @@ -225,13 +235,13 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); } else { - notification_message(instance->notifications, &sequence_success); + nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); } consumed = true; } else if(state == DictAttackStateSystemDictInProgress) { - notification_message(instance->notifications, &sequence_success); + nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); consumed = true; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c index 991c956c1c0..c3fb92bee06 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c @@ -11,7 +11,7 @@ void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { // Setup view Popup* popup = instance->popup; - popup_set_icon(popup, 72, 16, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); popup_set_text( popup, diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c index 8e07043e25b..eb0aa7c3aea 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c @@ -18,15 +18,16 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) { widget_add_string_multiline_element( instance->widget, 64, - 32, - AlignCenter, + 13, AlignCenter, + AlignTop, FontSecondary, - "Now use Mfkey32\nto extract keys"); + "Now use Mfkey32 to extract \nkeys: lab.flipper.net/nfc-tools"); + widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25); widget_add_button_element( instance->widget, - GuiButtonTypeCenter, - "OK", + GuiButtonTypeRight, + "Finish", nfc_scene_mf_classic_mfkey_complete_callback, instance); @@ -38,7 +39,7 @@ bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEve bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeCenter) { + if(event.event == GuiButtonTypeRight) { consumed = scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcSceneStart); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c index 02e307b01ba..2e0ada0da8c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_success.c @@ -12,8 +12,8 @@ void nfc_scene_mf_classic_update_initial_success_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); Popup* popup = instance->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(popup, "Updated", 11, 20, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_classic_update_initial_success_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c index 79f1def1d12..da576a276c2 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c @@ -65,8 +65,9 @@ static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial); if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { + popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); popup_set_text( - instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); } else { popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c index f85e5a80c3f..4d4367ec8f6 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c @@ -16,7 +16,7 @@ void nfc_scene_mf_classic_write_initial_fail_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c index acb75cd2e9f..100c5c43150 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c @@ -12,8 +12,8 @@ void nfc_scene_mf_classic_write_initial_success_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); Popup* popup = instance->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); + popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); popup_set_timeout(popup, 1500); popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_classic_write_initial_success_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c index 50025048af4..a879985bc8d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c @@ -16,7 +16,7 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index e3bbfba59aa..db5fd945fa0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -40,7 +40,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_center_button_text(dialog_ex, "OK"); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c index b3c1beef5ab..157d6ce1b35 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c @@ -46,8 +46,9 @@ static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) { scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite); if(state == NfcSceneMfUltralightWriteStateCardSearch) { + popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); popup_set_text( - instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); } else { popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c index dff5f278153..fcfb5f2b0f5 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c @@ -16,7 +16,7 @@ void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c index c1fbc35ee54..bb34190d20f 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c @@ -12,8 +12,8 @@ void nfc_scene_mf_ultralight_write_success_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); Popup* popup = instance->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(popup, "Successfully\nwritten", 5, 22, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, instance); popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback); @@ -28,8 +28,11 @@ bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerE if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventViewExit) { + bool was_saved = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); + consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneSavedMenu); + instance->scene_manager, was_saved ? NfcSceneSavedMenu : NfcSceneReadSuccess); } } return consumed; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c index a225c474db8..0ca765db78c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c @@ -16,7 +16,7 @@ void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); widget_add_string_multiline_element( diff --git a/applications/main/nfc/scenes/nfc_scene_restore_original.c b/applications/main/nfc/scenes/nfc_scene_restore_original.c index 612e6041e69..3a47ce8c3a6 100644 --- a/applications/main/nfc/scenes/nfc_scene_restore_original.c +++ b/applications/main/nfc/scenes/nfc_scene_restore_original.c @@ -10,8 +10,8 @@ void nfc_scene_restore_original_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Original file\nrestored", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(popup, "Original file\nrestored", 5, 22, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, nfc); popup_set_callback(popup, nfc_scene_restore_original_popup_callback); diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index b80f1bdcc14..99acbb0b803 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -12,9 +12,8 @@ void nfc_scene_retry_confirm_on_enter(void* context) { dialog_ex_set_left_button_text(dialog_ex, "Retry"); dialog_ex_set_right_button_text(dialog_ex, "Stay"); - dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop); - dialog_ex_set_text( - dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); dialog_ex_set_context(dialog_ex, nfc); dialog_ex_set_result_callback(dialog_ex, nfc_scene_retry_confirm_dialog_callback); @@ -29,7 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { + if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightUnlockWarn)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneDetect); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneRead)) { diff --git a/applications/main/nfc/scenes/nfc_scene_save_confirm.c b/applications/main/nfc/scenes/nfc_scene_save_confirm.c new file mode 100644 index 00000000000..9d0a206d326 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_save_confirm.c @@ -0,0 +1,44 @@ +#include "../nfc_app_i.h" + +void nfc_scene_save_confirm_dialog_callback(DialogExResult result, void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); +} + +void nfc_scene_save_confirm_on_enter(void* context) { + NfcApp* nfc = context; + DialogEx* dialog_ex = nfc->dialog_ex; + + dialog_ex_set_left_button_text(dialog_ex, "Skip"); + dialog_ex_set_right_button_text(dialog_ex, "Save"); + dialog_ex_set_header(dialog_ex, "Save the Key?", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); + dialog_ex_set_context(dialog_ex, nfc); + dialog_ex_set_result_callback(dialog_ex, nfc_scene_save_confirm_dialog_callback); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); +} + +bool nfc_scene_save_confirm_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); + consumed = true; + } else if(event.event == DialogExResultLeft) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_save_confirm_on_exit(void* context) { + NfcApp* nfc = context; + + // Clean view + dialog_ex_reset(nfc->dialog_ex); +} diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 0cb26c0d45a..ef7863c1388 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -10,8 +10,8 @@ void nfc_scene_save_success_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, nfc); popup_set_callback(popup, nfc_scene_save_success_popup_callback); @@ -28,6 +28,9 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); + consumed = true; } else { consumed = scene_manager_search_and_switch_to_another_scene( nfc->scene_manager, NfcSceneFileSelect); diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index e3366008078..b5102f8013a 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -32,10 +32,20 @@ void nfc_scene_set_type_on_enter(void* context) { nfc_protocol_support_common_submenu_callback, instance); + FuriString* str = furi_string_alloc(); for(size_t i = 0; i < NfcDataGeneratorTypeNum; i++) { - const char* name = nfc_data_generator_get_name(i); - submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance); + furi_string_cat_str(str, nfc_data_generator_get_name(i)); + furi_string_replace_str(str, "Mifare", "MIFARE"); + + submenu_add_item( + submenu, + furi_string_get_cstr(str), + i, + nfc_protocol_support_common_submenu_callback, + instance); + furi_string_reset(str); } + furi_string_free(str); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } diff --git a/applications/main/nfc/views/detect_reader.c b/applications/main/nfc/views/detect_reader.c index ebcda7caf1b..4d7b324e0a5 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -50,6 +50,7 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) { if(m->state == DetectReaderStateDone) { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Completed!"); + canvas_draw_icon(canvas, 24, 23, &I_check_big_20x17); } else { canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Collecting..."); diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 285b4a60f91..fe2c08fc648 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -26,6 +26,7 @@ typedef enum { //SubGhzCustomEvent SubGhzCustomEventSceneDeleteSuccess = 100, SubGhzCustomEventSceneDelete, + SubGhzCustomEventSceneDeleteBack, SubGhzCustomEventSceneDeleteRAW, SubGhzCustomEventSceneDeleteRAWBack, @@ -70,5 +71,6 @@ typedef enum { SubGhzCustomEventViewTransmitterBack, SubGhzCustomEventViewTransmitterSendStart, SubGhzCustomEventViewTransmitterSendStop, + SubGhzCustomEventViewTransmitterSendSave, SubGhzCustomEventViewTransmitterError, } SubGhzCustomEvent; diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 4a4445faad8..995434631ad 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -72,7 +72,6 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { uint32_t frequency = 0; float rssi_temp = -127.0f; uint32_t frequency_temp = 0; - CC1101Status status; //Start CC1101 furi_hal_subghz_reset(); @@ -123,9 +122,9 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { subghz_setting_get_frequency(instance->setting, i)); cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); - do { - status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); - } while(status.STATE != CC1101StateIDLE); + + furi_check(cc1101_wait_status_state( + &furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); @@ -168,9 +167,9 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); - do { - status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); - } while(status.STATE != CC1101StateIDLE); + + furi_check(cc1101_wait_status_state( + &furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_delete.c b/applications/main/subghz/scenes/subghz_scene_delete.c index 0d14cd23a3f..8092845a2f0 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete.c +++ b/applications/main/subghz/scenes/subghz_scene_delete.c @@ -6,47 +6,57 @@ void subghz_scene_delete_callback(GuiButtonType result, InputType type, void* co SubGhz* subghz = context; if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneDelete); + } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubGhzCustomEventSceneDeleteBack); } } void subghz_scene_delete_on_enter(void* context) { SubGhz* subghz = context; + FuriString* frequency_str; FuriString* modulation_str; + FuriString* text_out; FuriString* text; + text_out = furi_string_alloc(); + text = furi_string_alloc(); + + path_extract_filename(subghz->file_path, text, true); + furi_string_cat_printf(text_out, "\e#Delete %s?\e#\n", furi_string_get_cstr(text)); + + furi_string_reset(text); + subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); + + size_t dot = furi_string_search_char(text, '\r'); + if(dot > 0) { + furi_string_left(text, dot); + } + furi_string_cat_printf(text_out, "%s\n", furi_string_get_cstr(text)); + + furi_string_free(text); frequency_str = furi_string_alloc(); modulation_str = furi_string_alloc(); - text = furi_string_alloc(); - subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); - widget_add_string_element( - subghz->widget, - 78, - 0, - AlignLeft, - AlignTop, - FontSecondary, - furi_string_get_cstr(frequency_str)); - widget_add_string_element( - subghz->widget, - 113, - 0, - AlignLeft, - AlignTop, - FontSecondary, + furi_string_cat_printf( + text_out, + "%s %s", + furi_string_get_cstr(frequency_str), furi_string_get_cstr(modulation_str)); - subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); - widget_add_string_multiline_element( - subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); + + widget_add_text_box_element( + subghz->widget, 0, 0, 128, 54, AlignCenter, AlignTop, furi_string_get_cstr(text_out), false); furi_string_free(frequency_str); furi_string_free(modulation_str); - furi_string_free(text); + furi_string_free(text_out); widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Delete", subghz_scene_delete_callback, subghz); + widget_add_button_element( + subghz->widget, GuiButtonTypeLeft, "Cancel", subghz_scene_delete_callback, subghz); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); } @@ -63,6 +73,8 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart); } return true; + } else if(event.event == SubGhzCustomEventSceneDeleteBack) { + return scene_manager_previous_scene(subghz->scene_manager); } } return false; diff --git a/applications/main/subghz/scenes/subghz_scene_delete_raw.c b/applications/main/subghz/scenes/subghz_scene_delete_raw.c index 8dff442a870..53f13b68e0e 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_raw.c @@ -17,48 +17,37 @@ void subghz_scene_delete_raw_on_enter(void* context) { SubGhz* subghz = context; FuriString* frequency_str; FuriString* modulation_str; - - frequency_str = furi_string_alloc(); - modulation_str = furi_string_alloc(); - - char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; + FuriString* text_out; FuriString* file_name; + text_out = furi_string_alloc(); file_name = furi_string_alloc(); + path_extract_filename(subghz->file_path, file_name, true); - snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", furi_string_get_cstr(file_name)); + furi_string_cat_printf( + text_out, "\e#Delete %s?\e#\nRAW signal\n", furi_string_get_cstr(file_name)); furi_string_free(file_name); - widget_add_text_box_element( - subghz->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); - - widget_add_string_element( - subghz->widget, 38, 25, AlignLeft, AlignTop, FontSecondary, "RAW signal"); + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); - widget_add_string_element( - subghz->widget, - 35, - 37, - AlignLeft, - AlignTop, - FontSecondary, - furi_string_get_cstr(frequency_str)); - widget_add_string_element( - subghz->widget, - 72, - 37, - AlignLeft, - AlignTop, - FontSecondary, + furi_string_cat_printf( + text_out, + "%s %s", + furi_string_get_cstr(frequency_str), furi_string_get_cstr(modulation_str)); + widget_add_text_box_element( + subghz->widget, 0, 0, 128, 54, AlignCenter, AlignTop, furi_string_get_cstr(text_out), false); + furi_string_free(frequency_str); furi_string_free(modulation_str); + furi_string_free(text_out); widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Delete", subghz_scene_delete_raw_callback, subghz); widget_add_button_element( - subghz->widget, GuiButtonTypeLeft, "Back", subghz_scene_delete_raw_callback, subghz); + subghz->widget, GuiButtonTypeLeft, "Cancel", subghz_scene_delete_raw_callback, subghz); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); } diff --git a/applications/main/subghz/scenes/subghz_scene_delete_success.c b/applications/main/subghz/scenes/subghz_scene_delete_success.c index 4d9f33e37e0..d5c2b391ce2 100644 --- a/applications/main/subghz/scenes/subghz_scene_delete_success.c +++ b/applications/main/subghz/scenes/subghz_scene_delete_success.c @@ -12,8 +12,8 @@ void subghz_scene_delete_success_on_enter(void* context) { // Setup view Popup* popup = subghz->popup; - popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); + popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_delete_success_popup_callback); diff --git a/applications/main/subghz/scenes/subghz_scene_need_saving.c b/applications/main/subghz/scenes/subghz_scene_need_saving.c index f29f26309c6..90c72a0d7ce 100644 --- a/applications/main/subghz/scenes/subghz_scene_need_saving.c +++ b/applications/main/subghz/scenes/subghz_scene_need_saving.c @@ -15,16 +15,16 @@ void subghz_scene_need_saving_callback(GuiButtonType result, InputType type, voi void subghz_scene_need_saving_on_enter(void* context) { SubGhz* subghz = context; - widget_add_string_multiline_element( - subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-GHz Menu?"); - widget_add_string_multiline_element( + widget_add_text_box_element( subghz->widget, - 64, - 32, + 0, + 0, + 128, + 54, AlignCenter, - AlignCenter, - FontSecondary, - "All unsaved data\nwill be lost!"); + AlignTop, + "\e#Exit to Sub-GHz Menu?\e#\nAll unsaved data will be lost", + false); widget_add_button_element( subghz->widget, GuiButtonTypeRight, "Stay", subghz_scene_need_saving_callback, subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 7180bb3a403..4ab1e7ad39b 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -1,20 +1,11 @@ #include "../subghz_i.h" #include "../helpers/subghz_custom_event.h" +#include "../views/transmitter.h" -void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { +void subghz_scene_receiver_info_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; - - if((result == GuiButtonTypeCenter) && (type == InputTypePress)) { - view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoTxStart); - } else if((result == GuiButtonTypeCenter) && (type == InputTypeRelease)) { - view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoTxStop); - } else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { - view_dispatcher_send_custom_event( - subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoSave); - } + view_dispatcher_send_custom_event(subghz->view_dispatcher, event); } static bool subghz_scene_receiver_info_update_parser(void* context) { @@ -37,6 +28,28 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { preset->data, preset->data_size); + FuriString* key_str = furi_string_alloc(); + FuriString* frequency_str = furi_string_alloc(); + FuriString* modulation_str = furi_string_alloc(); + + subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), key_str); + subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); + subghz_view_transmitter_add_data_to_show( + subghz->subghz_transmitter, + furi_string_get_cstr(key_str), + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str), + subghz_txrx_protocol_is_transmittable(subghz->txrx, true)); + + furi_string_free(frequency_str); + furi_string_free(modulation_str); + furi_string_free(key_str); + + subghz_view_transmitter_set_radio_device_type( + subghz->subghz_transmitter, subghz_txrx_radio_device_get(subghz->txrx)); + subghz_view_transmitter_set_model_type( + subghz->subghz_transmitter, SubGhzViewTransmitterModelTypeInfo); + return true; } return false; @@ -44,67 +57,23 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { void subghz_scene_receiver_info_on_enter(void* context) { SubGhz* subghz = context; - if(subghz_scene_receiver_info_update_parser(subghz)) { - FuriString* frequency_str = furi_string_alloc(); - FuriString* modulation_str = furi_string_alloc(); - FuriString* text = furi_string_alloc(); - - subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); - widget_add_string_element( - subghz->widget, - 78, - 0, - AlignLeft, - AlignTop, - FontSecondary, - furi_string_get_cstr(frequency_str)); - - widget_add_string_element( - subghz->widget, - 113, - 0, - AlignLeft, - AlignTop, - FontSecondary, - furi_string_get_cstr(modulation_str)); - subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); - widget_add_string_multiline_element( - subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); - - furi_string_free(frequency_str); - furi_string_free(modulation_str); - furi_string_free(text); - - if(subghz_txrx_protocol_is_serializable(subghz->txrx)) { - widget_add_button_element( - subghz->widget, - GuiButtonTypeRight, - "Save", - subghz_scene_receiver_info_callback, - subghz); - } - if(subghz_txrx_protocol_is_transmittable(subghz->txrx, true)) { - widget_add_button_element( - subghz->widget, - GuiButtonTypeCenter, - "Send", - subghz_scene_receiver_info_callback, - subghz); - } } else { - widget_add_icon_element(subghz->widget, 37, 15, &I_DolphinCommon_56x48); - widget_add_string_element( - subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse."); + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubGhzCustomEventSceneShowErrorSub); } - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); + subghz_view_transmitter_set_callback( + subghz->subghz_transmitter, subghz_scene_receiver_info_callback, subghz); + + subghz->state_notifications = SubGhzNotificationStateIDLE; + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTransmitter); } bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzCustomEventSceneReceiverInfoTxStart) { + if(event.event == SubGhzCustomEventViewTransmitterSendStart) { if(!subghz_scene_receiver_info_update_parser(subghz)) { return false; } @@ -120,7 +89,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz->state_notifications = SubGhzNotificationStateTx; } return true; - } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { + } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { //CC1101 Stop Tx -> Start RX subghz->state_notifications = SubGhzNotificationStateIDLE; @@ -129,7 +98,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz_txrx_hopper_unpause(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateRx; return true; - } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { + } else if(event.event == SubGhzCustomEventViewTransmitterSendSave) { //CC1101 Stop RX -> Save subghz->state_notifications = SubGhzNotificationStateIDLE; subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF); @@ -144,7 +113,11 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } return true; + } else if(event.event == SubGhzCustomEventSceneShowErrorSub) { + furi_string_set(subghz->error_str, "Error history parse."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); } + } else if(event.type == SceneManagerEventTypeTick) { if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) { subghz_txrx_hopper_update(subghz->txrx); diff --git a/applications/main/subghz/scenes/subghz_scene_region_info.c b/applications/main/subghz/scenes/subghz_scene_region_info.c index b98394af072..61bd1acc851 100644 --- a/applications/main/subghz/scenes/subghz_scene_region_info.c +++ b/applications/main/subghz/scenes/subghz_scene_region_info.c @@ -6,12 +6,13 @@ void subghz_scene_region_info_on_enter(void* context) { SubGhz* subghz = context; const FuriHalRegion* const region = furi_hal_region_get(); FuriString* buffer = furi_string_alloc(); + if(region) { - furi_string_cat_printf(buffer, "Region: %s, bands:\n", region->country_code); + furi_string_cat_printf(buffer, "Region: %s\nBands:\n", region->country_code); for(uint16_t i = 0; i < region->bands_count; ++i) { furi_string_cat_printf( buffer, - " %lu-%lu kHz\n", + "%lu-%lu kHz\n", region->bands[i].start / 1000, region->bands[i].end / 1000); } @@ -20,7 +21,10 @@ void subghz_scene_region_info_on_enter(void* context) { } widget_add_string_multiline_element( - subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); + subghz->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Region Information"); + + widget_add_string_multiline_element( + subghz->widget, 0, 13, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); furi_string_free(buffer); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 394dda89e51..30d0821e158 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -132,7 +132,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { - subghz_file_name_clear(subghz); + furi_string_reset(subghz->file_path_tmp); } scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); diff --git a/applications/main/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c index 40ade5a5350..9b610b5f5f5 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_success.c +++ b/applications/main/subghz/scenes/subghz_scene_save_success.c @@ -11,8 +11,8 @@ void subghz_scene_save_success_on_enter(void* context) { // Setup view Popup* popup = subghz->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); + popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); popup_set_callback(popup, subghz_scene_save_success_popup_callback); diff --git a/applications/main/subghz/scenes/subghz_scene_saved.c b/applications/main/subghz/scenes/subghz_scene_saved.c index 8b198e3395e..3daedd33fb6 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved.c +++ b/applications/main/subghz/scenes/subghz_scene_saved.c @@ -22,5 +22,7 @@ bool subghz_scene_saved_on_event(void* context, SceneManagerEvent event) { } void subghz_scene_saved_on_exit(void* context) { + SubGhz* subghz = context; + scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu, 0); UNUSED(context); } diff --git a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c index 113e7ae7463..0de48c442a3 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_error_sub.c +++ b/applications/main/subghz/scenes/subghz_scene_show_error_sub.c @@ -11,7 +11,7 @@ void subghz_scene_show_error_sub_on_enter(void* context) { // Setup view Popup* popup = subghz->popup; - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_header(popup, furi_string_get_cstr(subghz->error_str), 14, 15, AlignLeft, AlignTop); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c index 1907c41926c..3522bf8aa64 100644 --- a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c +++ b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c @@ -21,7 +21,7 @@ void subghz_scene_show_only_rx_on_enter(void* context) { popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); - popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); popup_set_timeout(popup, 1500); popup_set_context(popup, subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index f83e44a0a1d..cbc4e7d8a68 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -37,6 +37,8 @@ bool subghz_scene_transmitter_update_data_show(void* context) { } subghz_view_transmitter_set_radio_device_type( subghz->subghz_transmitter, subghz_txrx_radio_device_get(subghz->txrx)); + subghz_view_transmitter_set_model_type( + subghz->subghz_transmitter, SubGhzViewTransmitterModelTypeTx); return ret; } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index e1b5e868414..3400011ecba 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -49,6 +49,28 @@ static void subghz_cli_radio_device_power_off() { if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); } +static SubGhzEnvironment* subghz_cli_environment_init(void) { + SubGhzEnvironment* environment = subghz_environment_alloc(); + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { + printf("Load_keystore keeloq_mfcodes \033[0;32mOK\033[0m\r\n"); + } else { + printf("Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); + } + if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { + printf("Load_keystore keeloq_mfcodes_user \033[0;32mOK\033[0m\r\n"); + } else { + printf("Load_keystore keeloq_mfcodes_user \033[0;33mAbsent\033[0m\r\n"); + } + subghz_environment_set_came_atomo_rainbow_table_file_name( + environment, SUBGHZ_CAME_ATOMO_DIR_NAME); + subghz_environment_set_alutech_at_4n_rainbow_table_file_name( + environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); + subghz_environment_set_nice_flor_s_rainbow_table_file_name( + environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); + subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); + return environment; +} + void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -324,16 +346,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { furi_stream_buffer_alloc(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); furi_check(instance->stream); - SubGhzEnvironment* environment = subghz_environment_alloc(); - subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME); - subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); - subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, SUBGHZ_CAME_ATOMO_DIR_NAME); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); - subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); + SubGhzEnvironment* environment = subghz_cli_environment_init(); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); @@ -517,25 +530,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { // Allocate context SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); - SubGhzEnvironment* environment = subghz_environment_alloc(); - if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { - printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;32mOK\033[0m\r\n"); - } else { - printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); - } - if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { - printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;32mOK\033[0m\r\n"); - } else { - printf( - "SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;31mERROR\033[0m\r\n"); - } - subghz_environment_set_came_atomo_rainbow_table_file_name( - environment, SUBGHZ_CAME_ATOMO_DIR_NAME); - subghz_environment_set_alutech_at_4n_rainbow_table_file_name( - environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); - subghz_environment_set_nice_flor_s_rainbow_table_file_name( - environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); - subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); + SubGhzEnvironment* environment = subghz_cli_environment_init(); SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); @@ -580,6 +575,262 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { furi_string_free(file_name); } +static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) { + FuriHalSubGhzPreset preset = FuriHalSubGhzPresetIDLE; + if(!strcmp(preset_name, "FuriHalSubGhzPresetOok270Async")) { + preset = FuriHalSubGhzPresetOok270Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPresetOok650Async")) { + preset = FuriHalSubGhzPresetOok650Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset = FuriHalSubGhzPreset2FSKDev238Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset = FuriHalSubGhzPreset2FSKDev476Async; + } else if(!strcmp(preset_name, "FuriHalSubGhzPresetCustom")) { + preset = FuriHalSubGhzPresetCustom; + } else { + printf("subghz tx_from_file: unknown preset"); + } + return preset; +} + +void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) { // -V524 + UNUSED(context); + FuriString* file_name; + file_name = furi_string_alloc(); + furi_string_set(file_name, ANY_PATH("subghz/test.sub")); + uint32_t repeat = 10; + uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT + + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + FlipperFormat* fff_data_raw = flipper_format_string_alloc(); + FuriString* temp_str; + temp_str = furi_string_alloc(); + uint32_t temp_data32; + bool check_file = false; + const SubGhzDevice* device = NULL; + + uint32_t frequency = 0; + SubGhzTransmitter* transmitter = NULL; + + subghz_devices_init(); + + SubGhzEnvironment* environment = subghz_cli_environment_init(); + + do { + if(furi_string_size(args)) { + if(!args_read_string_and_trim(args, file_name)) { + cli_print_usage( + "subghz tx_from_file: ", + " ", + furi_string_get_cstr(args)); + break; + } + } + + if(furi_string_size(args)) { + int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &repeat, &device_ind); + if(ret != 2) { + printf("sscanf returned %d, repeat: %lu device: %lu\r\n", ret, repeat, device_ind); + cli_print_usage( + "subghz tx_from_file:", + " ", + furi_string_get_cstr(args)); + break; + } + } + + device = subghz_cli_command_get_device(&device_ind); + if(device == NULL) { + printf("subghz tx_from_file: \033[0;31mError device not found\033[0m\r\n"); + break; + } + + if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { + printf( + "subghz tx_from_file: \033[0;31mError open file\033[0m %s\r\n", + furi_string_get_cstr(file_name)); + break; + } + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + printf("subghz tx_from_file: \033[0;31mMissing or incorrect header\033[0m\r\n"); + break; + } + + if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || + (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && + temp_data32 == SUBGHZ_KEY_FILE_VERSION) { + } else { + printf("subghz tx_from_file: \033[0;31mType or version mismatch\033[0m\r\n"); + break; + } + + //Load frequency + if(!flipper_format_read_uint32(fff_data_file, "Frequency", &frequency, 1)) { + printf("subghz tx_from_file: \033[0;31mMissing Frequency\033[0m\r\n"); + break; + } + + if(!subghz_devices_is_frequency_valid(device, frequency)) { + printf("subghz tx_from_file: \033[0;31mFrequency not supported\033[0m\r\n"); + break; + } + + //Load preset + if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { + printf("subghz tx_from_file: \033[0;31mMissing Preset\033[0m\r\n"); + break; + } + + subghz_devices_begin(device); + subghz_devices_reset(device); + + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + uint8_t* custom_preset_data; + uint32_t custom_preset_data_size; + if(!flipper_format_get_value_count(fff_data_file, "Custom_preset_data", &temp_data32)) + break; + if(!temp_data32 || (temp_data32 % 2)) { + printf("subghz tx_from_file: \033[0;31mCustom_preset_data size error\033[0m\r\n"); + break; + } + custom_preset_data_size = sizeof(uint8_t) * temp_data32; + custom_preset_data = malloc(custom_preset_data_size); + if(!flipper_format_read_hex( + fff_data_file, + "Custom_preset_data", + custom_preset_data, + custom_preset_data_size)) { + printf("subghz tx_from_file: \033[0;31mCustom_preset_data read error\033[0m\r\n"); + break; + } + subghz_devices_load_preset( + device, + subghz_cli_get_preset_name(furi_string_get_cstr(temp_str)), + custom_preset_data); + free(custom_preset_data); + } else { + subghz_devices_load_preset( + device, subghz_cli_get_preset_name(furi_string_get_cstr(temp_str)), NULL); + } + + subghz_devices_set_frequency(device, frequency); + + //Load protocol + if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { + printf("subghz tx_from_file: \033[0;31mMissing protocol\033[0m\r\n"); + break; + } + + SubGhzProtocolStatus status; + bool is_init_protocol = true; + if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { // if RAW protocol + subghz_protocol_raw_gen_fff_data( + fff_data_raw, furi_string_get_cstr(file_name), subghz_devices_get_name(device)); + + transmitter = + subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str)); + if(transmitter == NULL) { + printf("subghz tx_from_file: \033[0;31mError transmitter\033[0m\r\n"); + is_init_protocol = false; + } + + if(is_init_protocol) { + status = subghz_transmitter_deserialize(transmitter, fff_data_raw); + if(status != SubGhzProtocolStatusOk) { + printf( + "subghz tx_from_file: \033[0;31mError deserialize protocol\033[0m %d\r\n", + status); + is_init_protocol = false; + } + } + + } else { //if not RAW protocol + flipper_format_insert_or_update_uint32(fff_data_file, "Repeat", &repeat, 1); + + transmitter = + subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str)); + if(transmitter == NULL) { + printf("subghz tx_from_file: \033[0;31mError transmitter\033[0m\r\n"); + is_init_protocol = false; + } + if(is_init_protocol) { + status = subghz_transmitter_deserialize(transmitter, fff_data_file); + if(status != SubGhzProtocolStatusOk) { + printf( + "subghz tx_from_file: \033[0;31mError deserialize protocol\033[0m %d\r\n", + status); + is_init_protocol = false; + } + } + + flipper_format_delete_key(fff_data_file, "Repeat"); + } + + if(is_init_protocol) { + check_file = true; + } else { + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_transmitter_free(transmitter); + } + + } while(false); + + flipper_format_free(fff_data_file); + furi_record_close(RECORD_STORAGE); + + if(check_file) { + furi_hal_power_suppress_charge_enter(); + + printf( + "Listening at \033[0;33m%s\033[0m. Frequency=%lu, Protocol=%s\r\n\r\nPress CTRL+C to stop\r\n\r\n", + furi_string_get_cstr(file_name), + frequency, + furi_string_get_cstr(temp_str)); + do { + //delay in downloading files and other preparatory processes + furi_delay_ms(200); + if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { + while( + !(subghz_devices_is_async_complete_tx(device) || + cli_cmd_interrupt_received(cli))) { + printf("."); + fflush(stdout); + furi_delay_ms(333); + } + subghz_devices_stop_async_tx(device); + + } else { + printf("Transmission on this frequency is restricted in your region\r\n"); + } + + if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { + subghz_transmitter_stop(transmitter); + repeat--; + if(!cli_cmd_interrupt_received(cli) && repeat) + subghz_transmitter_deserialize(transmitter, fff_data_raw); + } + + } while(!cli_cmd_interrupt_received(cli) && + (repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW"))); + + subghz_devices_sleep(device); + subghz_devices_end(device); + subghz_cli_radio_device_power_off(); + + furi_hal_power_suppress_charge_exit(); + + subghz_transmitter_free(transmitter); + } + flipper_format_free(fff_data_raw); + furi_string_free(file_name); + furi_string_free(temp_str); + subghz_devices_deinit(); + subghz_environment_free(environment); +} + static void subghz_cli_command_print_usage() { printf("Usage:\r\n"); printf("subghz \r\n"); @@ -592,11 +843,13 @@ static void subghz_cli_command_print_usage() { printf("\trx \t - Receive\r\n"); printf("\trx_raw \t - Receive RAW\r\n"); printf("\tdecode_raw \t - Testing\r\n"); + printf( + "\ttx_from_file \t - Transmitting from file\r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("\r\n"); printf(" debug cmd:\r\n"); - printf("\ttx_carrier \t - Transmit carrier\r\n"); + printf("\ttx_carrier \t - Transmitting carrier\r\n"); printf("\trx_carrier \t - Receive carrier\r\n"); printf( "\tencrypt_keeloq \t - Encrypt keeloq manufacture keys\r\n"); @@ -915,6 +1168,11 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { break; } + if(furi_string_cmp_str(cmd, "tx_from_file") == 0) { + subghz_cli_command_tx_from_file(cli, args, context); + break; + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) { subghz_cli_command_encrypt_keeloq(cli, args); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index c03efe5e568..4358b164dab 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -70,7 +70,7 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) { dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); - dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); dialog_message_show(dialogs, message); dialog_message_free(message); diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 23fa26c772c..9dfe85d1ecc 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -251,9 +251,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { } if(model->device_type == SubGhzRadioDeviceTypeInternal) { - canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); + canvas_draw_icon(canvas, 109, 0, &I_Internal_ant_1_9x11); } else { - canvas_draw_icon(canvas, 108, 0, &I_External_antenna_20x12); + canvas_draw_icon(canvas, 109, 0, &I_External_ant_1_9x11); } subghz_view_rssi_draw(canvas, model); diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index d90401678a8..5a4afa630d1 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -178,7 +178,7 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel subghz_frequency_analyzer_log_frequency_draw(canvas, model); } else { canvas_draw_str(canvas, 0, 8, "Frequency Analyzer"); - canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); + canvas_draw_icon(canvas, 109, 0, &I_Internal_ant_1_9x11); canvas_draw_str(canvas, 0, 64, "RSSI"); subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20, 64); diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index d630d47ec8a..cf64567ca76 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -294,9 +294,9 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { canvas, 106, 2, AlignRight, AlignTop, furi_string_get_cstr(model->sample_write)); if(model->device_type == SubGhzRadioDeviceTypeInternal) { - canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); + canvas_draw_icon(canvas, 109, 0, &I_Internal_ant_1_9x11); } else { - canvas_draw_icon(canvas, 108, 0, &I_External_antenna_20x12); + canvas_draw_icon(canvas, 109, 0, &I_External_ant_1_9x11); } canvas_draw_line(canvas, 0, 14, 115, 14); canvas_draw_line(canvas, 0, 48, 115, 48); diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 2a876f8c268..45c07acca71 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -16,6 +16,9 @@ typedef struct { FuriString* key_str; bool show_button; SubGhzRadioDeviceType device_type; + SubGhzViewTransmitterModelType model_type; + IconAnimation* icon_int_ant; + IconAnimation* icon_ext_ant; } SubGhzViewTransmitterModel; void subghz_view_transmitter_set_callback( @@ -58,6 +61,17 @@ void subghz_view_transmitter_set_radio_device_type( true); } +void subghz_view_transmitter_set_model_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzViewTransmitterModelType model_type) { + furi_assert(subghz_transmitter); + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { model->model_type = model_type; }, + true); +} + static void subghz_view_transmitter_button_right(Canvas* canvas, const char* str) { const uint8_t button_height = 12; const uint8_t vertical_offset = 3; @@ -100,13 +114,21 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->key_str)); canvas_draw_str(canvas, 78, 7, furi_string_get_cstr(model->frequency_str)); canvas_draw_str(canvas, 113, 7, furi_string_get_cstr(model->preset_str)); + if(model->show_button) { + if(model->model_type == SubGhzViewTransmitterModelTypeInfo) { + elements_button_center(canvas, "Send"); + elements_button_right(canvas, "Save"); + } else { + //default type SubGhzViewTransmitterModelTypeTx + subghz_view_transmitter_button_right(canvas, "Send"); + } + if(model->device_type == SubGhzRadioDeviceTypeInternal) { - canvas_draw_icon(canvas, 108, 39, &I_Internal_antenna_20x12); + canvas_draw_icon_animation(canvas, 109, 40, model->icon_int_ant); } else { - canvas_draw_icon(canvas, 108, 39, &I_External_antenna_20x12); + canvas_draw_icon_animation(canvas, 109, 40, model->icon_ext_ant); } - subghz_view_transmitter_button_right(canvas, "Send"); } } @@ -140,13 +162,32 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { true); if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) { + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { + icon_animation_start(model->icon_int_ant); + icon_animation_start(model->icon_ext_ant); + }, + false); subghz_transmitter->callback( SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); return true; } else if(can_be_sent && event->key == InputKeyOk && event->type == InputTypeRelease) { + with_view_model( + subghz_transmitter->view, + SubGhzViewTransmitterModel * model, + { + icon_animation_stop(model->icon_int_ant); + icon_animation_stop(model->icon_ext_ant); + }, + false); subghz_transmitter->callback( SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); return true; + } else if(can_be_sent && event->key == InputKeyRight && event->type == InputTypeShort) { + subghz_transmitter->callback( + SubGhzCustomEventViewTransmitterSendSave, subghz_transmitter->context); } return true; @@ -181,6 +222,11 @@ SubGhzViewTransmitter* subghz_view_transmitter_alloc() { model->frequency_str = furi_string_alloc(); model->preset_str = furi_string_alloc(); model->key_str = furi_string_alloc(); + model->model_type = SubGhzViewTransmitterModelTypeTx; + model->icon_int_ant = icon_animation_alloc(&A_SubGhz_Internal_ant); + view_tie_icon_animation(subghz_transmitter->view, model->icon_int_ant); + model->icon_ext_ant = icon_animation_alloc(&A_SubGhz_External_ant); + view_tie_icon_animation(subghz_transmitter->view, model->icon_ext_ant); }, true); return subghz_transmitter; @@ -196,6 +242,8 @@ void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter) { furi_string_free(model->frequency_str); furi_string_free(model->preset_str); furi_string_free(model->key_str); + icon_animation_free(model->icon_int_ant); + icon_animation_free(model->icon_ext_ant); }, true); view_free(subghz_transmitter->view); diff --git a/applications/main/subghz/views/transmitter.h b/applications/main/subghz/views/transmitter.h index 19da3145c95..e22898ad7c5 100644 --- a/applications/main/subghz/views/transmitter.h +++ b/applications/main/subghz/views/transmitter.h @@ -6,6 +6,11 @@ typedef struct SubGhzViewTransmitter SubGhzViewTransmitter; +typedef enum { + SubGhzViewTransmitterModelTypeTx, + SubGhzViewTransmitterModelTypeInfo, +} SubGhzViewTransmitterModelType; + typedef void (*SubGhzViewTransmitterCallback)(SubGhzCustomEvent event, void* context); void subghz_view_transmitter_set_callback( @@ -17,6 +22,10 @@ void subghz_view_transmitter_set_radio_device_type( SubGhzViewTransmitter* subghz_transmitter, SubGhzRadioDeviceType device_type); +void subghz_view_transmitter_set_model_type( + SubGhzViewTransmitter* subghz_transmitter, + SubGhzViewTransmitterModelType model_type); + SubGhzViewTransmitter* subghz_view_transmitter_alloc(); void subghz_view_transmitter_free(SubGhzViewTransmitter* subghz_transmitter); diff --git a/applications/main/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c index d7d7e6cf413..83c8a575f56 100644 --- a/applications/main/u2f/u2f_hid.c +++ b/applications/main/u2f/u2f_hid.c @@ -8,8 +8,6 @@ #include #include -#include - #define TAG "U2fHid" #define WORKER_TAG TAG "Worker" diff --git a/applications/services/application.fam b/applications/services/application.fam index aec49b2312e..9ffb26dd6fd 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -5,6 +5,7 @@ App( provides=[ "crypto_start", "rpc_start", + "expansion_start", "bt", "desktop", "loader", diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 467e7c53024..025711fb58f 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -211,7 +211,12 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { furi_log_level_to_string(furi_log_get_level(), ¤t_level); printf("Current log level: %s\r\n", current_level); - furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring); + FuriLogHandler log_handler = { + .callback = cli_command_log_tx_callback, + .context = ring, + }; + + furi_log_add_handler(log_handler); printf("Use to list available log levels\r\n"); printf("Press CTRL+C to stop...\r\n"); @@ -220,7 +225,7 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { cli_write(cli, buffer, ret); } - furi_hal_console_set_tx_callback(NULL, NULL); + furi_log_remove_handler(log_handler); if(restore_log_level) { // There will be strange behaviour if log level is set from settings while log command is running diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 547883e9a74..7a49dd51e31 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -414,7 +414,7 @@ bool desktop_api_is_locked(Desktop* instance) { void desktop_api_unlock(Desktop* instance) { furi_assert(instance); - view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopLockedEventUnlocked); + view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalApiUnlock); } FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance) { diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index 034eedb8ace..c4cd5748f3d 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -83,6 +83,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case DesktopLockedEventUnlocked: + case DesktopGlobalApiUnlock: desktop_unlock(desktop); consumed = true; break; diff --git a/applications/services/desktop/scenes/desktop_scene_pin_input.c b/applications/services/desktop/scenes/desktop_scene_pin_input.c index 0e248def604..a21c59e380b 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_input.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_input.c @@ -126,6 +126,7 @@ bool desktop_scene_pin_input_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case DesktopPinInputEventUnlocked: + case DesktopGlobalApiUnlock: desktop_unlock(desktop); consumed = true; break; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 5dc51fd85cf..bce9c09d1ae 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -50,4 +50,5 @@ typedef enum { DesktopGlobalBeforeAppStarted, DesktopGlobalAfterAppFinished, DesktopGlobalAutoLock, + DesktopGlobalApiUnlock, } DesktopEvent; diff --git a/applications/services/expansion/application.fam b/applications/services/expansion/application.fam new file mode 100644 index 00000000000..1402e8413a9 --- /dev/null +++ b/applications/services/expansion/application.fam @@ -0,0 +1,12 @@ +App( + appid="expansion_start", + apptype=FlipperAppType.STARTUP, + entry_point="expansion_on_system_start", + cdefines=["SRV_EXPANSION"], + sdk_headers=[ + "expansion.h", + ], + requires=["rpc_start"], + provides=["expansion_settings"], + order=10, +) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c new file mode 100644 index 00000000000..e385734b79b --- /dev/null +++ b/applications/services/expansion/expansion.c @@ -0,0 +1,441 @@ +#include "expansion.h" + +#include +#include +#include + +#include + +#include + +#include "expansion_settings.h" +#include "expansion_protocol.h" + +#define TAG "ExpansionSrv" + +#define EXPANSION_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) + +typedef enum { + ExpansionStateDisabled, + ExpansionStateEnabled, + ExpansionStateRunning, +} ExpansionState; + +typedef enum { + ExpansionSessionStateHandShake, + ExpansionSessionStateConnected, + ExpansionSessionStateRpcActive, +} ExpansionSessionState; + +typedef enum { + ExpansionSessionExitReasonUnknown, + ExpansionSessionExitReasonUser, + ExpansionSessionExitReasonError, + ExpansionSessionExitReasonTimeout, +} ExpansionSessionExitReason; + +typedef enum { + ExpansionFlagStop = 1 << 0, + ExpansionFlagData = 1 << 1, + ExpansionFlagError = 1 << 2, +} ExpansionFlag; + +#define EXPANSION_ALL_FLAGS (ExpansionFlagData | ExpansionFlagStop) + +struct Expansion { + ExpansionState state; + ExpansionSessionState session_state; + ExpansionSessionExitReason exit_reason; + FuriStreamBuffer* rx_buf; + FuriSemaphore* tx_semaphore; + FuriMutex* state_mutex; + FuriThread* worker_thread; + FuriHalSerialId serial_id; + FuriHalSerialHandle* serial_handle; + RpcSession* rpc_session; +}; + +static void expansion_detect_callback(void* context); + +// Called in UART IRQ context +static void expansion_serial_rx_callback( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + void* context) { + furi_assert(handle); + furi_assert(context); + + Expansion* instance = context; + + if(event == FuriHalSerialRxEventData) { + const uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); + furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagData); + } +} + +static size_t expansion_receive_callback(uint8_t* data, size_t data_size, void* context) { + Expansion* instance = context; + + size_t received_size = 0; + + while(true) { + received_size += furi_stream_buffer_receive( + instance->rx_buf, data + received_size, data_size - received_size, 0); + + if(received_size == data_size) break; + + const uint32_t flags = furi_thread_flags_wait( + EXPANSION_ALL_FLAGS, FuriFlagWaitAny, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)); + + if(flags & FuriFlagError) { + if(flags == (unsigned)FuriFlagErrorTimeout) { + // Exiting due to timeout + instance->exit_reason = ExpansionSessionExitReasonTimeout; + } else { + // Exiting due to an unspecified error + instance->exit_reason = ExpansionSessionExitReasonError; + } + break; + } else if(flags & ExpansionFlagStop) { + // Exiting due to explicit request + instance->exit_reason = ExpansionSessionExitReasonUser; + break; + } else if(flags & ExpansionFlagError) { + // Exiting due to RPC error + instance->exit_reason = ExpansionSessionExitReasonError; + break; + } else if(flags & ExpansionFlagData) { + // Go to buffer reading + continue; + } + } + + return received_size; +} + +static inline bool expansion_receive_frame(Expansion* instance, ExpansionFrame* frame) { + return expansion_protocol_decode(frame, expansion_receive_callback, instance) == + ExpansionProtocolStatusOk; +} + +static size_t expansion_send_callback(const uint8_t* data, size_t data_size, void* context) { + Expansion* instance = context; + furi_hal_serial_tx(instance->serial_handle, data, data_size); + furi_hal_serial_tx_wait_complete(instance->serial_handle); + return data_size; +} + +static inline bool expansion_send_frame(Expansion* instance, const ExpansionFrame* frame) { + return expansion_protocol_encode(frame, expansion_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_send_heartbeat(Expansion* instance) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeHeartbeat, + .content.heartbeat = {}, + }; + + return expansion_send_frame(instance, &frame); +} + +static bool expansion_send_status_response(Expansion* instance, ExpansionFrameError error) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeStatus, + .content.status.error = error, + }; + + return expansion_send_frame(instance, &frame); +} + +static bool + expansion_send_data_response(Expansion* instance, const uint8_t* data, size_t data_size) { + furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); + + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeData, + .content.data.size = data_size, + }; + + memcpy(frame.content.data.bytes, data, data_size); + return expansion_send_frame(instance, &frame); +} + +// Called in Rpc session thread context +static void expansion_rpc_send_callback(void* context, uint8_t* data, size_t data_size) { + Expansion* instance = context; + + for(size_t sent_data_size = 0; sent_data_size < data_size;) { + if(furi_semaphore_acquire( + instance->tx_semaphore, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)) != + FuriStatusOk) { + furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagError); + break; + } + + const size_t current_data_size = + MIN(data_size - sent_data_size, EXPANSION_PROTOCOL_MAX_DATA_SIZE); + if(!expansion_send_data_response(instance, data + sent_data_size, current_data_size)) + break; + sent_data_size += current_data_size; + } +} + +static bool expansion_rpc_session_open(Expansion* instance) { + Rpc* rpc = furi_record_open(RECORD_RPC); + instance->rpc_session = rpc_session_open(rpc, RpcOwnerUart); + + if(instance->rpc_session) { + instance->tx_semaphore = furi_semaphore_alloc(1, 1); + rpc_session_set_context(instance->rpc_session, instance); + rpc_session_set_send_bytes_callback(instance->rpc_session, expansion_rpc_send_callback); + } + + return instance->rpc_session != NULL; +} + +static void expansion_rpc_session_close(Expansion* instance) { + if(instance->rpc_session) { + rpc_session_close(instance->rpc_session); + furi_semaphore_free(instance->tx_semaphore); + } + + furi_record_close(RECORD_RPC); +} + +static bool + expansion_handle_session_state_handshake(Expansion* instance, const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type != ExpansionFrameTypeBaudRate) break; + const uint32_t baud_rate = rx_frame->content.baud_rate.baud; + + FURI_LOG_D(TAG, "Proposed baud rate: %lu", baud_rate); + + if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { + instance->session_state = ExpansionSessionStateConnected; + // Send response at previous baud rate + if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; + furi_hal_serial_set_br(instance->serial_handle, baud_rate); + + } else { + if(!expansion_send_status_response(instance, ExpansionFrameErrorBaudRate)) break; + FURI_LOG_E(TAG, "Bad baud rate"); + } + success = true; + } while(false); + + return success; +} + +static bool + expansion_handle_session_state_connected(Expansion* instance, const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; + instance->session_state = ExpansionSessionStateRpcActive; + if(!expansion_rpc_session_open(instance)) break; + if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +static bool + expansion_handle_session_state_rpc_active(Expansion* instance, const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeData) { + if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; + + const size_t size_consumed = rpc_session_feed( + instance->rpc_session, + rx_frame->content.data.bytes, + rx_frame->content.data.size, + EXPANSION_PROTOCOL_TIMEOUT_MS); + if(size_consumed != rx_frame->content.data.size) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; + instance->session_state = ExpansionSessionStateConnected; + expansion_rpc_session_close(instance); + if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { + if(rx_frame->content.status.error != ExpansionFrameErrorNone) break; + furi_semaphore_release(instance->tx_semaphore); + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +static inline void expansion_state_machine(Expansion* instance) { + typedef bool (*ExpansionSessionStateHandler)(Expansion*, const ExpansionFrame*); + + static const ExpansionSessionStateHandler expansion_handlers[] = { + [ExpansionSessionStateHandShake] = expansion_handle_session_state_handshake, + [ExpansionSessionStateConnected] = expansion_handle_session_state_connected, + [ExpansionSessionStateRpcActive] = expansion_handle_session_state_rpc_active, + }; + + ExpansionFrame rx_frame; + + while(true) { + if(!expansion_receive_frame(instance, &rx_frame)) break; + if(!expansion_handlers[instance->session_state](instance, &rx_frame)) break; + } +} + +static void expansion_worker_pending_callback(void* context, uint32_t arg) { + furi_assert(context); + UNUSED(arg); + + Expansion* instance = context; + furi_thread_join(instance->worker_thread); + + // Do not re-enable detection interrupt on user-requested exit + if(instance->exit_reason != ExpansionSessionExitReasonUser) { + furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + instance->state = ExpansionStateEnabled; + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); + furi_mutex_release(instance->state_mutex); + } +} + +static int32_t expansion_worker(void* context) { + furi_assert(context); + Expansion* instance = context; + + furi_hal_power_insomnia_enter(); + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + + instance->serial_handle = furi_hal_serial_control_acquire(instance->serial_id); + furi_check(instance->serial_handle); + + FURI_LOG_D(TAG, "Service started"); + + instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_BUFFER_SIZE, 1); + instance->session_state = ExpansionSessionStateHandShake; + instance->exit_reason = ExpansionSessionExitReasonUnknown; + + furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); + + furi_hal_serial_async_rx_start( + instance->serial_handle, expansion_serial_rx_callback, instance, false); + + if(expansion_send_heartbeat(instance)) { + expansion_state_machine(instance); + } + + if(instance->session_state == ExpansionSessionStateRpcActive) { + expansion_rpc_session_close(instance); + } + + FURI_LOG_D(TAG, "Service stopped"); + + furi_hal_serial_control_release(instance->serial_handle); + furi_stream_buffer_free(instance->rx_buf); + + furi_hal_power_insomnia_exit(); + furi_timer_pending_callback(expansion_worker_pending_callback, instance, 0); + + return 0; +} + +// Called from the serial control thread +static void expansion_detect_callback(void* context) { + furi_assert(context); + Expansion* instance = context; + + furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + + if(instance->state == ExpansionStateEnabled) { + instance->state = ExpansionStateRunning; + furi_thread_start(instance->worker_thread); + } + + furi_mutex_release(instance->state_mutex); +} + +static Expansion* expansion_alloc() { + Expansion* instance = malloc(sizeof(Expansion)); + + instance->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + instance->worker_thread = furi_thread_alloc_ex(TAG, 768, expansion_worker, instance); + + return instance; +} + +void expansion_on_system_start(void* arg) { + UNUSED(arg); + + Expansion* instance = expansion_alloc(); + furi_record_create(RECORD_EXPANSION, instance); + + expansion_enable(instance); +} + +// Public API functions + +void expansion_enable(Expansion* instance) { + ExpansionSettings settings = {}; + if(!expansion_settings_load(&settings)) { + expansion_settings_save(&settings); + } else if(settings.uart_index < FuriHalSerialIdMax) { + expansion_set_listen_serial(instance, settings.uart_index); + } +} + +void expansion_disable(Expansion* instance) { + furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + + if(instance->state == ExpansionStateRunning) { + furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagStop); + furi_thread_join(instance->worker_thread); + } else if(instance->state == ExpansionStateEnabled) { + FURI_LOG_D(TAG, "Detection disabled"); + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + } + + instance->state = ExpansionStateDisabled; + + furi_mutex_release(instance->state_mutex); +} + +void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) { + expansion_disable(instance); + + furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + + instance->serial_id = serial_id; + instance->state = ExpansionStateEnabled; + + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); + + furi_mutex_release(instance->state_mutex); + + FURI_LOG_D(TAG, "Detection enabled"); +} diff --git a/applications/services/expansion/expansion.h b/applications/services/expansion/expansion.h new file mode 100644 index 00000000000..e169b3c15de --- /dev/null +++ b/applications/services/expansion/expansion.h @@ -0,0 +1,73 @@ +/** + * @file expansion.h + * @brief Expansion module support library. + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief FURI record key to access the expansion object. + */ +#define RECORD_EXPANSION "expansion" + +/** + * @brief Expansion opaque type declaration. + */ +typedef struct Expansion Expansion; + +/** + * @brief Enable support for expansion modules. + * + * Calling this function will load user settings and enable + * expansion module support on the serial port specified in said settings. + * + * If expansion module support was disabled in settings, this function + * does nothing. + * + * @param[in,out] instance pointer to the Expansion instance. + */ +void expansion_enable(Expansion* instance); + +/** + * @brief Disable support for expansion modules. + * + * Calling this function will cease all communications with the + * expansion module (if any), release the serial handle and + * reset the respective pins to the default state. + * + * @note Applications requiring serial port access MUST call + * this function BEFORE calling furi_hal_serial_control_acquire(). + * Similarly, an expansion_enable() call MUST be made right AFTER + * a call to furi_hal_serial_control_release() to ensure that + * the user settings are properly restored. + * + * @param[in,out] instance pointer to the Expansion instance. + */ +void expansion_disable(Expansion* instance); + +/** + * @brief Enable support for expansion modules on designated serial port. + * + * Only one serial port can be used to communicate with an expansion + * module at a time. + * + * Calling this function when expansion module support is already enabled + * will first disable the previous setting, then enable the current one. + * + * @warning This function does not respect user settings for expansion modules, + * so calling it might leave the system in inconsistent state. Avoid using it + * unless absolutely necessary. + * + * @param[in,out] instance pointer to the Expansion instance. + * @param[in] serial_id numerical identifier of the serial. + */ +void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/expansion/expansion_protocol.h b/applications/services/expansion/expansion_protocol.h new file mode 100644 index 00000000000..6ed818f82dd --- /dev/null +++ b/applications/services/expansion/expansion_protocol.h @@ -0,0 +1,353 @@ +/** + * @file expansion_protocol.h + * @brief Flipper Expansion Protocol parser reference implementation. + * + * This file is licensed separately under The Unlicense. + * See https://unlicense.org/ for more details. + * + * This parser is written with low-spec hardware in mind. It does not use + * dynamic memory allocation or Flipper-specific libraries and can be + * included directly into any module's firmware's sources. + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default baud rate to start all communications at. + */ +#define EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE (9600UL) + +/** + * @brief Maximum data size per frame, in bytes. + */ +#define EXPANSION_PROTOCOL_MAX_DATA_SIZE (64U) + +/** + * @brief Maximum allowed inactivity period, in milliseconds. + */ +#define EXPANSION_PROTOCOL_TIMEOUT_MS (250U) + +/** + * @brief Dead time after changing connection baud rate. + */ +#define EXPANSION_PROTOCOL_BAUD_CHANGE_DT_MS (25U) + +/** + * @brief Enumeration of supported frame types. + */ +typedef enum { + ExpansionFrameTypeHeartbeat = 1, /**< Heartbeat frame. */ + ExpansionFrameTypeStatus = 2, /**< Status report frame. */ + ExpansionFrameTypeBaudRate = 3, /**< Baud rate negotiation frame. */ + ExpansionFrameTypeControl = 4, /**< Control frame. */ + ExpansionFrameTypeData = 5, /**< Data frame. */ + ExpansionFrameTypeReserved, /**< Special value. */ +} ExpansionFrameType; + +/** + * @brief Enumeration of possible error types. + */ +typedef enum { + ExpansionFrameErrorNone = 0x00, /**< No error occurred. */ + ExpansionFrameErrorUnknown = 0x01, /**< An unknown error has occurred (generic response). */ + ExpansionFrameErrorBaudRate = 0x02, /**< Requested baud rate is not supported. */ +} ExpansionFrameError; + +/** + * @brief Enumeration of suported control commands. + */ +typedef enum { + ExpansionFrameControlCommandStartRpc = 0x00, /**< Start an RPC session. */ + ExpansionFrameControlCommandStopRpc = 0x01, /**< Stop an open RPC session. */ +} ExpansionFrameControlCommand; + +#pragma pack(push, 1) + +/** + * @brief Frame header structure. + */ +typedef struct { + uint8_t type; /**< Type of the frame. @see ExpansionFrameType. */ +} ExpansionFrameHeader; + +/** + * @brief Heartbeat frame contents. + */ +typedef struct { + /** Empty. */ +} ExpansionFrameHeartbeat; + +/** + * @brief Status frame contents. + */ +typedef struct { + uint8_t error; /**< Reported error code. @see ExpansionFrameError. */ +} ExpansionFrameStatus; + +/** + * @brief Baud rate frame contents. + */ +typedef struct { + uint32_t baud; /**< Requested baud rate. */ +} ExpansionFrameBaudRate; + +/** + * @brief Control frame contents. + */ +typedef struct { + uint8_t command; /**< Control command number. @see ExpansionFrameControlCommand. */ +} ExpansionFrameControl; + +/** + * @brief Data frame contents. + */ +typedef struct { + /** Size of the data. Must be less than EXPANSION_PROTOCOL_MAX_DATA_SIZE. */ + uint8_t size; + /** Data bytes. Valid only up to ExpansionFrameData::size bytes. */ + uint8_t bytes[EXPANSION_PROTOCOL_MAX_DATA_SIZE]; +} ExpansionFrameData; + +/** + * @brief Expansion protocol frame structure. + */ +typedef struct { + ExpansionFrameHeader header; /**< Header of the frame. Required. */ + union { + ExpansionFrameHeartbeat heartbeat; /**< Heartbeat frame contents. */ + ExpansionFrameStatus status; /**< Status frame contents. */ + ExpansionFrameBaudRate baud_rate; /**< Baud rate frame contents. */ + ExpansionFrameControl control; /**< Control frame contents. */ + ExpansionFrameData data; /**< Data frame contents. */ + } content; /**< Contents of the frame. */ +} ExpansionFrame; + +#pragma pack(pop) + +/** + * @brief Expansion checksum type. + */ +typedef uint8_t ExpansionFrameChecksum; + +/** + * @brief Receive function type declaration. + * + * @see expansion_frame_decode(). + * + * @param[out] data pointer to the buffer to reveive the data into. + * @param[in] data_size maximum output buffer capacity, in bytes. + * @param[in,out] context pointer to a user-defined context object. + * @returns number of bytes written into the output buffer. + */ +typedef size_t (*ExpansionFrameReceiveCallback)(uint8_t* data, size_t data_size, void* context); + +/** + * @brief Send function type declaration. + * + * @see expansion_frame_encode(). + * + * @param[in] data pointer to the buffer containing the data to be sent. + * @param[in] data_size size of the data to send, in bytes. + * @param[in,out] context pointer to a user-defined context object. + * @returns number of bytes actually sent. + */ +typedef size_t (*ExpansionFrameSendCallback)(const uint8_t* data, size_t data_size, void* context); + +/** + * @brief Get encoded frame size. + * + * The frame MUST be complete and properly formed. + * + * @param[in] frame pointer to the frame to be evaluated. + * @returns encoded frame size, in bytes. + */ +static inline size_t expansion_frame_get_encoded_size(const ExpansionFrame* frame) { + switch(frame->header.type) { + case ExpansionFrameTypeHeartbeat: + return sizeof(frame->header); + case ExpansionFrameTypeStatus: + return sizeof(frame->header) + sizeof(frame->content.status); + case ExpansionFrameTypeBaudRate: + return sizeof(frame->header) + sizeof(frame->content.baud_rate); + case ExpansionFrameTypeControl: + return sizeof(frame->header) + sizeof(frame->content.control); + case ExpansionFrameTypeData: + return sizeof(frame->header) + sizeof(frame->content.data.size) + frame->content.data.size; + default: + return 0; + } +} + +/** + * @brief Get remaining number of bytes needed to properly decode a frame. + * + * The return value will vary depending on the received_size parameter value. + * The frame is considered complete when the function returns 0. + * + * @param[in] frame pointer to the frame to be evaluated. + * @param[in] received_size number of bytes currently availabe for evaluation. + * @param[out] remaining_size pointer to the variable to contain the number of bytes needed for a complete frame. + * @returns true if the remaining size could be calculated, false on error. + */ +static inline bool expansion_frame_get_remaining_size( + const ExpansionFrame* frame, + size_t received_size, + size_t* remaining_size) { + if(received_size < sizeof(ExpansionFrameHeader)) { + // Frame type is unknown as of now + *remaining_size = sizeof(ExpansionFrameHeader); + return true; + } + + const size_t received_content_size = received_size - sizeof(ExpansionFrameHeader); + size_t content_size; + + switch(frame->header.type) { + case ExpansionFrameTypeHeartbeat: + content_size = 0; + break; + case ExpansionFrameTypeStatus: + content_size = sizeof(frame->content.status); + break; + case ExpansionFrameTypeBaudRate: + content_size = sizeof(frame->content.baud_rate); + break; + case ExpansionFrameTypeControl: + content_size = sizeof(frame->content.control); + break; + case ExpansionFrameTypeData: + if(received_content_size < sizeof(frame->content.data.size)) { + // Data size is unknown as of now + content_size = sizeof(frame->content.data.size); + } else if(frame->content.data.size > sizeof(frame->content.data.bytes)) { + // Malformed frame or garbage input + return false; + } else { + content_size = sizeof(frame->content.data.size) + frame->content.data.size; + } + break; + default: + return false; + } + + if(content_size > received_content_size) { + *remaining_size = content_size - received_content_size; + } else { + *remaining_size = 0; + } + + return true; +} + +/** + * @brief Enumeration of protocol parser statuses. + */ +typedef enum { + ExpansionProtocolStatusOk, /**< No error has occurred. */ + ExpansionProtocolStatusErrorFormat, /**< Invalid frame type. */ + ExpansionProtocolStatusErrorChecksum, /**< Checksum mismatch. */ + ExpansionProtocolStatusErrorCommunication, /**< Input/output error. */ +} ExpansionProtocolStatus; + +/** + * @brief Get the checksum byte corresponding to the frame + * + * Lightweight XOR checksum algorithm for basic error detection. + * + * @param[in] data pointer to a byte buffer containing the data. + * @param[in] data_size size of the data buffer. + * @returns checksum byte of the frame. + */ +static inline ExpansionFrameChecksum + expansion_protocol_get_checksum(const uint8_t* data, size_t data_size) { + ExpansionFrameChecksum checksum = 0; + for(size_t i = 0; i < data_size; ++i) { + checksum ^= data[i]; + } + return checksum; +} + +/** + * @brief Receive and decode a frame. + * + * Will repeatedly call the receive callback function until enough data is received. + * + * @param[out] frame pointer to the frame to contain decoded data. + * @param[in] receive pointer to the function used to receive data. + * @param[in,out] context pointer to a user-defined context object. Will be passed to the receive callback function. + * @returns ExpansionProtocolStatusOk on success, any other error code on failure. + */ +static inline ExpansionProtocolStatus expansion_protocol_decode( + ExpansionFrame* frame, + ExpansionFrameReceiveCallback receive, + void* context) { + size_t total_size = 0; + size_t remaining_size; + + while(true) { + if(!expansion_frame_get_remaining_size(frame, total_size, &remaining_size)) { + return ExpansionProtocolStatusErrorFormat; + } else if(remaining_size == 0) { + break; + } + + const size_t received_size = + receive((uint8_t*)frame + total_size, remaining_size, context); + + if(received_size == 0) { + return ExpansionProtocolStatusErrorCommunication; + } + + total_size += received_size; + } + + ExpansionFrameChecksum checksum; + const size_t received_size = receive(&checksum, sizeof(checksum), context); + + if(received_size != sizeof(checksum)) { + return ExpansionProtocolStatusErrorCommunication; + } else if(checksum != expansion_protocol_get_checksum((const uint8_t*)frame, total_size)) { + return ExpansionProtocolStatusErrorChecksum; + } else { + return ExpansionProtocolStatusOk; + } +} + +/** + * @brief Encode and send a frame. + * + * @param[in] frame pointer to the frame to be encoded and sent. + * @param[in] send pointer to the function used to send data. + * @param[in,out] context pointer to a user-defined context object. Will be passed to the send callback function. + * @returns ExpansionProtocolStatusOk on success, any other error code on failure. + */ +static inline ExpansionProtocolStatus expansion_protocol_encode( + const ExpansionFrame* frame, + ExpansionFrameSendCallback send, + void* context) { + const size_t encoded_size = expansion_frame_get_encoded_size(frame); + if(encoded_size == 0) { + return ExpansionProtocolStatusErrorFormat; + } + + const ExpansionFrameChecksum checksum = + expansion_protocol_get_checksum((const uint8_t*)frame, encoded_size); + + if((send((const uint8_t*)frame, encoded_size, context) != encoded_size) || + (send(&checksum, sizeof(checksum), context) != sizeof(checksum))) { + return ExpansionProtocolStatusErrorCommunication; + } else { + return ExpansionProtocolStatusOk; + } +} + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/expansion/expansion_settings.c b/applications/services/expansion/expansion_settings.c new file mode 100644 index 00000000000..586bf6e9cf8 --- /dev/null +++ b/applications/services/expansion/expansion_settings.c @@ -0,0 +1,30 @@ +#include "expansion_settings.h" + +#include +#include + +#include "expansion_settings_filename.h" + +#define EXPANSION_SETTINGS_PATH INT_PATH(EXPANSION_SETTINGS_FILE_NAME) +#define EXPANSION_SETTINGS_VERSION (0) +#define EXPANSION_SETTINGS_MAGIC (0xEA) + +bool expansion_settings_load(ExpansionSettings* settings) { + furi_assert(settings); + return saved_struct_load( + EXPANSION_SETTINGS_PATH, + settings, + sizeof(ExpansionSettings), + EXPANSION_SETTINGS_MAGIC, + EXPANSION_SETTINGS_VERSION); +} + +bool expansion_settings_save(ExpansionSettings* settings) { + furi_assert(settings); + return saved_struct_save( + EXPANSION_SETTINGS_PATH, + settings, + sizeof(ExpansionSettings), + EXPANSION_SETTINGS_MAGIC, + EXPANSION_SETTINGS_VERSION); +} diff --git a/applications/services/expansion/expansion_settings.h b/applications/services/expansion/expansion_settings.h new file mode 100644 index 00000000000..e7663f1b957 --- /dev/null +++ b/applications/services/expansion/expansion_settings.h @@ -0,0 +1,43 @@ +/** + * @file expansion_settings.h + * @brief Expansion module support settings. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Expansion module support settings storage type. + */ +typedef struct { + /** + * Numerical index of serial port used to communicate + * with expansion modules. + */ + uint8_t uart_index; +} ExpansionSettings; + +/** + * @brief Load expansion module support settings from file. + * + * @param[out] settings pointer to an ExpansionSettings instance to load settings into. + * @returns true if the settings were successfully loaded, false otherwise. + */ +bool expansion_settings_load(ExpansionSettings* settings); + +/** + * @brief Save expansion module support settings to file. + * + * @param[in] settings pointer to an ExpansionSettings instance to save settings from. + * @returns true if the settings were successfully saved, false otherwise. + */ +bool expansion_settings_save(ExpansionSettings* settings); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/expansion/expansion_settings_filename.h b/applications/services/expansion/expansion_settings_filename.h new file mode 100644 index 00000000000..23d6728e8e1 --- /dev/null +++ b/applications/services/expansion/expansion_settings_filename.h @@ -0,0 +1,9 @@ +/** + * @file expansion_settings_filename.h + */ +#pragma once + +/** + * @brief File name used for expansion settings. + */ +#define EXPANSION_SETTINGS_FILE_NAME ".expansion.settings" diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 29ec86ac669..158b95de641 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -55,7 +55,7 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const DialogMessage* message = dialog_message_alloc(); dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); dialog_message_set_buttons(message, NULL, NULL, NULL); - dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); dialog_message_set_text( message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); dialog_message_show(dialogs, message); diff --git a/applications/services/notification/notification_messages.c b/applications/services/notification/notification_messages.c index 28ec327c6e6..8b791622646 100644 --- a/applications/services/notification/notification_messages.c +++ b/applications/services/notification/notification_messages.c @@ -519,6 +519,24 @@ const NotificationSequence sequence_success = { NULL, }; +const NotificationSequence sequence_semi_success = { + &message_display_backlight_on, + &message_green_255, + &message_vibro_on, + &message_note_c4, + &message_delay_50, + &message_note_e4, + &message_delay_50, + &message_note_g4, + &message_delay_50, + &message_sound_off, + &message_delay_50, + &message_note_c5, + &message_delay_50, + &message_sound_off, + NULL, +}; + const NotificationSequence sequence_error = { &message_display_backlight_on, &message_red_255, diff --git a/applications/services/notification/notification_messages.h b/applications/services/notification/notification_messages.h index d87cf74f4ee..873bb37a868 100644 --- a/applications/services/notification/notification_messages.h +++ b/applications/services/notification/notification_messages.h @@ -138,6 +138,7 @@ extern const NotificationSequence sequence_blink_stop; extern const NotificationSequence sequence_single_vibro; extern const NotificationSequence sequence_double_vibro; extern const NotificationSequence sequence_success; +extern const NotificationSequence sequence_semi_success; extern const NotificationSequence sequence_error; extern const NotificationSequence sequence_audiovisual_alert; diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 5880e7d9f96..3179dbb5559 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -160,8 +160,11 @@ void rpc_session_set_terminated_callback( * command is gets processed - it's safe either. But case of it is quite * odd: client sends close request and sends command after. */ -size_t - rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, uint32_t timeout) { +size_t rpc_session_feed( + RpcSession* session, + const uint8_t* encoded_bytes, + size_t size, + uint32_t timeout) { furi_assert(session); furi_assert(encoded_bytes); diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index 863bca355b4..f7cda64f73b 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -35,6 +35,7 @@ typedef enum { RpcOwnerUnknown = 0, RpcOwnerBle, RpcOwnerUsb, + RpcOwnerUart, RpcOwnerCount, } RpcOwner; @@ -124,7 +125,7 @@ void rpc_session_set_terminated_callback( * * @return actually consumed bytes */ -size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, uint32_t timeout); +size_t rpc_session_feed(RpcSession* session, const uint8_t* buffer, size_t size, uint32_t timeout); /** Get available size of RPC buffer * @@ -136,4 +137,4 @@ size_t rpc_session_get_available_size(RpcSession* session); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index ca8fc61a45f..98860332d65 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -5,6 +5,41 @@ #include #include +// Contract assertion +_Static_assert(InputKeyMAX == 6, "InputKeyMAX"); +_Static_assert(InputTypeMAX == 5, "InputTypeMAX"); + +_Static_assert(InputKeyUp == (int32_t)PB_Gui_InputKey_UP, "InputKeyUp != PB_Gui_InputKey_UP"); +_Static_assert( + InputKeyDown == (int32_t)PB_Gui_InputKey_DOWN, + "InputKeyDown != PB_Gui_InputKey_DOWN"); +_Static_assert( + InputKeyRight == (int32_t)PB_Gui_InputKey_RIGHT, + "InputKeyRight != PB_Gui_InputKey_RIGHT"); +_Static_assert( + InputKeyLeft == (int32_t)PB_Gui_InputKey_LEFT, + "InputKeyLeft != PB_Gui_InputKey_LEFT"); +_Static_assert(InputKeyOk == (int32_t)PB_Gui_InputKey_OK, "InputKeyOk != PB_Gui_InputKey_OK"); +_Static_assert( + InputKeyBack == (int32_t)PB_Gui_InputKey_BACK, + "InputKeyBack != PB_Gui_InputKey_BACK"); + +_Static_assert( + InputTypePress == (int32_t)PB_Gui_InputType_PRESS, + "InputTypePress != PB_Gui_InputType_PRESS"); +_Static_assert( + InputTypeRelease == (int32_t)PB_Gui_InputType_RELEASE, + "InputTypeRelease != PB_Gui_InputType_RELEASE"); +_Static_assert( + InputTypeShort == (int32_t)PB_Gui_InputType_SHORT, + "InputTypeShort != PB_Gui_InputType_SHORT"); +_Static_assert( + InputTypeLong == (int32_t)PB_Gui_InputType_LONG, + "InputTypeLong != PB_Gui_InputType_LONG"); +_Static_assert( + InputTypeRepeat == (int32_t)PB_Gui_InputType_REPEAT, + "InputTypeRepeat != PB_Gui_InputType_REPEAT"); + #define TAG "RpcGui" typedef enum { @@ -168,63 +203,20 @@ static void RpcSession* session = rpc_gui->session; furi_assert(session); - InputEvent event; - - bool invalid = false; - - switch(request->content.gui_send_input_event_request.key) { - case PB_Gui_InputKey_UP: - event.key = InputKeyUp; - break; - case PB_Gui_InputKey_DOWN: - event.key = InputKeyDown; - break; - case PB_Gui_InputKey_RIGHT: - event.key = InputKeyRight; - break; - case PB_Gui_InputKey_LEFT: - event.key = InputKeyLeft; - break; - case PB_Gui_InputKey_OK: - event.key = InputKeyOk; - break; - case PB_Gui_InputKey_BACK: - event.key = InputKeyBack; - break; - default: - // Invalid key - invalid = true; - break; - } + bool is_valid = (request->content.gui_send_input_event_request.key < (int32_t)InputKeyMAX) && + (request->content.gui_send_input_event_request.type < (int32_t)InputTypeMAX); - switch(request->content.gui_send_input_event_request.type) { - case PB_Gui_InputType_PRESS: - event.type = InputTypePress; - break; - case PB_Gui_InputType_RELEASE: - event.type = InputTypeRelease; - break; - case PB_Gui_InputType_SHORT: - event.type = InputTypeShort; - break; - case PB_Gui_InputType_LONG: - event.type = InputTypeLong; - break; - case PB_Gui_InputType_REPEAT: - event.type = InputTypeRepeat; - break; - default: - // Invalid type - invalid = true; - break; - } - - if(invalid) { + if(!is_valid) { rpc_send_and_release_empty( session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } + InputEvent event = { + .key = (int32_t)request->content.gui_send_input_event_request.key, + .type = (int32_t)request->content.gui_send_input_event_request.type, + }; + // Event sequence shenanigans event.sequence_source = INPUT_SEQUENCE_SOURCE_SOFTWARE; if(event.type == InputTypePress) { @@ -264,6 +256,29 @@ static void rpc_system_gui_virtual_display_render_callback(Canvas* canvas, void* canvas_draw_xbm(canvas, 0, 0, canvas->width, canvas->height, rpc_gui->virtual_display_buffer); } +static void rpc_system_gui_virtual_display_input_callback(InputEvent* event, void* context) { + furi_assert(event); + furi_assert(event->key < InputKeyMAX); + furi_assert(event->type < InputTypeMAX); + furi_assert(context); + + RpcGuiSystem* rpc_gui = context; + RpcSession* session = rpc_gui->session; + + FURI_LOG_D(TAG, "VirtualDisplay: SendInputEvent"); + + PB_Main rpc_message = { + .command_id = 0, + .command_status = PB_CommandStatus_OK, + .has_next = false, + .which_content = PB_Main_gui_send_input_event_request_tag, + .content.gui_send_input_event_request.key = (int32_t)event->key, + .content.gui_send_input_event_request.type = (int32_t)event->type, + }; + + rpc_send_and_release(session, &rpc_message); +} + static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -300,6 +315,15 @@ static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, rpc_gui->virtual_display_view_port, rpc_system_gui_virtual_display_render_callback, rpc_gui); + + if(request->content.gui_start_virtual_display_request.send_input) { + FURI_LOG_D(TAG, "VirtualDisplay: input forwarding requested"); + view_port_input_callback_set( + rpc_gui->virtual_display_view_port, + rpc_system_gui_virtual_display_input_callback, + rpc_gui); + } + gui_add_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port, GuiLayerFullscreen); rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); @@ -440,4 +464,4 @@ void rpc_system_gui_free(void* context) { } furi_record_close(RECORD_GUI); free(rpc_gui); -} \ No newline at end of file +} diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index dcd7656fc47..8f0798d9cf6 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -11,7 +11,7 @@ typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message); -static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_product(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; FuriString* screen_header = furi_string_alloc_printf( @@ -31,8 +31,6 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me dialog_message_set_text( message, furi_string_get_cstr(screen_text), 0, 26, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); furi_string_free(screen_header); furi_string_free(screen_text); @@ -40,7 +38,7 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me return result; } -static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_address(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; const char* screen_text = "Flipper Devices Inc\n" @@ -50,12 +48,11 @@ static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* me dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); return result; } -static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_compliance(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; const char* screen_text = "For all compliance\n" @@ -64,35 +61,71 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); return result; } -static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_icon1(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; dialog_message_set_icon(message, &I_Certification1_103x56, 13, 0); result = dialog_message_show(dialogs, message); - dialog_message_set_icon(message, NULL, 0, 0); return result; } -static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_icon2(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; dialog_message_set_icon(message, &I_Certification2_46x33, 15, 10); dialog_message_set_text( message, furi_hal_version_get_mic_id(), 63, 27, AlignLeft, AlignCenter); result = dialog_message_show(dialogs, message); - dialog_message_set_icon(message, NULL, 0, 0); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); return result; } -static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_cert_china_0(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationChina0_121x41, 3, 3); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_cert_china_1(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationChina1_122x47, 3, 3); + dialog_message_set_text( + message, furi_hal_version_get_srrc_id(), 55, 11, AlignLeft, AlignBottom); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_cert_taiwan(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationTaiwan_33x32, 3, 10); + dialog_message_set_text( + message, furi_hal_version_get_ncc_id(), 39, 30, AlignLeft, AlignBottom); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_cert_mexico(DialogsApp* dialogs, DialogMessage* message) { + DialogMessageButton result; + + dialog_message_set_icon(message, &I_CertificationMexico_98x41, 17, 4); + result = dialog_message_show(dialogs, message); + + return result; +} + +static DialogMessageButton about_screen_hw_version(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; FuriString* buffer; buffer = furi_string_alloc(); @@ -118,14 +151,12 @@ static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* dialog_message_set_header(message, "HW Version Info:", 0, 0, AlignLeft, AlignTop); dialog_message_set_text(message, furi_string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); - dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); furi_string_free(buffer); return result; } -static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* message) { +static DialogMessageButton about_screen_fw_version(DialogsApp* dialogs, DialogMessage* message) { DialogMessageButton result; FuriString* buffer; buffer = furi_string_alloc(); @@ -157,21 +188,23 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* dialog_message_set_header(message, "FW Version Info:", 0, 0, AlignLeft, AlignTop); dialog_message_set_text(message, furi_string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); result = dialog_message_show(dialogs, message); - dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); - dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); furi_string_free(buffer); return result; } const AboutDialogScreen about_screens[] = { - product_screen, - compliance_screen, - address_screen, - icon1_screen, - icon2_screen, - hw_version_screen, - fw_version_screen}; + about_screen_product, + about_screen_compliance, + about_screen_address, + about_screen_icon1, + about_screen_icon2, + about_screen_cert_china_0, + about_screen_cert_china_1, + about_screen_cert_taiwan, + about_screen_cert_mexico, + about_screen_hw_version, + about_screen_fw_version}; int32_t about_settings_app(void* p) { UNUSED(p); @@ -201,6 +234,10 @@ int32_t about_settings_app(void* p) { screen_result = about_screens[screen_index](dialogs, message); + dialog_message_set_icon(message, NULL, 0, 0); + dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); + dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); + if(screen_result == DialogMessageButtonLeft) { if(screen_index <= 0) { break; diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c index 481ba6d5c85..b7ed63f63e8 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_forget_dev_success.c @@ -10,7 +10,7 @@ void bt_settings_scene_forget_dev_success_on_enter(void* context) { BtSettingsApp* app = context; Popup* popup = app->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); popup_set_header(popup, "Done", 14, 15, AlignLeft, AlignTop); popup_set_timeout(popup, 1500); popup_set_context(popup, app); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c index 7fbcc325219..43f05ec9bdb 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -24,8 +24,8 @@ void desktop_settings_scene_pin_disable_on_enter(void* context) { popup_set_context(app->popup, app); popup_set_callback(app->popup, pin_disable_back_callback); - popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_115x62); - popup_set_header(app->popup, "PIN\ndeleted!", 95, 9, AlignCenter, AlignCenter); + popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_119x62); + popup_set_header(app->popup, "Deleted", 80, 19, AlignLeft, AlignBottom); popup_set_timeout(app->popup, 1500); popup_enable_timeout(app->popup); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); diff --git a/applications/settings/expansion_settings_app/application.fam b/applications/settings/expansion_settings_app/application.fam new file mode 100644 index 00000000000..b253ad1744d --- /dev/null +++ b/applications/settings/expansion_settings_app/application.fam @@ -0,0 +1,9 @@ +App( + appid="expansion_settings", + name="Expansion Modules", + apptype=FlipperAppType.SETTINGS, + entry_point="expansion_settings_app", + requires=["gui"], + stack_size=1 * 1024, + order=80, +) diff --git a/applications/settings/expansion_settings_app/expansion_settings_app.c b/applications/settings/expansion_settings_app/expansion_settings_app.c new file mode 100644 index 00000000000..05e5f22e428 --- /dev/null +++ b/applications/settings/expansion_settings_app/expansion_settings_app.c @@ -0,0 +1,91 @@ +#include "expansion_settings_app.h" + +static const char* const expansion_uart_text[] = { + "USART", + "LPUART", + "None", +}; + +static void expansion_settings_app_uart_changed(VariableItem* item) { + ExpansionSettingsApp* app = variable_item_get_context(item); + const uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, expansion_uart_text[index]); + app->settings.uart_index = index; + + if(index < FuriHalSerialIdMax) { + expansion_set_listen_serial(app->expansion, index); + } else { + expansion_disable(app->expansion); + } +} + +static uint32_t expansion_settings_app_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +static ExpansionSettingsApp* expansion_settings_app_alloc() { + ExpansionSettingsApp* app = malloc(sizeof(ExpansionSettingsApp)); + + if(!expansion_settings_load(&app->settings)) { + expansion_settings_save(&app->settings); + } + + app->gui = furi_record_open(RECORD_GUI); + app->expansion = furi_record_open(RECORD_EXPANSION); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->var_item_list = variable_item_list_alloc(); + + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + app->var_item_list, + "Listen UART", + COUNT_OF(expansion_uart_text), + expansion_settings_app_uart_changed, + app); + value_index = app->settings.uart_index; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, expansion_uart_text[value_index]); + + view_set_previous_callback( + variable_item_list_get_view(app->var_item_list), expansion_settings_app_exit); + view_dispatcher_add_view( + app->view_dispatcher, + ExpansionSettingsViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + view_dispatcher_switch_to_view(app->view_dispatcher, ExpansionSettingsViewVarItemList); + + return app; +} + +static void expansion_settings_app_free(ExpansionSettingsApp* app) { + furi_assert(app); + + expansion_settings_save(&app->settings); + + view_dispatcher_remove_view(app->view_dispatcher, ExpansionSettingsViewVarItemList); + variable_item_list_free(app->var_item_list); + view_dispatcher_free(app->view_dispatcher); + + furi_record_close(RECORD_EXPANSION); + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t expansion_settings_app(void* p) { + UNUSED(p); + ExpansionSettingsApp* app = expansion_settings_app_alloc(); + view_dispatcher_run(app->view_dispatcher); + expansion_settings_app_free(app); + return 0; +} diff --git a/applications/settings/expansion_settings_app/expansion_settings_app.h b/applications/settings/expansion_settings_app/expansion_settings_app.h new file mode 100644 index 00000000000..a43bf853fcd --- /dev/null +++ b/applications/settings/expansion_settings_app/expansion_settings_app.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + VariableItemList* var_item_list; + Expansion* expansion; + ExpansionSettings settings; +} ExpansionSettingsApp; + +typedef enum { + ExpansionSettingsViewVarItemList, +} ExpansionSettingsView; diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index a5bf1b9d376..e734c78e03b 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -125,7 +125,7 @@ void storage_settings_scene_benchmark_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); if(sd_status != FSE_OK) { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c index 8af065bf8bb..862f55a4644 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -14,7 +14,7 @@ void storage_settings_scene_format_confirm_on_enter(void* context) { FS_Error sd_status = storage_sd_status(app->fs_api); if(sd_status == FSE_NOT_READY) { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c index df5e3cc17d5..f107aaceaec 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -47,7 +47,7 @@ void storage_settings_scene_formatting_on_enter(void* context) { dialog_ex_set_text( dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); } else { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop); } dialog_ex_set_center_button_text(dialog_ex, "OK"); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index 81c786d0cb0..aa9662a7143 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -19,7 +19,7 @@ void storage_settings_scene_sd_info_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback); if(sd_status != FSE_OK) { - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c index 33bb9552297..86398b1c952 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c @@ -42,7 +42,7 @@ void storage_settings_scene_unmounted_on_enter(void* context) { } dialog_ex_set_center_button_text(dialog_ex, "OK"); - dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48); + dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); dialog_ex_set_context(dialog_ex, app); dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback); diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index d19b4747b9c..832bc126c57 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -24,12 +24,56 @@ const uint32_t log_level_value[] = { }; static void log_level_changed(VariableItem* item) { - // SystemSettings* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, log_level_text[index]); furi_hal_rtc_set_log_level(log_level_value[index]); } +const char* const log_device_text[] = { + "USART", + "LPUART", + "None", +}; + +const uint32_t log_device_value[] = { + FuriHalRtcLogDeviceUsart, + FuriHalRtcLogDeviceLpuart, + FuriHalRtcLogDeviceNone}; + +static void log_device_changed(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, log_device_text[index]); + furi_hal_rtc_set_log_device(log_device_value[index]); +} + +const char* const log_baud_rate_text[] = { + "9600", + "38400", + "57600", + "115200", + "230400", + "460800", + "921600", + "1843200", +}; + +const uint32_t log_baud_rate_value[] = { + FuriHalRtcLogBaudRate9600, + FuriHalRtcLogBaudRate38400, + FuriHalRtcLogBaudRate57600, + FuriHalRtcLogBaudRate115200, + FuriHalRtcLogBaudRate230400, + FuriHalRtcLogBaudRate460800, + FuriHalRtcLogBaudRate921600, + FuriHalRtcLogBaudRate1843200, +}; + +static void log_baud_rate_changed(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, log_baud_rate_text[index]); + furi_hal_rtc_set_log_baud_rate(log_baud_rate_value[index]); +} + const char* const debug_text[] = { "OFF", "ON", @@ -64,7 +108,6 @@ const uint32_t heap_trace_mode_value[] = { }; static void heap_trace_mode_changed(VariableItem* item) { - // SystemSettings* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, heap_trace_mode_text[index]); furi_hal_rtc_set_heap_track_mode(heap_trace_mode_value[index]); @@ -81,7 +124,6 @@ const uint32_t mesurement_units_value[] = { }; static void mesurement_units_changed(VariableItem* item) { - // SystemSettings* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, mesurement_units_text[index]); locale_set_measurement_unit(mesurement_units_value[index]); @@ -98,7 +140,6 @@ const uint32_t time_format_value[] = { }; static void time_format_changed(VariableItem* item) { - // SystemSettings* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, time_format_text[index]); locale_set_time_format(time_format_value[index]); @@ -117,7 +158,6 @@ const uint32_t date_format_value[] = { }; static void date_format_changed(VariableItem* item) { - // SystemSettings* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, date_format_text[index]); locale_set_date_format(date_format_value[index]); @@ -227,6 +267,24 @@ SystemSettings* system_settings_alloc() { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, log_level_text[value_index]); + item = variable_item_list_add( + app->var_item_list, "Log Device", COUNT_OF(log_device_text), log_device_changed, app); + value_index = value_index_uint32( + furi_hal_rtc_get_log_device(), log_device_value, COUNT_OF(log_device_text)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, log_device_text[value_index]); + + item = variable_item_list_add( + app->var_item_list, + "Log Baud Rate", + COUNT_OF(log_baud_rate_text), + log_baud_rate_changed, + app); + value_index = value_index_uint32( + furi_hal_rtc_get_log_baud_rate(), log_baud_rate_value, COUNT_OF(log_baud_rate_text)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, log_baud_rate_text[value_index]); + item = variable_item_list_add( app->var_item_list, "Debug", COUNT_OF(debug_text), debug_changed, app); value_index = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) ? 1 : 0; diff --git a/assets/icons/About/CertificationChina0_121x41.png b/assets/icons/About/CertificationChina0_121x41.png new file mode 100644 index 00000000000..1d28577ab3e Binary files /dev/null and b/assets/icons/About/CertificationChina0_121x41.png differ diff --git a/assets/icons/About/CertificationChina1_122x47.png b/assets/icons/About/CertificationChina1_122x47.png new file mode 100644 index 00000000000..c5eebec6776 Binary files /dev/null and b/assets/icons/About/CertificationChina1_122x47.png differ diff --git a/assets/icons/About/CertificationMexico_98x41.png b/assets/icons/About/CertificationMexico_98x41.png new file mode 100644 index 00000000000..9b1e45e6f24 Binary files /dev/null and b/assets/icons/About/CertificationMexico_98x41.png differ diff --git a/assets/icons/About/CertificationTaiwan_33x32.png b/assets/icons/About/CertificationTaiwan_33x32.png new file mode 100644 index 00000000000..bf2bfa21a7b Binary files /dev/null and b/assets/icons/About/CertificationTaiwan_33x32.png differ diff --git a/assets/icons/Dolphin/DolphinCommon_56x48.png b/assets/icons/Dolphin/DolphinCommon_56x48.png deleted file mode 100644 index 089aaed8350..00000000000 Binary files a/assets/icons/Dolphin/DolphinCommon_56x48.png and /dev/null differ diff --git a/assets/icons/Dolphin/DolphinDone_80x58.png b/assets/icons/Dolphin/DolphinDone_80x58.png new file mode 100644 index 00000000000..594d62d5294 Binary files /dev/null and b/assets/icons/Dolphin/DolphinDone_80x58.png differ diff --git a/assets/icons/Dolphin/DolphinMafia_119x62.png b/assets/icons/Dolphin/DolphinMafia_119x62.png new file mode 100644 index 00000000000..1bbbec84ad7 Binary files /dev/null and b/assets/icons/Dolphin/DolphinMafia_119x62.png differ diff --git a/assets/icons/Infrared/DolphinReadingSuccess_59x63.png b/assets/icons/Dolphin/DolphinReadingSuccess_59x63.png similarity index 100% rename from assets/icons/Infrared/DolphinReadingSuccess_59x63.png rename to assets/icons/Dolphin/DolphinReadingSuccess_59x63.png diff --git a/assets/icons/Dolphin/DolphinSaved_92x58.png b/assets/icons/Dolphin/DolphinSaved_92x58.png new file mode 100644 index 00000000000..e8704295c6e Binary files /dev/null and b/assets/icons/Dolphin/DolphinSaved_92x58.png differ diff --git a/assets/icons/Dolphin/DolphinSuccess_91x55.png b/assets/icons/Dolphin/DolphinSuccess_91x55.png new file mode 100644 index 00000000000..80caeb203c6 Binary files /dev/null and b/assets/icons/Dolphin/DolphinSuccess_91x55.png differ diff --git a/assets/icons/iButton/DolphinWait_61x59.png b/assets/icons/Dolphin/DolphinWait_61x59.png similarity index 100% rename from assets/icons/iButton/DolphinWait_61x59.png rename to assets/icons/Dolphin/DolphinWait_61x59.png diff --git a/assets/icons/Dolphin/WarningDolphinFlip_45x42.png b/assets/icons/Dolphin/WarningDolphinFlip_45x42.png new file mode 100644 index 00000000000..2ba54afce02 Binary files /dev/null and b/assets/icons/Dolphin/WarningDolphinFlip_45x42.png differ diff --git a/assets/icons/Interface/WarningDolphin_45x42.png b/assets/icons/Dolphin/WarningDolphin_45x42.png similarity index 100% rename from assets/icons/Interface/WarningDolphin_45x42.png rename to assets/icons/Dolphin/WarningDolphin_45x42.png diff --git a/assets/icons/NFC/MFKey_qr_25x25.png b/assets/icons/NFC/MFKey_qr_25x25.png new file mode 100644 index 00000000000..feb07e2807e Binary files /dev/null and b/assets/icons/NFC/MFKey_qr_25x25.png differ diff --git a/assets/icons/NFC/check_big_20x17.png b/assets/icons/NFC/check_big_20x17.png new file mode 100644 index 00000000000..0e84cfa071c Binary files /dev/null and b/assets/icons/NFC/check_big_20x17.png differ diff --git a/assets/icons/RFID/RFIDDolphinSuccess_108x57.png b/assets/icons/RFID/RFIDDolphinSuccess_108x57.png deleted file mode 100644 index 34199910945..00000000000 Binary files a/assets/icons/RFID/RFIDDolphinSuccess_108x57.png and /dev/null differ diff --git a/assets/icons/SubGhz/External_ant_1_9x11.png b/assets/icons/SubGhz/External_ant_1_9x11.png new file mode 100644 index 00000000000..175f16048c5 Binary files /dev/null and b/assets/icons/SubGhz/External_ant_1_9x11.png differ diff --git a/assets/icons/SubGhz/External_antenna_20x12.png b/assets/icons/SubGhz/External_antenna_20x12.png deleted file mode 100644 index 940087071a1..00000000000 Binary files a/assets/icons/SubGhz/External_antenna_20x12.png and /dev/null differ diff --git a/assets/icons/SubGhz/Internal_ant_1_9x11.png b/assets/icons/SubGhz/Internal_ant_1_9x11.png new file mode 100644 index 00000000000..62be9f276f3 Binary files /dev/null and b/assets/icons/SubGhz/Internal_ant_1_9x11.png differ diff --git a/assets/icons/SubGhz/Internal_antenna_20x12.png b/assets/icons/SubGhz/Internal_antenna_20x12.png deleted file mode 100644 index a8a5be09fb8..00000000000 Binary files a/assets/icons/SubGhz/Internal_antenna_20x12.png and /dev/null differ diff --git a/assets/icons/SubGhz/SubGhz_External_ant/frame_01.png b/assets/icons/SubGhz/SubGhz_External_ant/frame_01.png new file mode 100644 index 00000000000..175f16048c5 Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_External_ant/frame_01.png differ diff --git a/assets/icons/SubGhz/SubGhz_External_ant/frame_02.png b/assets/icons/SubGhz/SubGhz_External_ant/frame_02.png new file mode 100644 index 00000000000..3285496fea8 Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_External_ant/frame_02.png differ diff --git a/assets/icons/SubGhz/SubGhz_External_ant/frame_03.png b/assets/icons/SubGhz/SubGhz_External_ant/frame_03.png new file mode 100644 index 00000000000..fdeaa296c2e Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_External_ant/frame_03.png differ diff --git a/assets/icons/SubGhz/SubGhz_External_ant/frame_04.png b/assets/icons/SubGhz/SubGhz_External_ant/frame_04.png new file mode 100644 index 00000000000..3acbc03f44d Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_External_ant/frame_04.png differ diff --git a/assets/icons/SubGhz/SubGhz_External_ant/frame_rate b/assets/icons/SubGhz/SubGhz_External_ant/frame_rate new file mode 100644 index 00000000000..62f9457511f --- /dev/null +++ b/assets/icons/SubGhz/SubGhz_External_ant/frame_rate @@ -0,0 +1 @@ +6 \ No newline at end of file diff --git a/assets/icons/SubGhz/SubGhz_Internal_ant/frame_01.png b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_01.png new file mode 100644 index 00000000000..62be9f276f3 Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_01.png differ diff --git a/assets/icons/SubGhz/SubGhz_Internal_ant/frame_02.png b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_02.png new file mode 100644 index 00000000000..cd87fcdc9d5 Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_02.png differ diff --git a/assets/icons/SubGhz/SubGhz_Internal_ant/frame_03.png b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_03.png new file mode 100644 index 00000000000..459bd3eca18 Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_03.png differ diff --git a/assets/icons/SubGhz/SubGhz_Internal_ant/frame_04.png b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_04.png new file mode 100644 index 00000000000..1d785d45341 Binary files /dev/null and b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_04.png differ diff --git a/assets/icons/SubGhz/SubGhz_Internal_ant/frame_rate b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_rate new file mode 100644 index 00000000000..62f9457511f --- /dev/null +++ b/assets/icons/SubGhz/SubGhz_Internal_ant/frame_rate @@ -0,0 +1 @@ +6 \ No newline at end of file diff --git a/assets/icons/iButton/DolphinMafia_115x62.png b/assets/icons/iButton/DolphinMafia_115x62.png deleted file mode 100644 index 66fdb40ff26..00000000000 Binary files a/assets/icons/iButton/DolphinMafia_115x62.png and /dev/null differ diff --git a/assets/icons/iButton/DolphinNice_96x59.png b/assets/icons/iButton/DolphinNice_96x59.png deleted file mode 100644 index a299d363023..00000000000 Binary files a/assets/icons/iButton/DolphinNice_96x59.png and /dev/null differ diff --git a/assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png b/assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png deleted file mode 100644 index 2b4bec7c6f1..00000000000 Binary files a/assets/icons/iButton/iButtonDolphinVerySuccess_108x52.png and /dev/null differ diff --git a/assets/icons/iButton/iButtonDolphinVerySuccess_92x55.png b/assets/icons/iButton/iButtonDolphinVerySuccess_92x55.png new file mode 100644 index 00000000000..0a85465cc52 Binary files /dev/null and b/assets/icons/iButton/iButtonDolphinVerySuccess_92x55.png differ diff --git a/assets/protobuf b/assets/protobuf index 23ad19a7566..1956b83bba9 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 23ad19a756649ed9f6677b598e5361c5cce6847b +Subproject commit 1956b83bba99313ee8d8386e5d35d0549341ca26 diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md new file mode 100644 index 00000000000..c757c0d2b42 --- /dev/null +++ b/documentation/ExpansionModules.md @@ -0,0 +1,164 @@ +# Expansion Module Protocol - Draft + +## Terms and definitions + +- Expansion Module: A third-party hardware unit meant for use with Flipper Zero by connecting it to its GPIO header. +- Expansion Module Protocol: A serial-based, byte-oriented, synchronous communication protocol described in this document. +- Host: Hardware unit tasked with serving requests. Used interchangeably with Flipper, Server, Host etc. throughout this document. +- Device: Used interchangeably with Expansion Module, Module, Client, etc. +- RPC: Remote Procedure Call, a protobuf-based communication protocol widely used by Flipper Zero companion applications. +- Timeout Interval: Period of inactivity to be treated as a loss of connection, also denoted as Tto. Equals to 250 ms. +- Baud Rate Switch Dead Time: Period of time after baud rate change during which no communication is allowed, also denoted Tdt. Equals to 25 ms. + +## Features + +- Automatic expansion module detection +- Baud rate negotiation +- Basic error detection +- Request-response communication flow +- Integration with Flipper RPC protocol + +## Hardware + +Depending on the UART selected for communication, the following pins area available for the expansion modules to connect to: + +| UART | Tx pin | Rx pin | +|--------|--------|--------| +| USART | 13 | 14 | +| LPUART | 15 | 16 | + +## Frame structure + +Each frame consists of a header (1 byte), contents (size depends of frame type) and checksum (1 byte) fields: + +| Header (1 byte) | Contents (0 or more bytes) | Checksum (1 byte) | +|-----------------|----------------------------|-------------------| +| Frame type | Frame payload | XOR checksum | + +### Heartbeat frame + +HEARTBEAT frames are used to maintain an idle connection. In the event of not receiving any frames within Tto, either side must cease all communications and be ready to initiate the connection again. + +| Header (1 byte) | Checksum (1 byte) | +|-----------------|-------------------| +| 0x01 | XOR checksum | + +Note that the contents field is not present (0 bytes length). + +### Status frame + +STATUS frames are used to report the status of a transaction. Every received frame MUST be confirmed by a matching STATUS response. + +| Header (1 byte) | Contents (1 byte) | Checksum (1 byte) | +|-----------------|-------------------|-------------------| +| 0x02 | Error code | XOR checksum | + +The `Error code` field SHALL have one of the following values: + +| Error code | Meaning | +|------------|-------------------------| +| 0x00 | OK (No error) | +| 0x01 | Unknown error | +| 0x02 | Baud rate not supported | + +### Baud rate frame + +BAUD RATE frames are used to negotiate communication speed. The initial connection SHALL always happen at 9600 baud. The first message sent by the module MUST be a BAUD RATE frame, even if a different speed is not required. + +| Header (1 byte) | Contents (4 bytes) | Checksum (1 byte) | +|-----------------|--------------------|-------------------| +| 0x03 | Baud rate | XOR checksum | + +If the requested baud rate is supported by the host, it SHALL respond with a STATUS frame with an OK error code, otherwise the error code SHALL be 0x02 (Baud rate not supported). Until the negotiation succeeds, the speed SHALL remain at 9600 baud. The module MAY send additional BAUD RATE frames with alternative speeds in case the initial request was refused. No other frames are allowed until the speed negotiation succeeds. + +### Control frame + +CONTROL frames are used to control various aspects of the communication. As of now, the sole purpose of CONTROL frames is to start and stop the RPC session. + +| Header (1 byte) | Contents (1 byte) | Checksum (1 byte) | +|-----------------|-------------------|-------------------| +| 0x04 | Command | XOR checksum | + +The `Command` field SHALL have one of the followind values: + +| Command | Meaning | +|---------|-------------------| +| 0x00 | Start RPC session | +| 0x01 | Stop RPC session | + +### Data frame + +DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is curretly open, all received bytes are forwarded to it. + +| Header (1 byte) | Contents (1 to 65 byte(s)) | Checksum (1 byte) | +|-----------------|----------------------------|-------------------| +| 0x05 | Data | XOR checksum | + +The `Data` field SHALL have the following structure: + +| Data size (1 byte) | Data (0 to 64 bytes) | +|--------------------|----------------------| +| 0x00 ... 0x40 | Arbitrary data | + +## Communication flow + +In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings -> Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules. + +The communication is always initiated by the module by the means of shortly pulling the RX pin down. The host SHALL respond with a HEARTBEAT frame indicating that it is ready to receive requests. The module then MUST issue a BAUDRATE request within Tto. Failure to do so will result in the host dropping the connection and returning to its initial state. + +``` + MODULE | FLIPPER +-----------------------------+--------------------------- + | (Start) +Pull down RX --> + <-- Heartbeat +Baud Rate --> + <-- Status [OK | Error] + | +(Module changes baud rate | (Flipper changes + and waits for Tdt) | baud rate) + | +Control [Start RPC] --> + <-- Status [OK | Error] +-----------------------------+--------------------------- (1) +Data [RPC Request] --> + <-- Status [OK | Error] + <-- Data [RPC Response] +Status [OK | Error] --> +-----------------------------+--------------------------- (2) +Data [RPC Request pt.1] --> + <-- Status [OK | Error] +Data [RPC Request pt.2] --> + <-- Status [OK | Error] +Data [RPC Request pt.3] --> + <-- Status [OK | Error] + <-- Data [RPC Response] +Status [OK | Error] --> +-----------------------------+--------------------------- (3) +Heartbeat --> + <-- Heartbeat +Heartbeat --> + <-- Heartbeat +-----------------------------+--------------------------- +Control [Stop RPC] --> + <-- Status [OK | Error] +(Module disconnected) | + | (No activity within Tto + | return to start) + +(1) The module MUST confirm all implicitly requested frames (e.g. DATA frames containing RPC responses) with a STATUS frame. +(2) RPC requests larger than 64 bytes are split into multiple frames. Every DATA frame MUST be confirmed with a STATUS frame. +(3) When the module has no data to send, it MUST send HEARTBEAT frames with a period < Tto in order to maintain the connection. + The host SHALL respond with a HEARTBEAT frame each time. +``` + +## Error detection + +Error detection is implemented via adding an extra checksum byte to every frame (see above). + +The checksum is calculated by bitwise XOR-ing every byte in the frame (excluding the checksum byte itself), with an initial value of 0. + +### Error recovery behaviour + +In the event of a detected error, the concerned side MUST cease all communications and reset to initial state. The other side will then experience +a communication timeout and the connection will be re-established automatically. diff --git a/documentation/FuriHalBus.md b/documentation/FuriHalBus.md index 230a98050fd..7880c041f6c 100644 --- a/documentation/FuriHalBus.md +++ b/documentation/FuriHalBus.md @@ -58,7 +58,7 @@ When not using the API, these peripherals MUST be enabled by the user code and t | SPI2 | -- | | I2C1 | `furi_hal_i2c.h` | | I2C3 | -- | -| USART1 | `furi_hal_uart.h` | +| USART1 | `furi_hal_serial.h` | | LPUART1 | -- | | USB | `furi_hal_usb.h` | @@ -102,8 +102,8 @@ Below is the list of DMA channels and their usage by the system. | -- | 3 | | | | -- | 4 | yes | pulse reader | | -- | 5 | | | -| -- | 6 | | | -| -- | 7 | | | +| -- | 6 | yes | USART_Rx | +| -- | 7 | yes | LPUART_Rx | | DMA2 | 1 | yes | infrared, lfrfid, subghz, | | -- | 2 | yes | -- | | -- | 3 | yes | cc1101_ext | diff --git a/firmware.scons b/firmware.scons index 004def9a999..901a762145b 100644 --- a/firmware.scons +++ b/firmware.scons @@ -249,7 +249,7 @@ fw_artifacts.extend( ) -fwcdb = fwenv.CompilationDatabase() +fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase() # without filtering, both updater & firmware commands would be generated in same file fwenv.Replace( COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*"), diff --git a/furi/core/check.c b/furi/core/check.c index b56db656379..233b574b041 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -2,7 +2,6 @@ #include "common_defines.h" #include -#include #include #include #include @@ -59,69 +58,69 @@ extern size_t xPortGetTotalHeapSize(void); static void __furi_put_uint32_as_text(uint32_t data) { char tmp_str[] = "-2147483648"; itoa(data, tmp_str, 10); - furi_hal_console_puts(tmp_str); + furi_log_puts(tmp_str); } static void __furi_put_uint32_as_hex(uint32_t data) { char tmp_str[] = "0xFFFFFFFF"; itoa(data, tmp_str, 16); - furi_hal_console_puts(tmp_str); + furi_log_puts(tmp_str); } static void __furi_print_register_info() { // Print registers for(uint8_t i = 0; i < 12; i++) { - furi_hal_console_puts("\r\n\tr"); + furi_log_puts("\r\n\tr"); __furi_put_uint32_as_text(i); - furi_hal_console_puts(" : "); + furi_log_puts(" : "); __furi_put_uint32_as_hex(__furi_check_registers[i]); } - furi_hal_console_puts("\r\n\tlr : "); + furi_log_puts("\r\n\tlr : "); __furi_put_uint32_as_hex(__furi_check_registers[12]); } static void __furi_print_stack_info() { - furi_hal_console_puts("\r\n\tstack watermark: "); + furi_log_puts("\r\n\tstack watermark: "); __furi_put_uint32_as_text(uxTaskGetStackHighWaterMark(NULL) * 4); } static void __furi_print_bt_stack_info() { const FuriHalBtHardfaultInfo* fault_info = furi_hal_bt_get_hardfault_info(); if(fault_info == NULL) { - furi_hal_console_puts("\r\n\tcore2: not faulted"); + furi_log_puts("\r\n\tcore2: not faulted"); } else { - furi_hal_console_puts("\r\n\tcore2: hardfaulted.\r\n\tPC: "); + furi_log_puts("\r\n\tcore2: hardfaulted.\r\n\tPC: "); __furi_put_uint32_as_hex(fault_info->source_pc); - furi_hal_console_puts("\r\n\tLR: "); + furi_log_puts("\r\n\tLR: "); __furi_put_uint32_as_hex(fault_info->source_lr); - furi_hal_console_puts("\r\n\tSP: "); + furi_log_puts("\r\n\tSP: "); __furi_put_uint32_as_hex(fault_info->source_sp); } } static void __furi_print_heap_info() { - furi_hal_console_puts("\r\n\t heap total: "); + furi_log_puts("\r\n\t heap total: "); __furi_put_uint32_as_text(xPortGetTotalHeapSize()); - furi_hal_console_puts("\r\n\t heap free: "); + furi_log_puts("\r\n\t heap free: "); __furi_put_uint32_as_text(xPortGetFreeHeapSize()); - furi_hal_console_puts("\r\n\t heap watermark: "); + furi_log_puts("\r\n\t heap watermark: "); __furi_put_uint32_as_text(xPortGetMinimumEverFreeHeapSize()); } static void __furi_print_name(bool isr) { if(isr) { - furi_hal_console_puts("[ISR "); + furi_log_puts("[ISR "); __furi_put_uint32_as_text(__get_IPSR()); - furi_hal_console_puts("] "); + furi_log_puts("] "); } else { const char* name = pcTaskGetName(NULL); if(name == NULL) { - furi_hal_console_puts("[main] "); + furi_log_puts("[main] "); } else { - furi_hal_console_puts("["); - furi_hal_console_puts(name); - furi_hal_console_puts("] "); + furi_log_puts("["); + furi_log_puts(name); + furi_log_puts("] "); } } } @@ -140,9 +139,9 @@ FURI_NORETURN void __furi_crash_implementation() { __furi_check_message = "furi_check failed"; } - furi_hal_console_puts("\r\n\033[0;31m[CRASH]"); + furi_log_puts("\r\n\033[0;31m[CRASH]"); __furi_print_name(isr); - furi_hal_console_puts(__furi_check_message); + furi_log_puts(__furi_check_message); __furi_print_register_info(); if(!isr) { @@ -157,8 +156,8 @@ FURI_NORETURN void __furi_crash_implementation() { #ifdef FURI_NDEBUG if(debug) { #endif - furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n"); - furi_hal_console_puts("\033[0m\r\n"); + furi_log_puts("\r\nSystem halted. Connect debugger for more info\r\n"); + furi_log_puts("\033[0m\r\n"); furi_hal_debug_enable(); RESTORE_REGISTERS_AND_HALT_MCU(debug); @@ -169,8 +168,8 @@ FURI_NORETURN void __furi_crash_implementation() { ptr = (uint32_t) "Check serial logs"; } furi_hal_rtc_set_fault_data(ptr); - furi_hal_console_puts("\r\nRebooting system.\r\n"); - furi_hal_console_puts("\033[0m\r\n"); + furi_log_puts("\r\nRebooting system.\r\n"); + furi_log_puts("\033[0m\r\n"); furi_hal_power_reset(); } #endif @@ -187,11 +186,11 @@ FURI_NORETURN void __furi_halt_implementation() { __furi_check_message = "System halt requested."; } - furi_hal_console_puts("\r\n\033[0;31m[HALT]"); + furi_log_puts("\r\n\033[0;31m[HALT]"); __furi_print_name(isr); - furi_hal_console_puts(__furi_check_message); - furi_hal_console_puts("\r\nSystem halted. Bye-bye!\r\n"); - furi_hal_console_puts("\033[0m\r\n"); + furi_log_puts(__furi_check_message); + furi_log_puts("\r\nSystem halted. Bye-bye!\r\n"); + furi_log_puts("\033[0m\r\n"); // Check if debug enabled by DAP // https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en diff --git a/furi/core/log.c b/furi/core/log.c index 53467ecdb2d..3d270816c5f 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -2,17 +2,19 @@ #include "check.h" #include "mutex.h" #include +#include + +LIST_DEF(FuriLogHandlersList, FuriLogHandler, M_POD_OPLIST) #define FURI_LOG_LEVEL_DEFAULT FuriLogLevelInfo typedef struct { FuriLogLevel log_level; - FuriLogPuts puts; - FuriLogTimestamp timestamp; FuriMutex* mutex; + FuriLogHandlersList_t tx_handlers; } FuriLogParams; -static FuriLogParams furi_log; +static FuriLogParams furi_log = {0}; typedef struct { const char* str; @@ -32,9 +34,77 @@ static const FuriLogLevelDescription FURI_LOG_LEVEL_DESCRIPTIONS[] = { void furi_log_init() { // Set default logging parameters furi_log.log_level = FURI_LOG_LEVEL_DEFAULT; - furi_log.puts = furi_hal_console_puts; - furi_log.timestamp = furi_get_tick; - furi_log.mutex = furi_mutex_alloc(FuriMutexTypeNormal); + furi_log.mutex = furi_mutex_alloc(FuriMutexTypeRecursive); + FuriLogHandlersList_init(furi_log.tx_handlers); +} + +bool furi_log_add_handler(FuriLogHandler handler) { + furi_check(handler.callback); + + bool ret = true; + + furi_check(furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk); + + FuriLogHandlersList_it_t it; + FuriLogHandlersList_it(it, furi_log.tx_handlers); + while(!FuriLogHandlersList_end_p(it)) { + if(memcmp(FuriLogHandlersList_ref(it), &handler, sizeof(FuriLogHandler)) == 0) { + ret = false; + } else { + FuriLogHandlersList_next(it); + } + } + + if(ret) { + FuriLogHandlersList_push_back(furi_log.tx_handlers, handler); + } + + furi_mutex_release(furi_log.mutex); + + return ret; +} + +bool furi_log_remove_handler(FuriLogHandler handler) { + bool ret = false; + + furi_check(furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk); + + FuriLogHandlersList_it_t it; + FuriLogHandlersList_it(it, furi_log.tx_handlers); + while(!FuriLogHandlersList_end_p(it)) { + if(memcmp(FuriLogHandlersList_ref(it), &handler, sizeof(FuriLogHandler)) == 0) { + FuriLogHandlersList_remove(furi_log.tx_handlers, it); + ret = true; + } else { + FuriLogHandlersList_next(it); + } + } + + furi_mutex_release(furi_log.mutex); + + return ret; +} + +void furi_log_tx(const uint8_t* data, size_t size) { + if(!FURI_IS_ISR()) { + furi_check(furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk); + } else { + if(furi_mutex_get_owner(furi_log.mutex)) return; + } + + FuriLogHandlersList_it_t it; + FuriLogHandlersList_it(it, furi_log.tx_handlers); + while(!FuriLogHandlersList_end_p(it)) { + FuriLogHandlersList_ref(it)->callback(data, size, FuriLogHandlersList_ref(it)->context); + FuriLogHandlersList_next(it); + } + + if(!FURI_IS_ISR()) furi_mutex_release(furi_log.mutex); +} + +void furi_log_puts(const char* data) { + furi_check(data); + furi_log_tx((const uint8_t*)data, strlen(data)); } void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) { @@ -72,13 +142,8 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form // Timestamp furi_string_printf( - string, - "%lu %s[%s][%s] " _FURI_LOG_CLR_RESET, - furi_log.timestamp(), - color, - log_letter, - tag); - furi_log.puts(furi_string_get_cstr(string)); + string, "%lu %s[%s][%s] " _FURI_LOG_CLR_RESET, furi_get_tick(), color, log_letter, tag); + furi_log_puts(furi_string_get_cstr(string)); furi_string_reset(string); va_list args; @@ -86,10 +151,10 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form furi_string_vprintf(string, format, args); va_end(args); - furi_log.puts(furi_string_get_cstr(string)); + furi_log_puts(furi_string_get_cstr(string)); furi_string_free(string); - furi_log.puts("\r\n"); + furi_log_puts("\r\n"); furi_mutex_release(furi_log.mutex); } @@ -105,7 +170,7 @@ void furi_log_print_raw_format(FuriLogLevel level, const char* format, ...) { furi_string_vprintf(string, format, args); va_end(args); - furi_log.puts(furi_string_get_cstr(string)); + furi_log_puts(furi_string_get_cstr(string)); furi_string_free(string); furi_mutex_release(furi_log.mutex); @@ -123,16 +188,6 @@ FuriLogLevel furi_log_get_level(void) { return furi_log.log_level; } -void furi_log_set_puts(FuriLogPuts puts) { - furi_assert(puts); - furi_log.puts = puts; -} - -void furi_log_set_timestamp(FuriLogTimestamp timestamp) { - furi_assert(timestamp); - furi_log.timestamp = timestamp; -} - bool furi_log_level_to_string(FuriLogLevel level, const char** str) { for(size_t i = 0; i < COUNT_OF(FURI_LOG_LEVEL_DESCRIPTIONS); i++) { if(level == FURI_LOG_LEVEL_DESCRIPTIONS[i].level) { @@ -151,4 +206,4 @@ bool furi_log_level_from_string(const char* str, FuriLogLevel* level) { } } return false; -} \ No newline at end of file +} diff --git a/furi/core/log.h b/furi/core/log.h index 5d11add9b91..a587d8ab275 100644 --- a/furi/core/log.h +++ b/furi/core/log.h @@ -39,11 +39,44 @@ typedef enum { #define _FURI_LOG_CLR_D _FURI_LOG_CLR(_FURI_LOG_CLR_BLUE) #define _FURI_LOG_CLR_T _FURI_LOG_CLR(_FURI_LOG_CLR_PURPLE) -typedef void (*FuriLogPuts)(const char* data); -typedef uint32_t (*FuriLogTimestamp)(void); +typedef void (*FuriLogHandlerCallback)(const uint8_t* data, size_t size, void* context); + +typedef struct { + FuriLogHandlerCallback callback; + void* context; +} FuriLogHandler; /** Initialize logging */ -void furi_log_init(); +void furi_log_init(void); + +/** Add log TX callback + * + * @param[in] callback The callback + * + * @return true on success, false otherwise + */ +bool furi_log_add_handler(FuriLogHandler handler); + +/** Remove log TX callback + * + * @param[in] callback The callback + * + * @return true on success, false otherwise + */ +bool furi_log_remove_handler(FuriLogHandler handler); + +/** Transmit data through log IO callbacks + * + * @param[in] data The data + * @param[in] size The size + */ +void furi_log_tx(const uint8_t* data, size_t size); + +/** Transmit data through log IO callbacks + * + * @param[in] data The data, null-terminated C-string + */ +void furi_log_puts(const char* data); /** Print log record * @@ -74,19 +107,7 @@ void furi_log_set_level(FuriLogLevel level); * * @return The furi log level. */ -FuriLogLevel furi_log_get_level(); - -/** Set log output callback - * - * @param[in] puts The puts callback - */ -void furi_log_set_puts(FuriLogPuts puts); - -/** Set timestamp callback - * - * @param[in] timestamp The timestamp callback - */ -void furi_log_set_timestamp(FuriLogTimestamp timestamp); +FuriLogLevel furi_log_get_level(void); /** Log level to string * diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index a3e127c3c1b..c0ab46ebcd6 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include /* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining @@ -52,6 +52,10 @@ task.h is included from an application file. */ #undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE +#ifdef HEAP_PRINT_DEBUG +#error This feature is broken, logging transport must be replaced with RTT +#endif + #if(configSUPPORT_DYNAMIC_ALLOCATION == 0) #error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 #endif @@ -286,13 +290,13 @@ static void print_heap_init() { // {PHStart|heap_start|heap_end} FURI_CRITICAL_ENTER(); - furi_hal_console_puts("{PHStart|"); + furi_log_puts("{PHStart|"); ultoa(heap_start, tmp_str, 16); - furi_hal_console_puts(tmp_str); - furi_hal_console_puts("|"); + furi_log_puts(tmp_str); + furi_log_puts("|"); ultoa(heap_end, tmp_str, 16); - furi_hal_console_puts(tmp_str); - furi_hal_console_puts("}\r\n"); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); FURI_CRITICAL_EXIT(); } @@ -305,15 +309,15 @@ static void print_heap_malloc(void* ptr, size_t size) { // {thread name|m|address|size} FURI_CRITICAL_ENTER(); - furi_hal_console_puts("{"); - furi_hal_console_puts(name); - furi_hal_console_puts("|m|0x"); + furi_log_puts("{"); + furi_log_puts(name); + furi_log_puts("|m|0x"); ultoa((unsigned long)ptr, tmp_str, 16); - furi_hal_console_puts(tmp_str); - furi_hal_console_puts("|"); + furi_log_puts(tmp_str); + furi_log_puts("|"); utoa(size, tmp_str, 10); - furi_hal_console_puts(tmp_str); - furi_hal_console_puts("}\r\n"); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); FURI_CRITICAL_EXIT(); } @@ -326,12 +330,12 @@ static void print_heap_free(void* ptr) { // {thread name|f|address} FURI_CRITICAL_ENTER(); - furi_hal_console_puts("{"); - furi_hal_console_puts(name); - furi_hal_console_puts("|f|0x"); + furi_log_puts("{"); + furi_log_puts(name); + furi_log_puts("|f|0x"); ultoa((unsigned long)ptr, tmp_str, 16); - furi_hal_console_puts(tmp_str); - furi_hal_console_puts("}\r\n"); + furi_log_puts(tmp_str); + furi_log_puts("}\r\n"); FURI_CRITICAL_EXIT(); } #endif diff --git a/furi/core/mutex.c b/furi/core/mutex.c index 8794e10dc39..f18fb1681df 100644 --- a/furi/core/mutex.c +++ b/furi/core/mutex.c @@ -114,8 +114,10 @@ FuriThreadId furi_mutex_get_owner(FuriMutex* instance) { hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); - if((FURI_IS_IRQ_MODE()) || (hMutex == NULL)) { + if((hMutex == NULL)) { owner = 0; + } else if(FURI_IS_IRQ_MODE()) { + owner = (FuriThreadId)xSemaphoreGetMutexHolderFromISR(hMutex); } else { owner = (FuriThreadId)xSemaphoreGetMutexHolder(hMutex); } diff --git a/furi/core/stream_buffer.h b/furi/core/stream_buffer.h index d07f7e60ba3..5ddc4941630 100644 --- a/furi/core/stream_buffer.h +++ b/furi/core/stream_buffer.h @@ -149,4 +149,4 @@ FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/furi/core/thread.c b/furi/core/thread.c index db4feeb4e16..abc85bb90d7 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -9,7 +9,6 @@ #include "log.h" #include -#include #include #include @@ -570,7 +569,7 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s if(thread->output.write_callback != NULL) { thread->output.write_callback(data, size); } else { - furi_hal_console_tx((const uint8_t*)data, size); + furi_log_tx((const uint8_t*)data, size); } return size; } diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index 85d915acdcc..b71d78ff0a9 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -78,6 +78,20 @@ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SNOP); } +bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us) { + bool result = false; + CC1101Status status = {0}; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_us); + while(!furi_hal_cortex_timer_is_expired(timer)) { + status = cc1101_strobe(handle, CC1101_STROBE_SNOP); + if(status.STATE == state) { + result = true; + break; + } + } + return result; +} + CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SPWD); } diff --git a/lib/drivers/cc1101.h b/lib/drivers/cc1101.h index d8ee05d5289..c8c552becee 100644 --- a/lib/drivers/cc1101.h +++ b/lib/drivers/cc1101.h @@ -59,6 +59,16 @@ CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); */ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); +/** Wait specific chip state + * + * @param handle The SPI bus handle + * @param[in] state The state to wait + * @param[in] timeout_us The timeout in microseconds + * + * @return true on success, false otherwise + */ +bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us); + /** Enable shutdown mode * * @param handle - pointer to FuriHalSpiHandle diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index 04386a67528..a3ab56f25b2 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -101,7 +101,7 @@ static bool protocol_fdx_b_can_be_decoded(ProtocolFDXB* protocol) { void protocol_fdx_b_decode(ProtocolFDXB* protocol) { // remove parity - bit_lib_remove_bit_every_nth(protocol->encoded_data, 3, 13 * 9, 9); + bit_lib_remove_bit_every_nth(protocol->encoded_data, 3, 14 * 9, 9); // remove header pattern for(size_t i = 0; i < 11; i++) @@ -119,7 +119,7 @@ void protocol_fdx_b_decode(ProtocolFDXB* protocol) { // 72 xxxxxxxx // 80 eeeeeeee 24 bits of extra data if present. // 88 eeeeeeee eg. $123456. - // 92 eeeeeeee + // 96 eeeeeeee // copy data without checksum bit_lib_copy_bits(protocol->data, 0, 64, protocol->encoded_data, 0); diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 6475cce435a..22a21c9d2fe 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -150,7 +150,7 @@ static int32_t nfc_worker_listener(void* context) { } else if(command == NfcCommandReset) { furi_hal_nfc_listener_enable_rx(); } else if(command == NfcCommandSleep) { - furi_hal_nfc_listener_sleep(); + furi_hal_nfc_listener_idle(); } } } diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index 45fd9a9a1fc..b0e6778a0f5 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -18,8 +18,8 @@ typedef struct FelicaPoller FelicaPoller; * @brief Enumeration of possible Felica poller event types. */ typedef enum { - FelicaPollerEventTypeError, /**< The card was activated by the poller. */ - FelicaPollerEventTypeReady, /**< An error occured during activation procedure. */ + FelicaPollerEventTypeError, /**< An error occured during activation procedure. */ + FelicaPollerEventTypeReady, /**< The card was activated by the poller. */ } FelicaPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h index 42e4b4bf524..c6649152f61 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h @@ -18,8 +18,8 @@ typedef struct Iso14443_3aPoller Iso14443_3aPoller; * @brief Enumeration of possible Iso14443_3a poller event types. */ typedef enum { - Iso14443_3aPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_3aPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_3aPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_3aPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_3aPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h index 940903c1dcc..cdf8ddf6875 100644 --- a/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h +++ b/lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h @@ -18,8 +18,8 @@ typedef struct Iso14443_3bPoller Iso14443_3bPoller; * @brief Enumeration of possible Iso14443_3b poller event types. */ typedef enum { - Iso14443_3bPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_3bPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_3bPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_3bPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_3bPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a.c index 9c2a530d53c..bfa2e71c643 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.c @@ -252,7 +252,12 @@ const uint8_t* iso14443_4a_get_historical_bytes(const Iso14443_4aData* data, uin furi_assert(count); *count = simple_array_get_count(data->ats_data.t1_tk); - return simple_array_cget_data(data->ats_data.t1_tk); + const uint8_t* hist_bytes = NULL; + if(*count > 0) { + hist_bytes = simple_array_cget_data(data->ats_data.t1_tk); + } + + return hist_bytes; } bool iso14443_4a_supports_bit_rate(const Iso14443_4aData* data, Iso14443_4aBitRate bit_rate) { diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index 73eb6ef74da..fef565e5149 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -17,8 +17,8 @@ typedef struct Iso14443_4aPoller Iso14443_4aPoller; * @brief Enumeration of possible Iso14443_4a poller event types. */ typedef enum { - Iso14443_4aPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_4aPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_4aPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_4aPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_4aPollerEventType; /** diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h index 03b288c079c..faccf2f3c18 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h @@ -17,8 +17,8 @@ typedef struct Iso14443_4bPoller Iso14443_4bPoller; * @brief Enumeration of possible Iso14443_4b poller event types. */ typedef enum { - Iso14443_4bPollerEventTypeError, /**< The card was activated by the poller. */ - Iso14443_4bPollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso14443_4bPollerEventTypeError, /**< An error occured during activation procedure. */ + Iso14443_4bPollerEventTypeReady, /**< The card was activated by the poller. */ } Iso14443_4bPollerEventType; /** diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h index a187ceace14..efe8337afac 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -17,8 +17,8 @@ typedef struct Iso15693_3Poller Iso15693_3Poller; * @brief Enumeration of possible Iso15693_3 poller event types. */ typedef enum { - Iso15693_3PollerEventTypeError, /**< The card was activated by the poller. */ - Iso15693_3PollerEventTypeReady, /**< An error occured during activation procedure. */ + Iso15693_3PollerEventTypeError, /**< An error occured during activation procedure. */ + Iso15693_3PollerEventTypeReady, /**< The card was activated by the poller. */ } Iso15693_3PollerEventType; /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index bd25aba23e8..9f6f1f85c5a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -33,6 +33,7 @@ static void mf_classic_listener_reset_state(MfClassicListener* instance) { instance->state = MfClassicListenerStateIdle; instance->cmd_in_progress = false; instance->current_cmd_handler_idx = 0; + instance->write_block = 0; instance->transfer_value = 0; instance->transfer_valid = false; instance->value_cmd = MfClassicValueCommandInvalid; @@ -240,11 +241,13 @@ static MfClassicListenerCommand mf_classic_listener_write_block_first_part_handl uint8_t block_num = bit_buffer_get_byte(buff, 1); if(block_num >= instance->total_block_num) break; + if(block_num == 0) break; uint8_t sector_num = mf_classic_get_sector_by_block(block_num); uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num); if(sector_num != auth_sector_num) break; + instance->write_block = block_num; instance->cmd_in_progress = true; instance->current_cmd_handler_idx++; command = MfClassicListenerCommandAck; @@ -265,7 +268,7 @@ static MfClassicListenerCommand mf_classic_listener_write_block_second_part_hand size_t buff_size = bit_buffer_get_size_bytes(buff); if(buff_size != sizeof(MfClassicBlock)) break; - uint8_t block_num = auth_ctx->block_num; + uint8_t block_num = instance->write_block; MfClassicKeyType key_type = auth_ctx->key_type; MfClassicBlock block = instance->data->block[block_num]; @@ -609,6 +612,7 @@ NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) { mf_classic_listener_send_short_frame(instance, nack); mf_classic_listener_reset_state(instance); + command = NfcCommandSleep; } else if(mfc_command == MfClassicListenerCommandSilent) { command = NfcCommandReset; } else if(mfc_command == MfClassicListenerCommandSleep) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 52273be9c22..5269743b5c4 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -40,6 +40,9 @@ struct MfClassicListener { Crypto1* crypto; MfClassicAuthContext auth_context; + // Write block context + uint8_t write_block; + // Value operation data int32_t transfer_value; bool transfer_valid; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index dbc32a1b51c..d846bba69bc 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -22,6 +22,7 @@ MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); instance->rx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE); instance->current_type_check = MfClassicType4k; + instance->card_state = MfClassicCardStateLost; instance->mfc_event.data = &instance->mfc_event_data; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 8e65eca5a54..646803e75b6 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -179,44 +179,53 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer const size_t data_size = bit_buffer_get_size_bytes(buf); const size_t min_data_size = sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); + const size_t max_data_size = + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue); if(data_size < min_data_size) break; - - MfDesfireFileSettingsLayout layout; - bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); - - data->type = layout.header.type; - data->comm = layout.header.comm; - data->access_rights = layout.header.access_rights; - - if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { - if(data_size != min_data_size) break; - - data->data.size = layout.data.size; - - } else if(data->type == MfDesfireFileTypeValue) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) - break; - - data->value.lo_limit = layout.value.lo_limit; - data->value.hi_limit = layout.value.hi_limit; - data->value.limited_credit_value = layout.value.limited_credit_value; - data->value.limited_credit_enabled = layout.value.limited_credit_enabled; - - } else if( - data->type == MfDesfireFileTypeLinearRecord || - data->type == MfDesfireFileTypeCyclicRecord) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) + if(data_size <= max_data_size) { + MfDesfireFileSettingsLayout layout; + bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); + + data->type = layout.header.type; + data->comm = layout.header.comm; + data->access_rights = layout.header.access_rights; + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + if(data_size != min_data_size) break; + + data->data.size = layout.data.size; + } else if(data->type == MfDesfireFileTypeValue) { + if(data_size != + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) + break; + + data->value.lo_limit = layout.value.lo_limit; + data->value.hi_limit = layout.value.hi_limit; + data->value.limited_credit_value = layout.value.limited_credit_value; + data->value.limited_credit_enabled = layout.value.limited_credit_enabled; + + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + if(data_size != + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) + break; + + data->record.size = layout.record.size; + data->record.max = layout.record.max; + data->record.cur = layout.record.cur; + + } else { break; - - data->record.size = layout.record.size; - data->record.max = layout.record.max; - data->record.cur = layout.record.cur; - + } } else { - break; + // TODO FL-3750: process HID Desfire command response here + // Set default fields for now + data->type = 0; + data->comm = 0; + data->access_rights = 0; + data->data.size = 0; } parsed = true; @@ -478,19 +487,25 @@ bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, do { if(!mf_desfire_key_settings_load(&data->key_settings, prefix, ff)) break; + uint32_t i; const uint32_t key_version_count = data->key_settings.max_keys; - simple_array_init(data->key_versions, key_version_count); + if(key_version_count) { + simple_array_init(data->key_versions, key_version_count); - uint32_t i; - for(i = 0; i < key_version_count; ++i) { - if(!mf_desfire_key_version_load(simple_array_get(data->key_versions, i), prefix, i, ff)) - break; - } + for(i = 0; i < key_version_count; ++i) { + if(!mf_desfire_key_version_load( + simple_array_get(data->key_versions, i), prefix, i, ff)) + break; + } - if(i != key_version_count) break; + if(i != key_version_count) break; + } uint32_t file_count; - if(!mf_desfire_file_count_load(&file_count, prefix, ff)) break; + if(!mf_desfire_file_count_load(&file_count, prefix, ff)) { + success = true; + break; + } simple_array_init(data->file_ids, file_count); if(!mf_desfire_file_ids_load(simple_array_get_data(data->file_ids), file_count, prefix, ff)) diff --git a/lib/signal_reader/signal_reader.c b/lib/signal_reader/signal_reader.c index 7c4d0bae7ed..1c08d29f455 100644 --- a/lib/signal_reader/signal_reader.c +++ b/lib/signal_reader/signal_reader.c @@ -278,7 +278,10 @@ void signal_reader_start(SignalReader* instance, SignalReaderCallback callback, // Start DMA irq, higher priority than normal furi_hal_interrupt_set_isr_ex( - SIGNAL_READER_DMA_GPIO_IRQ, 14, furi_hal_sw_digital_pin_dma_rx_isr, instance); + SIGNAL_READER_DMA_GPIO_IRQ, + FuriHalInterruptPriorityHighest, + furi_hal_sw_digital_pin_dma_rx_isr, + instance); // Start DMA Sync timer LL_DMA_EnableChannel(SIGNAL_READER_DMA_CNT_SYNC_DEF); diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 3c045c26966..239e02985fa 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -18,7 +18,6 @@ struct SubGhzFileEncoderWorker { volatile bool worker_running; volatile bool worker_stoping; - bool level; bool is_storage_slow; FuriString* str_data; FuriString* file_path; @@ -41,19 +40,8 @@ void subghz_file_encoder_worker_callback_end( void subghz_file_encoder_worker_add_level_duration( SubGhzFileEncoderWorker* instance, int32_t duration) { - bool res = true; - if(duration < 0 && !instance->level) { - res = false; - } else if(duration > 0 && instance->level) { - res = false; - } - - if(res) { - instance->level = !instance->level; - furi_stream_buffer_send(instance->stream, &duration, sizeof(int32_t), 100); - } else { - FURI_LOG_E(TAG, "Invalid level in the stream"); - } + size_t ret = furi_stream_buffer_send(instance->stream, &duration, sizeof(int32_t), 100); + if(sizeof(int32_t) != ret) FURI_LOG_E(TAG, "Invalid add duration in the stream"); } bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, const char* strStart) { @@ -190,7 +178,6 @@ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { instance->str_data = furi_string_alloc(); instance->file_path = furi_string_alloc(); - instance->level = false; instance->worker_stoping = true; return instance; diff --git a/lib/toolbox/value_index.c b/lib/toolbox/value_index.c index 5ec0fb96287..c17b0ae7949 100644 --- a/lib/toolbox/value_index.c +++ b/lib/toolbox/value_index.c @@ -1,52 +1,55 @@ #include "value_index.h" +#include -uint8_t value_index_int32(const int32_t value, const int32_t values[], uint8_t values_count) { - int64_t last_value = INT64_MIN; - uint8_t index = 0; - for(uint8_t i = 0; i < values_count; i++) { - if((value >= last_value) && (value <= values[i])) { +size_t value_index_int32(const int32_t value, const int32_t values[], size_t values_count) { + size_t index = 0; + + for(size_t i = 0; i < values_count; i++) { + if(value == values[i]) { index = i; break; } - last_value = values[i]; } + return index; } -uint8_t value_index_uint32(const uint32_t value, const uint32_t values[], uint8_t values_count) { - int64_t last_value = INT64_MIN; - uint8_t index = 0; - for(uint8_t i = 0; i < values_count; i++) { - if((value >= last_value) && (value <= values[i])) { +size_t value_index_uint32(const uint32_t value, const uint32_t values[], size_t values_count) { + size_t index = 0; + + for(size_t i = 0; i < values_count; i++) { + if(value == values[i]) { index = i; break; } - last_value = values[i]; } + return index; } -uint8_t value_index_float(const float value, const float values[], uint8_t values_count) { - const float epsilon = 0.01f; - float last_value = values[0]; - uint8_t index = 0; - for(uint8_t i = 0; i < values_count; i++) { - if((value >= last_value - epsilon) && (value <= values[i] + epsilon)) { +size_t value_index_float(const float value, const float values[], size_t values_count) { + size_t index = 0; + + for(size_t i = 0; i < values_count; i++) { + const float epsilon = fabsf(values[i] * 0.01f); + if(fabsf(values[i] - value) <= epsilon) { index = i; break; } - last_value = values[i]; } + return index; } -uint8_t value_index_bool(const bool value, const bool values[], uint8_t values_count) { - uint8_t index = 0; - for(uint8_t i = 0; i < values_count; i++) { +size_t value_index_bool(const bool value, const bool values[], size_t values_count) { + size_t index = 0; + + for(size_t i = 0; i < values_count; i++) { if(value == values[i]) { index = i; break; } } + return index; } diff --git a/lib/toolbox/value_index.h b/lib/toolbox/value_index.h index 5aa768e3d1d..bcd3024acd8 100644 --- a/lib/toolbox/value_index.h +++ b/lib/toolbox/value_index.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -18,7 +19,7 @@ extern "C" { * * @return value's index. */ -uint8_t value_index_int32(const int32_t value, const int32_t values[], uint8_t values_count); +size_t value_index_int32(const int32_t value, const int32_t values[], size_t values_count); /** Get the index of a uint32_t array element which is closest to the given value. * @@ -31,7 +32,7 @@ uint8_t value_index_int32(const int32_t value, const int32_t values[], uint8_t v * * @return value's index. */ -uint8_t value_index_uint32(const uint32_t value, const uint32_t values[], uint8_t values_count); +size_t value_index_uint32(const uint32_t value, const uint32_t values[], size_t values_count); /** Get the index of a float array element which is closest to the given value. * @@ -44,7 +45,7 @@ uint8_t value_index_uint32(const uint32_t value, const uint32_t values[], uint8_ * * @return value's index. */ -uint8_t value_index_float(const float value, const float values[], uint8_t values_count); +size_t value_index_float(const float value, const float values[], size_t values_count); /** Get the index of a bool array element which is equal to the given value. * @@ -57,7 +58,7 @@ uint8_t value_index_float(const float value, const float values[], uint8_t value * * @return value's index. */ -uint8_t value_index_bool(const bool value, const bool values[], uint8_t values_count); +size_t value_index_bool(const bool value, const bool values[], size_t values_count); #ifdef __cplusplus } diff --git a/scripts/assets.py b/scripts/assets.py index 1099f0c330d..711c1b440b2 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -24,6 +24,9 @@ ICONS_TEMPLATE_C_DATA = "const uint8_t* const {name}[] = {data};\n" ICONS_TEMPLATE_C_ICONS = "const Icon {name} = {{.width={width},.height={height},.frame_count={frame_count},.frame_rate={frame_rate},.frames=_{name}}};\n" +MAX_IMAGE_WIDTH = 128 +MAX_IMAGE_HEIGHT = 64 + class Main(App): def init(self): @@ -102,6 +105,10 @@ def init(self): def _icon2header(self, file): image = file2image(file) + if image.width > MAX_IMAGE_WIDTH or image.height > MAX_IMAGE_HEIGHT: + raise Exception( + f"Image {file} is too big ({image.width}x{image.height} vs. {MAX_IMAGE_WIDTH}x{MAX_IMAGE_HEIGHT})" + ) return image.width, image.height, image.data_as_carray() def _iconIsSupported(self, filename): diff --git a/scripts/storage.py b/scripts/storage.py index e04eaa7e1f0..9df2f6c391b 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -74,7 +74,7 @@ def init(self): self.parser_list.set_defaults(func=self.list) self.parser_stress = self.subparsers.add_parser("stress", help="Stress test") - self.parser.add_argument( + self.parser_stress.add_argument( "-c", "--count", type=int, default=10, help="Iteration count" ) self.parser_stress.add_argument("flipper_path", help="Flipper path") diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 2fc170ad950..8df1ae110ab 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -275,15 +275,16 @@ Default(install_and_check) # Compilation database -fwcdb = appenv.CompilationDatabase( +app_cdb = appenv.CompilationDatabase( original_app_dir.Dir(".vscode").File("compile_commands.json") ) -AlwaysBuild(fwcdb) -Precious(fwcdb) -NoClean(fwcdb) +AlwaysBuild(app_cdb) +Precious(app_cdb) +NoClean(app_cdb) if len(apps_artifacts): - Default(fwcdb) + Default(app_cdb) +Alias("cdb", app_cdb) # launch handler @@ -365,7 +366,7 @@ for template_file in project_template_dir.Dir(".vscode").glob("*"): "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")), "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath), "@UFBT_ROOT_DIR@": _path_as_posix(Dir("#").abspath), - "@UFBT_DEBUG_DIR@": dist_env["FBT_DEBUG_DIR"], + "@UFBT_DEBUG_DIR@": _path_as_posix(dist_env["FBT_DEBUG_DIR"].abspath), "@UFBT_DEBUG_ELF_DIR@": _path_as_posix( dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath ), @@ -381,7 +382,7 @@ for config_file in project_template_dir.glob(".*"): dist_env.Precious(vscode_dist) dist_env.NoClean(vscode_dist) -dist_env.Alias("vscode_dist", vscode_dist) +dist_env.Alias("vscode_dist", (vscode_dist, app_cdb)) # Creating app from base template diff --git a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json index 922a9091b60..f957ee98bb9 100644 --- a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json +++ b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json @@ -8,7 +8,7 @@ "configurationProvider": "ms-vscode.cpptools", "cStandard": "gnu17", "cppStandard": "c++17" - }, + } ], "version": 4 } \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/extensions.json b/scripts/ufbt/project_template/.vscode/extensions.json index ead935b08b4..9daefb43088 100644 --- a/scripts/ufbt/project_template/.vscode/extensions.json +++ b/scripts/ufbt/project_template/.vscode/extensions.json @@ -1,3 +1,7 @@ +// This file is autogeneated by the ufbt. +// You can modify it, and it will not be overwritten if exists. +// Some paths are absolute, and will need to be updated if you move the project. +// To regenerate the file, delete it and run `ufbt vscode_dist` again. { // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp diff --git a/scripts/ufbt/project_template/.vscode/launch.json b/scripts/ufbt/project_template/.vscode/launch.json index 3269bab5754..a5639743f92 100644 --- a/scripts/ufbt/project_template/.vscode/launch.json +++ b/scripts/ufbt/project_template/.vscode/launch.json @@ -1,3 +1,7 @@ +// This file is autogeneated by the ufbt. +// You can modify it, and it will not be overwritten if exists. +// Some paths are absolute, and will need to be updated if you move the project. +// To regenerate the file, delete it and run `ufbt vscode_dist` again. { // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", diff --git a/scripts/ufbt/project_template/.vscode/settings.json b/scripts/ufbt/project_template/.vscode/settings.json index 33cd3f035e1..ce5210f5fe4 100644 --- a/scripts/ufbt/project_template/.vscode/settings.json +++ b/scripts/ufbt/project_template/.vscode/settings.json @@ -1,3 +1,7 @@ +// This file is autogeneated by the ufbt. +// You can modify it, and it will not be overwritten if exists. +// Some paths are absolute, and will need to be updated if you move the project. +// To regenerate the file, delete it and run `ufbt vscode_dist` again. { "cortex-debug.enableTelemetry": false, "cortex-debug.variableUseNaturalFormat": false, diff --git a/scripts/ufbt/project_template/.vscode/tasks.json b/scripts/ufbt/project_template/.vscode/tasks.json index 4b3f4bda563..65c749e07b9 100644 --- a/scripts/ufbt/project_template/.vscode/tasks.json +++ b/scripts/ufbt/project_template/.vscode/tasks.json @@ -1,3 +1,7 @@ +// This file is autogeneated by the ufbt. +// You can modify it, and it will not be overwritten if exists. +// Some paths are absolute, and will need to be updated if you move the project. +// To regenerate the file, delete it and run `ufbt vscode_dist` again. { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py index ab20e2f7de9..4873b385c65 100644 --- a/scripts/ufbt/site_tools/ufbt_help.py +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -18,6 +18,8 @@ Build all FAP apps fap_{APPID}, launch APPSRC={APPID}: Build FAP app with appid={APPID}; upload & start it over USB + cdb: + regenerate "compile_commands.json" file (for IDE integration) Flashing & debugging: flash, *jflash: diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 8a5e44c97ca..ffb664a3e06 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,10 +1,11 @@ entry,status,name,type,params -Version,+,50.0,, +Version,+,54.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, +Header,+,applications/services/expansion/expansion.h,, Header,+,applications/services/gui/elements.h,, Header,+,applications/services/gui/gui.h,, Header,+,applications/services/gui/icon_i.h,, @@ -160,7 +161,6 @@ Header,+,targets/f18/furi_hal/furi_hal_spi_config.h,, Header,+,targets/f18/furi_hal/furi_hal_target_hw.h,, Header,+,targets/f7/furi_hal/furi_hal_bus.h,, Header,+,targets/f7/furi_hal/furi_hal_clock.h,, -Header,+,targets/f7/furi_hal/furi_hal_console.h,, Header,+,targets/f7/furi_hal/furi_hal_dma.h,, Header,+,targets/f7/furi_hal/furi_hal_flash.h,, Header,+,targets/f7/furi_hal/furi_hal_gpio.h,, @@ -170,8 +170,11 @@ Header,+,targets/f7/furi_hal/furi_hal_idle_timer.h,, Header,+,targets/f7/furi_hal/furi_hal_interrupt.h,, Header,+,targets/f7/furi_hal/furi_hal_os.h,, Header,+,targets/f7/furi_hal/furi_hal_pwm.h,, +Header,+,targets/f7/furi_hal/furi_hal_rtc.h,, +Header,+,targets/f7/furi_hal/furi_hal_serial.h,, +Header,+,targets/f7/furi_hal/furi_hal_serial_control.h,, +Header,+,targets/f7/furi_hal/furi_hal_serial_types.h,, Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, -Header,+,targets/f7/furi_hal/furi_hal_uart.h,, Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, @@ -190,7 +193,6 @@ Header,+,targets/furi_hal_include/furi_hal_mpu.h,, Header,+,targets/furi_hal_include/furi_hal_power.h,, Header,+,targets/furi_hal_include/furi_hal_random.h,, Header,+,targets/furi_hal_include/furi_hal_region.h,, -Header,+,targets/furi_hal_include/furi_hal_rtc.h,, Header,+,targets/furi_hal_include/furi_hal_sd.h,, Header,+,targets/furi_hal_include/furi_hal_speaker.h,, Header,+,targets/furi_hal_include/furi_hal_spi.h,, @@ -787,6 +789,9 @@ Function,-,exp10f,float,float Function,-,exp2,double,double Function,-,exp2f,float,float Function,-,exp2l,long double,long double +Function,+,expansion_disable,void,Expansion* +Function,+,expansion_enable,void,Expansion* +Function,+,expansion_set_listen_serial,void,"Expansion*, FuriHalSerialId" Function,-,expf,float,float Function,-,expl,long double,long double Function,-,explicit_bzero,void,"void*, size_t" @@ -1057,14 +1062,6 @@ Function,-,furi_hal_clock_switch_hse2hsi,void, Function,-,furi_hal_clock_switch_hse2pll,_Bool, Function,-,furi_hal_clock_switch_hsi2hse,void, Function,-,furi_hal_clock_switch_pll2hse,_Bool, -Function,+,furi_hal_console_disable,void, -Function,+,furi_hal_console_enable,void, -Function,+,furi_hal_console_init,void, -Function,+,furi_hal_console_printf,void,"const char[], ..." -Function,+,furi_hal_console_puts,void,const char* -Function,+,furi_hal_console_set_tx_callback,void,"FuriHalConsoleTxCallback, void*" -Function,+,furi_hal_console_tx,void,"const uint8_t*, size_t" -Function,+,furi_hal_console_tx_with_new_line,void,"const uint8_t*, size_t" Function,+,furi_hal_cortex_comp_enable,void,"FuriHalCortexComp, FuriHalCortexCompFunction, uint32_t, uint32_t, FuriHalCortexCompSize" Function,+,furi_hal_cortex_comp_reset,void,FuriHalCortexComp Function,+,furi_hal_cortex_delay_us,void,uint32_t @@ -1156,7 +1153,7 @@ Function,-,furi_hal_init,void, Function,-,furi_hal_init_early,void, Function,-,furi_hal_interrupt_init,void, Function,+,furi_hal_interrupt_set_isr,void,"FuriHalInterruptId, FuriHalInterruptISR, void*" -Function,+,furi_hal_interrupt_set_isr_ex,void,"FuriHalInterruptId, uint16_t, FuriHalInterruptISR, void*" +Function,+,furi_hal_interrupt_set_isr_ex,void,"FuriHalInterruptId, FuriHalInterruptPriority, FuriHalInterruptISR, void*" Function,+,furi_hal_light_blink_set_color,void,Light Function,+,furi_hal_light_blink_start,void,"Light, uint8_t, uint16_t, uint16_t" Function,+,furi_hal_light_blink_stop,void, @@ -1239,6 +1236,8 @@ Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, Function,+,furi_hal_rtc_get_locale_timeformat,FuriHalRtcLocaleTimeFormat, Function,+,furi_hal_rtc_get_locale_units,FuriHalRtcLocaleUnits, +Function,+,furi_hal_rtc_get_log_baud_rate,FuriHalRtcLogBaudRate, +Function,+,furi_hal_rtc_get_log_device,FuriHalRtcLogDevice, Function,+,furi_hal_rtc_get_log_level,uint8_t, Function,+,furi_hal_rtc_get_pin_fails,uint32_t, Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister @@ -1257,10 +1256,13 @@ Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode Function,+,furi_hal_rtc_set_locale_dateformat,void,FuriHalRtcLocaleDateFormat Function,+,furi_hal_rtc_set_locale_timeformat,void,FuriHalRtcLocaleTimeFormat Function,+,furi_hal_rtc_set_locale_units,void,FuriHalRtcLocaleUnits +Function,+,furi_hal_rtc_set_log_baud_rate,void,FuriHalRtcLogBaudRate +Function,+,furi_hal_rtc_set_log_device,void,FuriHalRtcLogDevice Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_sync_shadow,void, +Function,+,furi_hal_rtc_timestamp_to_datetime,void,"uint32_t, FuriHalRtcDateTime*" Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* Function,+,furi_hal_sd_get_card_state,FuriStatus, Function,+,furi_hal_sd_info,FuriStatus,FuriHalSdInfo* @@ -1270,6 +1272,32 @@ Function,+,furi_hal_sd_max_mount_retry_count,uint8_t, Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" +Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" +Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId +Function,+,furi_hal_serial_control_deinit,void, +Function,+,furi_hal_serial_control_init,void, +Function,+,furi_hal_serial_control_is_busy,_Bool,FuriHalSerialId +Function,+,furi_hal_serial_control_release,void,FuriHalSerialHandle* +Function,-,furi_hal_serial_control_resume,void, +Function,+,furi_hal_serial_control_set_expansion_callback,void,"FuriHalSerialId, FuriHalSerialControlExpansionCallback, void*" +Function,+,furi_hal_serial_control_set_logging_config,void,"FuriHalSerialId, uint32_t" +Function,-,furi_hal_serial_control_suspend,void, +Function,+,furi_hal_serial_deinit,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_disable_direction,void,"FuriHalSerialHandle*, FuriHalSerialDirection" +Function,+,furi_hal_serial_dma_rx,size_t,"FuriHalSerialHandle*, uint8_t*, size_t" +Function,+,furi_hal_serial_dma_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialDmaRxCallback, void*, _Bool" +Function,+,furi_hal_serial_dma_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_enable_direction,void,"FuriHalSerialHandle*, FuriHalSerialDirection" +Function,+,furi_hal_serial_get_gpio_pin,const GpioPin*,"FuriHalSerialHandle*, FuriHalSerialDirection" +Function,+,furi_hal_serial_init,void,"FuriHalSerialHandle*, uint32_t" +Function,+,furi_hal_serial_is_baud_rate_supported,_Bool,"FuriHalSerialHandle*, uint32_t" +Function,+,furi_hal_serial_resume,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_set_br,void,"FuriHalSerialHandle*, uint32_t" +Function,+,furi_hal_serial_suspend,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_tx,void,"FuriHalSerialHandle*, const uint8_t*, size_t" +Function,+,furi_hal_serial_tx_wait_complete,void,FuriHalSerialHandle* Function,+,furi_hal_speaker_acquire,_Bool,uint32_t Function,-,furi_hal_speaker_deinit,void, Function,-,furi_hal_speaker_init,void, @@ -1293,13 +1321,6 @@ Function,-,furi_hal_spi_config_init_early,void, Function,-,furi_hal_spi_dma_init,void, Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* Function,+,furi_hal_switch,void,void* -Function,+,furi_hal_uart_deinit,void,FuriHalUartId -Function,+,furi_hal_uart_init,void,"FuriHalUartId, uint32_t" -Function,+,furi_hal_uart_resume,void,FuriHalUartId -Function,+,furi_hal_uart_set_br,void,"FuriHalUartId, uint32_t" -Function,+,furi_hal_uart_set_irq_cb,void,"FuriHalUartId, void (*)(UartIrqEvent, uint8_t, void*), void*" -Function,+,furi_hal_uart_suspend,void,FuriHalUartId -Function,+,furi_hal_uart_tx,void,"FuriHalUartId, uint8_t*, size_t" Function,+,furi_hal_usb_disable,void, Function,+,furi_hal_usb_enable,void, Function,+,furi_hal_usb_get_config,FuriHalUsbInterface*, @@ -1330,7 +1351,9 @@ Function,+,furi_hal_version_get_mic_id,const char*, Function,+,furi_hal_version_get_model_code,const char*, Function,+,furi_hal_version_get_model_name,const char*, Function,+,furi_hal_version_get_name_ptr,const char*, +Function,+,furi_hal_version_get_ncc_id,const char*, Function,+,furi_hal_version_get_otp_version,FuriHalVersionOtpVersion, +Function,+,furi_hal_version_get_srrc_id,const char*, Function,-,furi_hal_version_init,void, Function,+,furi_hal_version_uid,const uint8_t*, Function,+,furi_hal_version_uid_size,size_t, @@ -1343,15 +1366,17 @@ Function,+,furi_kernel_is_running,_Bool, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, +Function,+,furi_log_add_handler,_Bool,FuriLogHandler Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, Function,+,furi_log_level_from_string,_Bool,"const char*, FuriLogLevel*" Function,+,furi_log_level_to_string,_Bool,"FuriLogLevel, const char**" Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." +Function,+,furi_log_puts,void,const char* +Function,+,furi_log_remove_handler,_Bool,FuriLogHandler Function,+,furi_log_set_level,void,FuriLogLevel -Function,-,furi_log_set_puts,void,FuriLogPuts -Function,-,furi_log_set_timestamp,void,FuriLogTimestamp +Function,+,furi_log_tx,void,"const uint8_t*, size_t" Function,+,furi_message_queue_alloc,FuriMessageQueue*,"uint32_t, uint32_t" Function,+,furi_message_queue_free,void,FuriMessageQueue* Function,+,furi_message_queue_get,FuriStatus,"FuriMessageQueue*, void*, uint32_t" @@ -2083,7 +2108,7 @@ Function,-,round,double,double Function,+,roundf,float,float Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* -Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, uint32_t" +Function,+,rpc_session_feed,size_t,"RpcSession*, const uint8_t*, size_t, uint32_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* Function,+,rpc_session_get_owner,RpcOwner,RpcSession* Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" @@ -2415,10 +2440,10 @@ Function,-,utoa,char*,"unsigned, char*, int" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* -Function,+,value_index_bool,uint8_t,"const _Bool, const _Bool[], uint8_t" -Function,+,value_index_float,uint8_t,"const float, const float[], uint8_t" -Function,+,value_index_int32,uint8_t,"const int32_t, const int32_t[], uint8_t" -Function,+,value_index_uint32,uint8_t,"const uint32_t, const uint32_t[], uint8_t" +Function,+,value_index_bool,size_t,"const _Bool, const _Bool[], size_t" +Function,+,value_index_float,size_t,"const float, const float[], size_t" +Function,+,value_index_int32,size_t,"const int32_t, const int32_t[], size_t" +Function,+,value_index_uint32,size_t,"const uint32_t, const uint32_t[], size_t" Function,+,variable_item_get_context,void*,VariableItem* Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* Function,+,variable_item_list_add,VariableItem*,"VariableItemList*, const char*, uint8_t, VariableItemChangeCallback, void*" @@ -2796,6 +2821,7 @@ Variable,+,sequence_reset_red,const NotificationSequence, Variable,+,sequence_reset_rgb,const NotificationSequence, Variable,+,sequence_reset_sound,const NotificationSequence, Variable,+,sequence_reset_vibro,const NotificationSequence, +Variable,+,sequence_semi_success,const NotificationSequence, Variable,+,sequence_set_blue_255,const NotificationSequence, Variable,+,sequence_set_green_255,const NotificationSequence, Variable,+,sequence_set_only_blue_255,const NotificationSequence, diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 5f4e6165dca..957d9d6733c 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -33,7 +33,7 @@ void furi_hal_init() { furi_hal_mpu_init(); furi_hal_clock_init(); furi_hal_random_init(); - furi_hal_console_init(); + furi_hal_serial_control_init(); furi_hal_rtc_init(); furi_hal_interrupt_init(); furi_hal_flash_init(); diff --git a/targets/f18/furi_hal/furi_hal_version_device.c b/targets/f18/furi_hal/furi_hal_version_device.c index e038b98d737..198ceb3f19f 100644 --- a/targets/f18/furi_hal/furi_hal_version_device.c +++ b/targets/f18/furi_hal/furi_hal_version_device.c @@ -23,3 +23,11 @@ const char* furi_hal_version_get_ic_id() { const char* furi_hal_version_get_mic_id() { return "Pending"; } + +const char* furi_hal_version_get_srrc_id() { + return "Pending"; +} + +const char* furi_hal_version_get_ncc_id() { + return "Pending"; +} \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 353d511ec89..f852a69be62 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,11 +1,12 @@ entry,status,name,type,params -Version,+,50.0,, +Version,+,54.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/dialogs/dialogs.h,, Header,+,applications/services/dolphin/dolphin.h,, +Header,+,applications/services/expansion/expansion.h,, Header,+,applications/services/gui/elements.h,, Header,+,applications/services/gui/gui.h,, Header,+,applications/services/gui/icon_i.h,, @@ -221,7 +222,6 @@ Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, Header,+,targets/f7/furi_hal/furi_hal_bus.h,, Header,+,targets/f7/furi_hal/furi_hal_clock.h,, -Header,+,targets/f7/furi_hal/furi_hal_console.h,, Header,+,targets/f7/furi_hal/furi_hal_dma.h,, Header,+,targets/f7/furi_hal/furi_hal_flash.h,, Header,+,targets/f7/furi_hal/furi_hal_gpio.h,, @@ -234,11 +234,14 @@ Header,+,targets/f7/furi_hal/furi_hal_os.h,, Header,+,targets/f7/furi_hal/furi_hal_pwm.h,, Header,+,targets/f7/furi_hal/furi_hal_resources.h,, Header,+,targets/f7/furi_hal/furi_hal_rfid.h,, +Header,+,targets/f7/furi_hal/furi_hal_rtc.h,, +Header,+,targets/f7/furi_hal/furi_hal_serial.h,, +Header,+,targets/f7/furi_hal/furi_hal_serial_control.h,, +Header,+,targets/f7/furi_hal/furi_hal_serial_types.h,, Header,+,targets/f7/furi_hal/furi_hal_spi_config.h,, Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,targets/f7/furi_hal/furi_hal_subghz.h,, Header,+,targets/f7/furi_hal/furi_hal_target_hw.h,, -Header,+,targets/f7/furi_hal/furi_hal_uart.h,, Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, @@ -259,7 +262,6 @@ Header,+,targets/furi_hal_include/furi_hal_nfc.h,, Header,+,targets/furi_hal_include/furi_hal_power.h,, Header,+,targets/furi_hal_include/furi_hal_random.h,, Header,+,targets/furi_hal_include/furi_hal_region.h,, -Header,+,targets/furi_hal_include/furi_hal_rtc.h,, Header,+,targets/furi_hal_include/furi_hal_sd.h,, Header,+,targets/furi_hal_include/furi_hal_speaker.h,, Header,+,targets/furi_hal_include/furi_hal_spi.h,, @@ -876,6 +878,9 @@ Function,-,exp10f,float,float Function,-,exp2,double,double Function,-,exp2f,float,float Function,-,exp2l,long double,long double +Function,+,expansion_disable,void,Expansion* +Function,+,expansion_enable,void,Expansion* +Function,+,expansion_set_listen_serial,void,"Expansion*, FuriHalSerialId" Function,-,expf,float,float Function,-,expl,long double,long double Function,-,explicit_bzero,void,"void*, size_t" @@ -1146,14 +1151,6 @@ Function,-,furi_hal_clock_switch_hse2hsi,void, Function,-,furi_hal_clock_switch_hse2pll,_Bool, Function,-,furi_hal_clock_switch_hsi2hse,void, Function,-,furi_hal_clock_switch_pll2hse,_Bool, -Function,+,furi_hal_console_disable,void, -Function,+,furi_hal_console_enable,void, -Function,+,furi_hal_console_init,void, -Function,+,furi_hal_console_printf,void,"const char[], ..." -Function,+,furi_hal_console_puts,void,const char* -Function,+,furi_hal_console_set_tx_callback,void,"FuriHalConsoleTxCallback, void*" -Function,+,furi_hal_console_tx,void,"const uint8_t*, size_t" -Function,+,furi_hal_console_tx_with_new_line,void,"const uint8_t*, size_t" Function,+,furi_hal_cortex_comp_enable,void,"FuriHalCortexComp, FuriHalCortexCompFunction, uint32_t, uint32_t, FuriHalCortexCompSize" Function,+,furi_hal_cortex_comp_reset,void,FuriHalCortexComp Function,+,furi_hal_cortex_delay_us,void,uint32_t @@ -1263,7 +1260,7 @@ Function,-,furi_hal_init,void, Function,-,furi_hal_init_early,void, Function,-,furi_hal_interrupt_init,void, Function,+,furi_hal_interrupt_set_isr,void,"FuriHalInterruptId, FuriHalInterruptISR, void*" -Function,+,furi_hal_interrupt_set_isr_ex,void,"FuriHalInterruptId, uint16_t, FuriHalInterruptISR, void*" +Function,+,furi_hal_interrupt_set_isr_ex,void,"FuriHalInterruptId, FuriHalInterruptPriority, FuriHalInterruptISR, void*" Function,+,furi_hal_light_blink_set_color,void,Light Function,+,furi_hal_light_blink_start,void,"Light, uint8_t, uint16_t, uint16_t" Function,+,furi_hal_light_blink_stop,void, @@ -1405,6 +1402,8 @@ Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, Function,+,furi_hal_rtc_get_locale_timeformat,FuriHalRtcLocaleTimeFormat, Function,+,furi_hal_rtc_get_locale_units,FuriHalRtcLocaleUnits, +Function,+,furi_hal_rtc_get_log_baud_rate,FuriHalRtcLogBaudRate, +Function,+,furi_hal_rtc_get_log_device,FuriHalRtcLogDevice, Function,+,furi_hal_rtc_get_log_level,uint8_t, Function,+,furi_hal_rtc_get_pin_fails,uint32_t, Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister @@ -1423,10 +1422,13 @@ Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode Function,+,furi_hal_rtc_set_locale_dateformat,void,FuriHalRtcLocaleDateFormat Function,+,furi_hal_rtc_set_locale_timeformat,void,FuriHalRtcLocaleTimeFormat Function,+,furi_hal_rtc_set_locale_units,void,FuriHalRtcLocaleUnits +Function,+,furi_hal_rtc_set_log_baud_rate,void,FuriHalRtcLogBaudRate +Function,+,furi_hal_rtc_set_log_device,void,FuriHalRtcLogDevice Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_sync_shadow,void, +Function,+,furi_hal_rtc_timestamp_to_datetime,void,"uint32_t, FuriHalRtcDateTime*" Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* Function,+,furi_hal_sd_get_card_state,FuriStatus, Function,+,furi_hal_sd_info,FuriStatus,FuriHalSdInfo* @@ -1436,6 +1438,32 @@ Function,+,furi_hal_sd_max_mount_retry_count,uint8_t, Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" +Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" +Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId +Function,+,furi_hal_serial_control_deinit,void, +Function,+,furi_hal_serial_control_init,void, +Function,+,furi_hal_serial_control_is_busy,_Bool,FuriHalSerialId +Function,+,furi_hal_serial_control_release,void,FuriHalSerialHandle* +Function,-,furi_hal_serial_control_resume,void, +Function,+,furi_hal_serial_control_set_expansion_callback,void,"FuriHalSerialId, FuriHalSerialControlExpansionCallback, void*" +Function,+,furi_hal_serial_control_set_logging_config,void,"FuriHalSerialId, uint32_t" +Function,-,furi_hal_serial_control_suspend,void, +Function,+,furi_hal_serial_deinit,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_disable_direction,void,"FuriHalSerialHandle*, FuriHalSerialDirection" +Function,+,furi_hal_serial_dma_rx,size_t,"FuriHalSerialHandle*, uint8_t*, size_t" +Function,+,furi_hal_serial_dma_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialDmaRxCallback, void*, _Bool" +Function,+,furi_hal_serial_dma_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_enable_direction,void,"FuriHalSerialHandle*, FuriHalSerialDirection" +Function,+,furi_hal_serial_get_gpio_pin,const GpioPin*,"FuriHalSerialHandle*, FuriHalSerialDirection" +Function,+,furi_hal_serial_init,void,"FuriHalSerialHandle*, uint32_t" +Function,+,furi_hal_serial_is_baud_rate_supported,_Bool,"FuriHalSerialHandle*, uint32_t" +Function,+,furi_hal_serial_resume,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_set_br,void,"FuriHalSerialHandle*, uint32_t" +Function,+,furi_hal_serial_suspend,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_tx,void,"FuriHalSerialHandle*, const uint8_t*, size_t" +Function,+,furi_hal_serial_tx_wait_complete,void,FuriHalSerialHandle* Function,+,furi_hal_speaker_acquire,_Bool,uint32_t Function,-,furi_hal_speaker_deinit,void, Function,-,furi_hal_speaker_init,void, @@ -1489,13 +1517,6 @@ Function,+,furi_hal_subghz_stop_async_tx,void, Function,+,furi_hal_subghz_tx,_Bool, Function,+,furi_hal_subghz_write_packet,void,"const uint8_t*, uint8_t" Function,+,furi_hal_switch,void,void* -Function,+,furi_hal_uart_deinit,void,FuriHalUartId -Function,+,furi_hal_uart_init,void,"FuriHalUartId, uint32_t" -Function,+,furi_hal_uart_resume,void,FuriHalUartId -Function,+,furi_hal_uart_set_br,void,"FuriHalUartId, uint32_t" -Function,+,furi_hal_uart_set_irq_cb,void,"FuriHalUartId, void (*)(UartIrqEvent, uint8_t, void*), void*" -Function,+,furi_hal_uart_suspend,void,FuriHalUartId -Function,+,furi_hal_uart_tx,void,"FuriHalUartId, uint8_t*, size_t" Function,+,furi_hal_usb_disable,void, Function,+,furi_hal_usb_enable,void, Function,+,furi_hal_usb_get_config,FuriHalUsbInterface*, @@ -1526,7 +1547,9 @@ Function,+,furi_hal_version_get_mic_id,const char*, Function,+,furi_hal_version_get_model_code,const char*, Function,+,furi_hal_version_get_model_name,const char*, Function,+,furi_hal_version_get_name_ptr,const char*, +Function,+,furi_hal_version_get_ncc_id,const char*, Function,+,furi_hal_version_get_otp_version,FuriHalVersionOtpVersion, +Function,+,furi_hal_version_get_srrc_id,const char*, Function,-,furi_hal_version_init,void, Function,+,furi_hal_version_uid,const uint8_t*, Function,+,furi_hal_version_uid_size,size_t, @@ -1539,15 +1562,17 @@ Function,+,furi_kernel_is_running,_Bool, Function,+,furi_kernel_lock,int32_t, Function,+,furi_kernel_restore_lock,int32_t,int32_t Function,+,furi_kernel_unlock,int32_t, +Function,+,furi_log_add_handler,_Bool,FuriLogHandler Function,+,furi_log_get_level,FuriLogLevel, Function,-,furi_log_init,void, Function,+,furi_log_level_from_string,_Bool,"const char*, FuriLogLevel*" Function,+,furi_log_level_to_string,_Bool,"FuriLogLevel, const char**" Function,+,furi_log_print_format,void,"FuriLogLevel, const char*, const char*, ..." Function,+,furi_log_print_raw_format,void,"FuriLogLevel, const char*, ..." +Function,+,furi_log_puts,void,const char* +Function,+,furi_log_remove_handler,_Bool,FuriLogHandler Function,+,furi_log_set_level,void,FuriLogLevel -Function,-,furi_log_set_puts,void,FuriLogPuts -Function,-,furi_log_set_timestamp,void,FuriLogTimestamp +Function,+,furi_log_tx,void,"const uint8_t*, size_t" Function,+,furi_message_queue_alloc,FuriMessageQueue*,"uint32_t, uint32_t" Function,+,furi_message_queue_free,void,FuriMessageQueue* Function,+,furi_message_queue_get,FuriStatus,"FuriMessageQueue*, void*, uint32_t" @@ -2667,7 +2692,7 @@ Function,-,round,double,double Function,+,roundf,float,float Function,-,roundl,long double,long double Function,+,rpc_session_close,void,RpcSession* -Function,+,rpc_session_feed,size_t,"RpcSession*, uint8_t*, size_t, uint32_t" +Function,+,rpc_session_feed,size_t,"RpcSession*, const uint8_t*, size_t, uint32_t" Function,+,rpc_session_get_available_size,size_t,RpcSession* Function,+,rpc_session_get_owner,RpcOwner,RpcSession* Function,+,rpc_session_open,RpcSession*,"Rpc*, RpcOwner" @@ -3199,10 +3224,10 @@ Function,-,utoa,char*,"unsigned, char*, int" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* -Function,+,value_index_bool,uint8_t,"const _Bool, const _Bool[], uint8_t" -Function,+,value_index_float,uint8_t,"const float, const float[], uint8_t" -Function,+,value_index_int32,uint8_t,"const int32_t, const int32_t[], uint8_t" -Function,+,value_index_uint32,uint8_t,"const uint32_t, const uint32_t[], uint8_t" +Function,+,value_index_bool,size_t,"const _Bool, const _Bool[], size_t" +Function,+,value_index_float,size_t,"const float, const float[], size_t" +Function,+,value_index_int32,size_t,"const int32_t, const int32_t[], size_t" +Function,+,value_index_uint32,size_t,"const uint32_t, const uint32_t[], size_t" Function,+,variable_item_get_context,void*,VariableItem* Function,+,variable_item_get_current_value_index,uint8_t,VariableItem* Function,+,variable_item_list_add,VariableItem*,"VariableItemList*, const char*, uint8_t, VariableItemChangeCallback, void*" @@ -3587,6 +3612,7 @@ Variable,+,sequence_reset_red,const NotificationSequence, Variable,+,sequence_reset_rgb,const NotificationSequence, Variable,+,sequence_reset_sound,const NotificationSequence, Variable,+,sequence_reset_vibro,const NotificationSequence, +Variable,+,sequence_semi_success,const NotificationSequence, Variable,+,sequence_set_blue_255,const NotificationSequence, Variable,+,sequence_set_green_255,const NotificationSequence, Variable,+,sequence_set_only_blue_255,const NotificationSequence, diff --git a/targets/f7/ble_glue/tl_dbg_conf.h b/targets/f7/ble_glue/tl_dbg_conf.h index daaa9d82ba8..240cd5f2faf 100644 --- a/targets/f7/ble_glue/tl_dbg_conf.h +++ b/targets/f7/ble_glue/tl_dbg_conf.h @@ -38,7 +38,7 @@ extern "C" { #endif #if(TL_SHCI_CMD_DBG_RAW_EN != 0) -#define TL_SHCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_) +#define TL_SHCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_log_tx(_PDATA_, _SIZE_) #else #define TL_SHCI_CMD_DBG_RAW(...) #endif @@ -52,7 +52,7 @@ extern "C" { #endif #if(TL_SHCI_EVT_DBG_RAW_EN != 0) -#define TL_SHCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_) +#define TL_SHCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_log_tx(_PDATA_, _SIZE_) #else #define TL_SHCI_EVT_DBG_RAW(...) #endif @@ -69,7 +69,7 @@ extern "C" { #endif #if(TL_HCI_CMD_DBG_RAW_EN != 0) -#define TL_HCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_) +#define TL_HCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_log_tx(_PDATA_, _SIZE_) #else #define TL_HCI_CMD_DBG_RAW(...) #endif @@ -83,7 +83,7 @@ extern "C" { #endif #if(TL_HCI_EVT_DBG_RAW_EN != 0) -#define TL_HCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_) +#define TL_HCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_log_tx(_PDATA_, _SIZE_) #else #define TL_HCI_EVT_DBG_RAW(...) #endif diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 2062645cdf3..88401429e63 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -33,7 +33,7 @@ void furi_hal_init() { furi_hal_mpu_init(); furi_hal_clock_init(); furi_hal_random_init(); - furi_hal_console_init(); + furi_hal_serial_control_init(); furi_hal_rtc_init(); furi_hal_interrupt_init(); furi_hal_flash_init(); diff --git a/targets/f7/furi_hal/furi_hal_console.c b/targets/f7/furi_hal/furi_hal_console.c deleted file mode 100644 index 0b113d2dac5..00000000000 --- a/targets/f7/furi_hal/furi_hal_console.c +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include - -#include -#include -#include - -#include - -#define TAG "FuriHalConsole" - -#ifdef HEAP_PRINT_DEBUG -#define CONSOLE_BAUDRATE 1843200 -#else -#define CONSOLE_BAUDRATE 230400 -#endif - -typedef struct { - bool alive; - FuriHalConsoleTxCallback tx_callback; - void* tx_callback_context; -} FuriHalConsole; - -FuriHalConsole furi_hal_console = { - .alive = false, - .tx_callback = NULL, - .tx_callback_context = NULL, -}; - -void furi_hal_console_init() { - furi_hal_uart_init(FuriHalUartIdUSART1, CONSOLE_BAUDRATE); - furi_hal_console.alive = true; -} - -void furi_hal_console_enable() { - furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL, NULL); - while(!LL_USART_IsActiveFlag_TC(USART1)) - ; - furi_hal_uart_set_br(FuriHalUartIdUSART1, CONSOLE_BAUDRATE); - furi_hal_console.alive = true; -} - -void furi_hal_console_disable() { - while(!LL_USART_IsActiveFlag_TC(USART1)) - ; - furi_hal_console.alive = false; -} - -void furi_hal_console_set_tx_callback(FuriHalConsoleTxCallback callback, void* context) { - FURI_CRITICAL_ENTER(); - furi_hal_console.tx_callback = callback; - furi_hal_console.tx_callback_context = context; - FURI_CRITICAL_EXIT(); -} - -void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size) { - if(!furi_hal_console.alive) return; - - FURI_CRITICAL_ENTER(); - // Transmit data - - if(furi_hal_console.tx_callback) { - furi_hal_console.tx_callback(buffer, buffer_size, furi_hal_console.tx_callback_context); - } - - furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size); - // Wait for TC flag to be raised for last char - while(!LL_USART_IsActiveFlag_TC(USART1)) - ; - FURI_CRITICAL_EXIT(); -} - -void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size) { - if(!furi_hal_console.alive) return; - - FURI_CRITICAL_ENTER(); - // Transmit data - furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size); - // Transmit new line symbols - furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)"\r\n", 2); - // Wait for TC flag to be raised for last char - while(!LL_USART_IsActiveFlag_TC(USART1)) - ; - FURI_CRITICAL_EXIT(); -} - -void furi_hal_console_printf(const char format[], ...) { - FuriString* string; - va_list args; - va_start(args, format); - string = furi_string_alloc_vprintf(format, args); - va_end(args); - furi_hal_console_tx((const uint8_t*)furi_string_get_cstr(string), furi_string_size(string)); - furi_string_free(string); -} - -void furi_hal_console_puts(const char* data) { - furi_hal_console_tx((const uint8_t*)data, strlen(data)); -} diff --git a/targets/f7/furi_hal/furi_hal_console.h b/targets/f7/furi_hal/furi_hal_console.h deleted file mode 100644 index ce31a66b33f..00000000000 --- a/targets/f7/furi_hal/furi_hal_console.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void (*FuriHalConsoleTxCallback)(const uint8_t* buffer, size_t size, void* context); - -void furi_hal_console_init(); - -void furi_hal_console_enable(); - -void furi_hal_console_disable(); - -void furi_hal_console_set_tx_callback(FuriHalConsoleTxCallback callback, void* context); - -void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size); - -void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size); - -/** - * Printf-like plain uart interface - * @warning Will not work in ISR context - * @param format - * @param ... - */ -void furi_hal_console_printf(const char format[], ...) _ATTRIBUTE((__format__(__printf__, 1, 2))); - -void furi_hal_console_puts(const char* data); - -#ifdef __cplusplus -} -#endif diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index 3b20b6bc3a6..cc41568728b 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -406,7 +406,10 @@ static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) { LL_DMA_EnableIT_TC(INFRARED_DMA_CH1_DEF); furi_hal_interrupt_set_isr_ex( - INFRARED_DMA_CH1_IRQ, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL); + INFRARED_DMA_CH1_IRQ, + FuriHalInterruptPriorityKamiSama, + furi_hal_infrared_tx_dma_polarity_isr, + NULL); } static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { @@ -436,7 +439,7 @@ static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) { LL_DMA_EnableIT_HT(INFRARED_DMA_CH2_DEF); LL_DMA_EnableIT_TE(INFRARED_DMA_CH2_DEF); - furi_hal_interrupt_set_isr_ex(INFRARED_DMA_CH2_IRQ, 5, furi_hal_infrared_tx_dma_isr, NULL); + furi_hal_interrupt_set_isr(INFRARED_DMA_CH2_IRQ, furi_hal_infrared_tx_dma_isr, NULL); } static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num) { diff --git a/targets/f7/furi_hal/furi_hal_interrupt.c b/targets/f7/furi_hal/furi_hal_interrupt.c index c508dac72bf..a9cd4e7aa6a 100644 --- a/targets/f7/furi_hal/furi_hal_interrupt.c +++ b/targets/f7/furi_hal/furi_hal_interrupt.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,7 +11,7 @@ #define TAG "FuriHalInterrupt" -#define FURI_HAL_INTERRUPT_DEFAULT_PRIORITY 5 +#define FURI_HAL_INTERRUPT_DEFAULT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 5) typedef struct { FuriHalInterruptISR isr; @@ -58,6 +59,12 @@ const IRQn_Type furi_hal_interrupt_irqn[FuriHalInterruptIdMax] = { // LPTIMx [FuriHalInterruptIdLpTim1] = LPTIM1_IRQn, [FuriHalInterruptIdLpTim2] = LPTIM2_IRQn, + + // UARTx + [FuriHalInterruptIdUart1] = USART1_IRQn, + + // LPUARTx + [FuriHalInterruptIdLpUart1] = LPUART1_IRQn, }; __attribute__((always_inline)) static inline void @@ -119,16 +126,21 @@ void furi_hal_interrupt_init() { } void furi_hal_interrupt_set_isr(FuriHalInterruptId index, FuriHalInterruptISR isr, void* context) { - furi_hal_interrupt_set_isr_ex(index, FURI_HAL_INTERRUPT_DEFAULT_PRIORITY, isr, context); + furi_hal_interrupt_set_isr_ex(index, FuriHalInterruptPriorityNormal, isr, context); } void furi_hal_interrupt_set_isr_ex( FuriHalInterruptId index, - uint16_t priority, + FuriHalInterruptPriority priority, FuriHalInterruptISR isr, void* context) { furi_check(index < FuriHalInterruptIdMax); - furi_check(priority <= 15); + furi_check( + (priority >= FuriHalInterruptPriorityLowest && + priority <= FuriHalInterruptPriorityHighest) || + priority == FuriHalInterruptPriorityKamiSama); + + uint16_t real_priority = FURI_HAL_INTERRUPT_DEFAULT_PRIORITY - priority; if(isr) { // Pre ISR set @@ -146,7 +158,7 @@ void furi_hal_interrupt_set_isr_ex( if(isr) { // Post ISR set furi_hal_interrupt_clear_pending(index); - furi_hal_interrupt_enable(index, priority); + furi_hal_interrupt_enable(index, real_priority); } else { // Post ISR clear } @@ -328,3 +340,11 @@ void LPTIM1_IRQHandler() { void LPTIM2_IRQHandler() { furi_hal_interrupt_call(FuriHalInterruptIdLpTim2); } + +void USART1_IRQHandler(void) { + furi_hal_interrupt_call(FuriHalInterruptIdUart1); +} + +void LPUART1_IRQHandler(void) { + furi_hal_interrupt_call(FuriHalInterruptIdLpUart1); +} \ No newline at end of file diff --git a/targets/f7/furi_hal/furi_hal_interrupt.h b/targets/f7/furi_hal/furi_hal_interrupt.h index 8a280ff8d68..03d7850f94d 100644 --- a/targets/f7/furi_hal/furi_hal_interrupt.h +++ b/targets/f7/furi_hal/furi_hal_interrupt.h @@ -49,31 +49,64 @@ typedef enum { FuriHalInterruptIdLpTim1, FuriHalInterruptIdLpTim2, + //UARTx + FuriHalInterruptIdUart1, + + //LPUARTx + FuriHalInterruptIdLpUart1, + // Service value FuriHalInterruptIdMax, } FuriHalInterruptId; +typedef enum { + FuriHalInterruptPriorityLowest = + -3, /**< Lowest priority level, you can use ISR-safe OS primitives */ + FuriHalInterruptPriorityLower = + -2, /**< Lower priority level, you can use ISR-safe OS primitives */ + FuriHalInterruptPriorityLow = + -1, /**< Low priority level, you can use ISR-safe OS primitives */ + FuriHalInterruptPriorityNormal = + 0, /**< Normal(default) priority level, you can use ISR-safe OS primitives */ + FuriHalInterruptPriorityHigh = + 1, /**< High priority level, you can use ISR-safe OS primitives */ + FuriHalInterruptPriorityHigher = + 2, /**< Higher priority level, you can use ISR-safe OS primitives */ + FuriHalInterruptPriorityHighest = + 3, /**< Highest priority level, you can use ISR-safe OS primitives */ + + /* Special group, read docs first(ALL OF THEM: especially FreeRTOS configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY) */ + FuriHalInterruptPriorityKamiSama = + 6, /**< Forget about thread safety, you are god now. No one can prevent you from messing with OS critical section. You are not allowed to use any OS primitives, but who can stop you? Use this priority only for direct hardware interaction with LL HAL. */ +} FuriHalInterruptPriority; + /** Initialize interrupt subsystem */ void furi_hal_interrupt_init(); /** Set ISR and enable interrupt with default priority - * We don't clear interrupt flags for you, do it by your self. - * @param index - interrupt ID - * @param isr - your interrupt service routine or use NULL to clear - * @param context - isr context + * + * @warning Interrupt flags are not cleared automatically. You may want to + * ensure that your peripheral status flags are cleared. + * + * @param index - interrupt ID + * @param isr - your interrupt service routine or use NULL to clear + * @param context - isr context */ void furi_hal_interrupt_set_isr(FuriHalInterruptId index, FuriHalInterruptISR isr, void* context); /** Set ISR and enable interrupt with custom priority - * We don't clear interrupt flags for you, do it by your self. - * @param index - interrupt ID - * @param priority - 0 to 15, 0 highest - * @param isr - your interrupt service routine or use NULL to clear - * @param context - isr context + * + * @warning Interrupt flags are not cleared automatically. You may want to + * ensure that your peripheral status flags are cleared. + * + * @param index - interrupt ID + * @param priority - One of FuriHalInterruptPriority + * @param isr - your interrupt service routine or use NULL to clear + * @param context - isr context */ void furi_hal_interrupt_set_isr_ex( FuriHalInterruptId index, - uint16_t priority, + FuriHalInterruptPriority priority, FuriHalInterruptISR isr, void* context); diff --git a/targets/f7/furi_hal/furi_hal_os.c b/targets/f7/furi_hal/furi_hal_os.c index ea835b95fbe..85f2d2e45de 100644 --- a/targets/f7/furi_hal/furi_hal_os.c +++ b/targets/f7/furi_hal/furi_hal_os.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -195,7 +194,8 @@ void vPortSuppressTicksAndSleep(TickType_t expected_idle_ticks) { if(completed_ticks > 0) { if(completed_ticks > expected_idle_ticks) { #ifdef FURI_HAL_OS_DEBUG - furi_hal_console_printf(">%lu\r\n", completed_ticks - expected_idle_ticks); + furi_log_print_raw_format( + FuriLogLevelDebug, ">%lu\r\n", completed_ticks - expected_idle_ticks); #endif completed_ticks = expected_idle_ticks; } @@ -208,8 +208,8 @@ void vPortSuppressTicksAndSleep(TickType_t expected_idle_ticks) { void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName) { UNUSED(xTask); - furi_hal_console_puts("\r\n\r\n stack overflow in "); - furi_hal_console_puts(pcTaskName); - furi_hal_console_puts("\r\n\r\n"); + furi_log_puts("\r\n\r\n stack overflow in "); + furi_log_puts(pcTaskName); + furi_log_puts("\r\n\r\n"); furi_crash("StackOverflow"); } diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index 9e3a70da731..f03aea75f75 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -173,19 +173,23 @@ static inline bool furi_hal_power_deep_sleep_available() { } static inline void furi_hal_power_light_sleep() { +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 1); +#endif __WFI(); +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0); +#endif } static inline void furi_hal_power_suspend_aux_periphs() { // Disable USART - furi_hal_uart_suspend(FuriHalUartIdUSART1); - furi_hal_uart_suspend(FuriHalUartIdLPUART1); + furi_hal_serial_control_suspend(); } static inline void furi_hal_power_resume_aux_periphs() { // Re-enable USART - furi_hal_uart_resume(FuriHalUartIdUSART1); - furi_hal_uart_resume(FuriHalUartIdLPUART1); + furi_hal_serial_control_resume(); } static inline void furi_hal_power_deep_sleep() { @@ -225,7 +229,13 @@ static inline void furi_hal_power_deep_sleep() { __force_stores(); #endif +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 1); +#endif __WFI(); +#ifdef FURI_HAL_POWER_DEBUG + furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0); +#endif LL_LPM_EnableSleep(); @@ -252,21 +262,9 @@ static inline void furi_hal_power_deep_sleep() { void furi_hal_power_sleep() { if(furi_hal_power_deep_sleep_available()) { -#ifdef FURI_HAL_POWER_DEBUG - furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 1); -#endif furi_hal_power_deep_sleep(); -#ifdef FURI_HAL_POWER_DEBUG - furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0); -#endif } else { -#ifdef FURI_HAL_POWER_DEBUG - furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 1); -#endif furi_hal_power_light_sleep(); -#ifdef FURI_HAL_POWER_DEBUG - furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0); -#endif } } diff --git a/targets/f7/furi_hal/furi_hal_rtc.c b/targets/f7/furi_hal/furi_hal_rtc.c index cb8065bedbb..88aad6858ef 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.c +++ b/targets/f7/furi_hal/furi_hal_rtc.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -34,7 +35,9 @@ typedef struct { FuriHalRtcLocaleUnits locale_units : 1; FuriHalRtcLocaleTimeFormat locale_timeformat : 1; FuriHalRtcLocaleDateFormat locale_dateformat : 2; - uint8_t reserved : 6; + FuriHalRtcLogDevice log_device : 2; + FuriHalRtcLogBaudRate log_baud_rate : 3; + uint8_t reserved : 1; } SystemReg; _Static_assert(sizeof(SystemReg) == 4, "SystemReg size mismatch"); @@ -51,6 +54,24 @@ static const uint8_t furi_hal_rtc_days_per_month[2][FURI_HAL_RTC_MONTHS_COUNT] = static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366}; +static const FuriHalSerialId furi_hal_rtc_log_devices[] = { + [FuriHalRtcLogDeviceUsart] = FuriHalSerialIdUsart, + [FuriHalRtcLogDeviceLpuart] = FuriHalSerialIdLpuart, + [FuriHalRtcLogDeviceReserved] = FuriHalSerialIdMax, + [FuriHalRtcLogDeviceNone] = FuriHalSerialIdMax, +}; + +static const uint32_t furi_hal_rtc_log_baud_rates[] = { + [FuriHalRtcLogBaudRate230400] = 230400, + [FuriHalRtcLogBaudRate9600] = 9600, + [FuriHalRtcLogBaudRate38400] = 38400, + [FuriHalRtcLogBaudRate57600] = 57600, + [FuriHalRtcLogBaudRate115200] = 115200, + [FuriHalRtcLogBaudRate460800] = 460800, + [FuriHalRtcLogBaudRate921600] = 921600, + [FuriHalRtcLogBaudRate1843200] = 1843200, +}; + static void furi_hal_rtc_reset() { LL_RCC_ForceBackupDomainReset(); LL_RCC_ReleaseBackupDomainReset(); @@ -153,6 +174,9 @@ void furi_hal_rtc_init() { LL_RTC_Init(RTC, &RTC_InitStruct); furi_log_set_level(furi_hal_rtc_get_log_level()); + furi_hal_serial_control_set_logging_config( + furi_hal_rtc_log_devices[furi_hal_rtc_get_log_device()], + furi_hal_rtc_log_baud_rates[furi_hal_rtc_get_log_baud_rate()]); FURI_LOG_I(TAG, "Init OK"); } @@ -199,6 +223,40 @@ uint8_t furi_hal_rtc_get_log_level() { return data->log_level; } +void furi_hal_rtc_set_log_device(FuriHalRtcLogDevice device) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->log_device = device; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); + + furi_hal_serial_control_set_logging_config( + furi_hal_rtc_log_devices[furi_hal_rtc_get_log_device()], + furi_hal_rtc_log_baud_rates[furi_hal_rtc_get_log_baud_rate()]); +} + +FuriHalRtcLogDevice furi_hal_rtc_get_log_device() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->log_device; +} + +void furi_hal_rtc_set_log_baud_rate(FuriHalRtcLogBaudRate baud_rate) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->log_baud_rate = baud_rate; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); + + furi_hal_serial_control_set_logging_config( + furi_hal_rtc_log_devices[furi_hal_rtc_get_log_device()], + furi_hal_rtc_log_baud_rates[furi_hal_rtc_get_log_baud_rate()]); +} + +FuriHalRtcLogBaudRate furi_hal_rtc_get_log_baud_rate() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->log_baud_rate; +} + void furi_hal_rtc_set_flag(FuriHalRtcFlag flag) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); SystemReg* data = (SystemReg*)&data_reg; @@ -424,6 +482,32 @@ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { return timestamp; } +void furi_hal_rtc_timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { + uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; + uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; + + datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; + + while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { + days -= furi_hal_rtc_get_days_per_year(datetime->year); + (datetime->year)++; + } + + datetime->month = 1; + while(days >= furi_hal_rtc_get_days_per_month( + furi_hal_rtc_is_leap_year(datetime->year), datetime->month)) { + days -= furi_hal_rtc_get_days_per_month( + furi_hal_rtc_is_leap_year(datetime->year), datetime->month); + (datetime->month)++; + } + + datetime->day = days + 1; + datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; + datetime->minute = + (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; + datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; +} + uint16_t furi_hal_rtc_get_days_per_year(uint16_t year) { return furi_hal_rtc_days_per_year[furi_hal_rtc_is_leap_year(year) ? 1 : 0]; } diff --git a/targets/furi_hal_include/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h similarity index 69% rename from targets/furi_hal_include/furi_hal_rtc.h rename to targets/f7/furi_hal/furi_hal_rtc.h index 98b23466c27..0a5023131f8 100644 --- a/targets/furi_hal_include/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -64,32 +64,50 @@ typedef enum { } FuriHalRtcRegister; typedef enum { - FuriHalRtcLocaleUnitsMetric = 0, /**< Metric measurement units */ - FuriHalRtcLocaleUnitsImperial = 1, /**< Imperial measurement units */ + FuriHalRtcLocaleUnitsMetric = 0x0, /**< Metric measurement units */ + FuriHalRtcLocaleUnitsImperial = 0x1, /**< Imperial measurement units */ } FuriHalRtcLocaleUnits; typedef enum { - FuriHalRtcLocaleTimeFormat24h = 0, /**< 24-hour format */ - FuriHalRtcLocaleTimeFormat12h = 1, /**< 12-hour format */ + FuriHalRtcLocaleTimeFormat24h = 0x0, /**< 24-hour format */ + FuriHalRtcLocaleTimeFormat12h = 0x1, /**< 12-hour format */ } FuriHalRtcLocaleTimeFormat; typedef enum { - FuriHalRtcLocaleDateFormatDMY = 0, /**< Day/Month/Year */ - FuriHalRtcLocaleDateFormatMDY = 1, /**< Month/Day/Year */ - FuriHalRtcLocaleDateFormatYMD = 2, /**< Year/Month/Day */ + FuriHalRtcLocaleDateFormatDMY = 0x0, /**< Day/Month/Year */ + FuriHalRtcLocaleDateFormatMDY = 0x1, /**< Month/Day/Year */ + FuriHalRtcLocaleDateFormatYMD = 0x2, /**< Year/Month/Day */ } FuriHalRtcLocaleDateFormat; +typedef enum { + FuriHalRtcLogDeviceUsart = 0x0, /**< Default: USART */ + FuriHalRtcLogDeviceLpuart = 0x1, /**< Default: LPUART */ + FuriHalRtcLogDeviceReserved = 0x2, /**< Reserved for future use */ + FuriHalRtcLogDeviceNone = 0x3, /**< None, disable serial logging */ +} FuriHalRtcLogDevice; + +typedef enum { + FuriHalRtcLogBaudRate230400 = 0x0, /**< 230400 baud */ + FuriHalRtcLogBaudRate9600 = 0x1, /**< 9600 baud */ + FuriHalRtcLogBaudRate38400 = 0x2, /**< 38400 baud */ + FuriHalRtcLogBaudRate57600 = 0x3, /**< 57600 baud */ + FuriHalRtcLogBaudRate115200 = 0x4, /**< 115200 baud */ + FuriHalRtcLogBaudRate460800 = 0x5, /**< 460800 baud */ + FuriHalRtcLogBaudRate921600 = 0x6, /**< 921600 baud */ + FuriHalRtcLogBaudRate1843200 = 0x7, /**< 1843200 baud */ +} FuriHalRtcLogBaudRate; + /** Early initialization */ -void furi_hal_rtc_init_early(); +void furi_hal_rtc_init_early(void); /** Early de-initialization */ -void furi_hal_rtc_deinit_early(); +void furi_hal_rtc_deinit_early(void); /** Initialize RTC subsystem */ -void furi_hal_rtc_init(); +void furi_hal_rtc_init(void); /** Force sync shadow registers */ -void furi_hal_rtc_sync_shadow(); +void furi_hal_rtc_sync_shadow(void); /** Reset ALL RTC registers content */ void furi_hal_rtc_reset_registers(); @@ -119,7 +137,31 @@ void furi_hal_rtc_set_log_level(uint8_t level); * * @return The Log Level value */ -uint8_t furi_hal_rtc_get_log_level(); +uint8_t furi_hal_rtc_get_log_level(void); + +/** Set logging device + * + * @param[in] device The device + */ +void furi_hal_rtc_set_log_device(FuriHalRtcLogDevice device); + +/** Get logging device + * + * @return The furi hal rtc log device. + */ +FuriHalRtcLogDevice furi_hal_rtc_get_log_device(void); + +/** Set logging baud rate + * + * @param[in] baud_rate The baud rate + */ +void furi_hal_rtc_set_log_baud_rate(FuriHalRtcLogBaudRate baud_rate); + +/** Get logging baud rate + * + * @return The furi hal rtc log baud rate. + */ +FuriHalRtcLogBaudRate furi_hal_rtc_get_log_baud_rate(void); /** Set RTC Flag * @@ -151,7 +193,7 @@ void furi_hal_rtc_set_boot_mode(FuriHalRtcBootMode mode); * * @return The RTC boot mode. */ -FuriHalRtcBootMode furi_hal_rtc_get_boot_mode(); +FuriHalRtcBootMode furi_hal_rtc_get_boot_mode(void); /** Set Heap Track mode * @@ -163,7 +205,7 @@ void furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackMode mode); * * @return The RTC heap track mode. */ -FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode(); +FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode(void); /** Set locale units * @@ -175,7 +217,7 @@ void furi_hal_rtc_set_locale_units(FuriHalRtcLocaleUnits value); * * @return The RTC Locale Units. */ -FuriHalRtcLocaleUnits furi_hal_rtc_get_locale_units(); +FuriHalRtcLocaleUnits furi_hal_rtc_get_locale_units(void); /** Set RTC Locale Time Format * @@ -187,7 +229,7 @@ void furi_hal_rtc_set_locale_timeformat(FuriHalRtcLocaleTimeFormat value); * * @return The RTC Locale Time Format. */ -FuriHalRtcLocaleTimeFormat furi_hal_rtc_get_locale_timeformat(); +FuriHalRtcLocaleTimeFormat furi_hal_rtc_get_locale_timeformat(void); /** Set RTC Locale Date Format * @@ -199,7 +241,7 @@ void furi_hal_rtc_set_locale_dateformat(FuriHalRtcLocaleDateFormat value); * * @return The RTC Locale Date Format */ -FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat(); +FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat(void); /** Set RTC Date Time * @@ -231,7 +273,7 @@ void furi_hal_rtc_set_fault_data(uint32_t value); * * @return RTC Fault Data value */ -uint32_t furi_hal_rtc_get_fault_data(); +uint32_t furi_hal_rtc_get_fault_data(void); /** Set Pin Fails count * @@ -243,22 +285,33 @@ void furi_hal_rtc_set_pin_fails(uint32_t value); * * @return Pin Fails Count */ -uint32_t furi_hal_rtc_get_pin_fails(); +uint32_t furi_hal_rtc_get_pin_fails(void); /** Get UNIX Timestamp * * @return Unix Timestamp in seconds from UNIX epoch start */ -uint32_t furi_hal_rtc_get_timestamp(); +uint32_t furi_hal_rtc_get_timestamp(void); /** Convert DateTime to UNIX timestamp + * + * @warning Mind timezone when perform conversion * - * @param datetime The datetime + * @param datetime The datetime (UTC) * * @return UNIX Timestamp in seconds from UNIX epoch start */ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); +/** Convert UNIX timestamp to DateTime + * + * @warning Mind timezone when perform conversion + * + * @param[in] timestamp UNIX Timestamp in seconds from UNIX epoch start + * @param[out] datetime The datetime (UTC) + */ +void furi_hal_rtc_timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime); + /** Gets the number of days in the year according to the Gregorian calendar. * * @param year Input year. diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c new file mode 100644 index 00000000000..1296ee6202d --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -0,0 +1,937 @@ +#include +#include "furi_hal_serial_types_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FURI_HAL_SERIAL_USART_OVERSAMPLING LL_USART_OVERSAMPLING_16 + +#define FURI_HAL_SERIAL_USART_DMA_INSTANCE (DMA1) +#define FURI_HAL_SERIAL_USART_DMA_CHANNEL (LL_DMA_CHANNEL_6) + +#define FURI_HAL_SERIAL_LPUART_DMA_INSTANCE (DMA1) +#define FURI_HAL_SERIAL_LPUART_DMA_CHANNEL (LL_DMA_CHANNEL_7) + +typedef struct { + uint8_t* buffer_rx_ptr; + size_t buffer_rx_index_write; + size_t buffer_rx_index_read; + bool enabled; + FuriHalSerialHandle* handle; + FuriHalSerialAsyncRxCallback rx_byte_callback; + FuriHalSerialDmaRxCallback rx_dma_callback; + void* context; +} FuriHalSerial; + +typedef void (*FuriHalSerialControlFunc)(USART_TypeDef*); + +typedef struct { + USART_TypeDef* periph; + GpioAltFn alt_fn; + const GpioPin* gpio[FuriHalSerialDirectionMax]; + FuriHalSerialControlFunc enable[FuriHalSerialDirectionMax]; + FuriHalSerialControlFunc disable[FuriHalSerialDirectionMax]; +} FuriHalSerialConfig; + +static const FuriHalSerialConfig furi_hal_serial_config[FuriHalSerialIdMax] = { + [FuriHalSerialIdUsart] = + { + .periph = USART1, + .alt_fn = GpioAltFn7USART1, + .gpio = + { + [FuriHalSerialDirectionTx] = &gpio_usart_tx, + [FuriHalSerialDirectionRx] = &gpio_usart_rx, + }, + .enable = + { + [FuriHalSerialDirectionTx] = LL_USART_EnableDirectionTx, + [FuriHalSerialDirectionRx] = LL_USART_EnableDirectionRx, + }, + .disable = + { + [FuriHalSerialDirectionTx] = LL_USART_DisableDirectionTx, + [FuriHalSerialDirectionRx] = LL_USART_DisableDirectionRx, + }, + }, + [FuriHalSerialIdLpuart] = + { + .periph = LPUART1, + .alt_fn = GpioAltFn8LPUART1, + .gpio = + { + [FuriHalSerialDirectionTx] = &gpio_ext_pc1, + [FuriHalSerialDirectionRx] = &gpio_ext_pc0, + }, + .enable = + { + [FuriHalSerialDirectionTx] = LL_LPUART_EnableDirectionTx, + [FuriHalSerialDirectionRx] = LL_LPUART_EnableDirectionRx, + }, + .disable = + { + [FuriHalSerialDirectionTx] = LL_LPUART_DisableDirectionTx, + [FuriHalSerialDirectionRx] = LL_LPUART_DisableDirectionRx, + }, + }, +}; + +static FuriHalSerial furi_hal_serial[FuriHalSerialIdMax] = {0}; + +static size_t furi_hal_serial_dma_bytes_available(FuriHalSerialId ch); + +static void furi_hal_serial_async_rx_configure( + FuriHalSerialHandle* handle, + FuriHalSerialAsyncRxCallback callback, + void* context); + +static void furi_hal_serial_usart_irq_callback(void* context) { + UNUSED(context); + + FuriHalSerialRxEvent event = 0; + // Notification flags + if(USART1->ISR & USART_ISR_RXNE_RXFNE) { + event |= FuriHalSerialRxEventData; + } + if(USART1->ISR & USART_ISR_IDLE) { + USART1->ICR = USART_ICR_IDLECF; + event |= FuriHalSerialRxEventIdle; + } + // Error flags + if(USART1->ISR & USART_ISR_ORE) { + USART1->ICR = USART_ICR_ORECF; + event |= FuriHalSerialRxEventOverrunError; + } + if(USART1->ISR & USART_ISR_NE) { + USART1->ICR = USART_ICR_NECF; + event |= FuriHalSerialRxEventNoiseError; + } + if(USART1->ISR & USART_ISR_FE) { + USART1->ICR = USART_ICR_FECF; + event |= FuriHalSerialRxEventFrameError; + } + if(USART1->ISR & USART_ISR_PE) { + USART1->ICR = USART_ICR_PECF; + event |= FuriHalSerialRxEventFrameError; + } + + if(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr == NULL) { + if(furi_hal_serial[FuriHalSerialIdUsart].rx_byte_callback) { + furi_hal_serial[FuriHalSerialIdUsart].rx_byte_callback( + furi_hal_serial[FuriHalSerialIdUsart].handle, + event, + furi_hal_serial[FuriHalSerialIdUsart].context); + } + } else { + if(furi_hal_serial[FuriHalSerialIdUsart].rx_dma_callback) { + furi_hal_serial[FuriHalSerialIdUsart].rx_dma_callback( + furi_hal_serial[FuriHalSerialIdUsart].handle, + event, + furi_hal_serial_dma_bytes_available(FuriHalSerialIdUsart), + furi_hal_serial[FuriHalSerialIdUsart].context); + } + } +} + +static void furi_hal_serial_usart_dma_rx_isr(void* context) { + UNUSED(context); +#if FURI_HAL_SERIAL_USART_DMA_CHANNEL == LL_DMA_CHANNEL_6 + if(LL_DMA_IsActiveFlag_HT6(FURI_HAL_SERIAL_USART_DMA_INSTANCE)) { + LL_DMA_ClearFlag_HT6(FURI_HAL_SERIAL_USART_DMA_INSTANCE); + furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_index_write = + FURI_HAL_SERIAL_DMA_BUFFER_SIZE - + LL_DMA_GetDataLength( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + if((furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_index_read > + furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_index_write) || + (furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_index_read < + FURI_HAL_SERIAL_DMA_BUFFER_SIZE / 4)) { + if(furi_hal_serial[FuriHalSerialIdUsart].rx_dma_callback) { + furi_hal_serial[FuriHalSerialIdUsart].rx_dma_callback( + furi_hal_serial[FuriHalSerialIdUsart].handle, + FuriHalSerialRxEventData, + furi_hal_serial_dma_bytes_available(FuriHalSerialIdUsart), + furi_hal_serial[FuriHalSerialIdUsart].context); + } + } + + } else if(LL_DMA_IsActiveFlag_TC6(FURI_HAL_SERIAL_USART_DMA_INSTANCE)) { + LL_DMA_ClearFlag_TC6(FURI_HAL_SERIAL_USART_DMA_INSTANCE); + + if(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_index_read < + FURI_HAL_SERIAL_DMA_BUFFER_SIZE * 3 / 4) { + if(furi_hal_serial[FuriHalSerialIdUsart].rx_dma_callback) { + furi_hal_serial[FuriHalSerialIdUsart].rx_dma_callback( + furi_hal_serial[FuriHalSerialIdUsart].handle, + FuriHalSerialRxEventData, + furi_hal_serial_dma_bytes_available(FuriHalSerialIdUsart), + furi_hal_serial[FuriHalSerialIdUsart].context); + } + } + } +#else +#error Update this code. Would you kindly? +#endif +} + +static void furi_hal_serial_usart_init_dma_rx(void) { + /* USART1_RX_DMA Init */ + furi_check(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr == NULL); + furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_index_write = 0; + furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_index_read = 0; + furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr = malloc(FURI_HAL_SERIAL_DMA_BUFFER_SIZE); + LL_DMA_SetMemoryAddress( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, + FURI_HAL_SERIAL_USART_DMA_CHANNEL, + (uint32_t)furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr); + LL_DMA_SetPeriphAddress( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, + FURI_HAL_SERIAL_USART_DMA_CHANNEL, + (uint32_t) & (USART1->RDR)); + + LL_DMA_ConfigTransfer( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, + FURI_HAL_SERIAL_USART_DMA_CHANNEL, + LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | LL_DMA_MDATAALIGN_BYTE | + LL_DMA_PRIORITY_HIGH); + LL_DMA_SetDataLength( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, + FURI_HAL_SERIAL_USART_DMA_CHANNEL, + FURI_HAL_SERIAL_DMA_BUFFER_SIZE); + LL_DMA_SetPeriphRequest( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, + FURI_HAL_SERIAL_USART_DMA_CHANNEL, + LL_DMAMUX_REQ_USART1_RX); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch6, furi_hal_serial_usart_dma_rx_isr, NULL); + +#if FURI_HAL_SERIAL_USART_DMA_CHANNEL == LL_DMA_CHANNEL_6 + if(LL_DMA_IsActiveFlag_HT6(FURI_HAL_SERIAL_USART_DMA_INSTANCE)) + LL_DMA_ClearFlag_HT6(FURI_HAL_SERIAL_USART_DMA_INSTANCE); + if(LL_DMA_IsActiveFlag_TC6(FURI_HAL_SERIAL_USART_DMA_INSTANCE)) + LL_DMA_ClearFlag_TC6(FURI_HAL_SERIAL_USART_DMA_INSTANCE); + if(LL_DMA_IsActiveFlag_TE6(FURI_HAL_SERIAL_USART_DMA_INSTANCE)) + LL_DMA_ClearFlag_TE6(FURI_HAL_SERIAL_USART_DMA_INSTANCE); +#else +#error Update this code. Would you kindly? +#endif + + LL_DMA_EnableIT_TC(FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + LL_DMA_EnableIT_HT(FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + + LL_DMA_EnableChannel(FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + LL_USART_EnableDMAReq_RX(USART1); + + LL_USART_EnableIT_IDLE(USART1); +} + +static void furi_hal_serial_usart_deinit_dma_rx(void) { + if(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr != NULL) { + LL_DMA_DisableChannel( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + LL_USART_DisableDMAReq_RX(USART1); + + LL_USART_DisableIT_IDLE(USART1); + LL_DMA_DisableIT_TC(FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + LL_DMA_DisableIT_HT(FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + + LL_DMA_ClearFlag_TC6(FURI_HAL_SERIAL_USART_DMA_INSTANCE); + LL_DMA_ClearFlag_HT6(FURI_HAL_SERIAL_USART_DMA_INSTANCE); + + LL_DMA_DeInit(FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch6, NULL, NULL); + free(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr); + furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr = NULL; + } +} + +static void furi_hal_serial_usart_init(FuriHalSerialHandle* handle, uint32_t baud) { + furi_hal_bus_enable(FuriHalBusUSART1); + LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK2); + + furi_hal_gpio_init_ex( + &gpio_usart_tx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + furi_hal_gpio_init_ex( + &gpio_usart_rx, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn7USART1); + + LL_USART_InitTypeDef USART_InitStruct; + USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1; + USART_InitStruct.BaudRate = baud; + USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; + USART_InitStruct.StopBits = LL_USART_STOPBITS_1; + USART_InitStruct.Parity = LL_USART_PARITY_NONE; + USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; + USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; + USART_InitStruct.OverSampling = FURI_HAL_SERIAL_USART_OVERSAMPLING; + LL_USART_Init(USART1, &USART_InitStruct); + LL_USART_EnableFIFO(USART1); + LL_USART_ConfigAsyncMode(USART1); + + LL_USART_Enable(USART1); + + while(!LL_USART_IsActiveFlag_TEACK(USART1) || !LL_USART_IsActiveFlag_REACK(USART1)) + ; + + furi_hal_serial_set_br(handle, baud); + LL_USART_DisableIT_ERROR(USART1); + furi_hal_serial[handle->id].enabled = true; +} + +static void furi_hal_serial_lpuart_irq_callback(void* context) { + UNUSED(context); + + FuriHalSerialRxEvent event = 0; + // Notification flags + if(LPUART1->ISR & USART_ISR_RXNE_RXFNE) { + event |= FuriHalSerialRxEventData; + } + if(LPUART1->ISR & USART_ISR_IDLE) { + LPUART1->ICR = USART_ICR_IDLECF; + event |= FuriHalSerialRxEventIdle; + } + // Error flags + if(LPUART1->ISR & USART_ISR_ORE) { + LPUART1->ICR = USART_ICR_ORECF; + event |= FuriHalSerialRxEventOverrunError; + } + if(LPUART1->ISR & USART_ISR_NE) { + LPUART1->ICR = USART_ICR_NECF; + event |= FuriHalSerialRxEventNoiseError; + } + if(LPUART1->ISR & USART_ISR_FE) { + LPUART1->ICR = USART_ICR_FECF; + event |= FuriHalSerialRxEventFrameError; + } + if(LPUART1->ISR & USART_ISR_PE) { + LPUART1->ICR = USART_ICR_PECF; + event |= FuriHalSerialRxEventFrameError; + } + + if(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr == NULL) { + if(furi_hal_serial[FuriHalSerialIdLpuart].rx_byte_callback) { + furi_hal_serial[FuriHalSerialIdLpuart].rx_byte_callback( + furi_hal_serial[FuriHalSerialIdLpuart].handle, + event, + furi_hal_serial[FuriHalSerialIdLpuart].context); + } + } else { + if(furi_hal_serial[FuriHalSerialIdLpuart].rx_dma_callback) { + furi_hal_serial[FuriHalSerialIdLpuart].rx_dma_callback( + furi_hal_serial[FuriHalSerialIdLpuart].handle, + event, + furi_hal_serial_dma_bytes_available(FuriHalSerialIdLpuart), + furi_hal_serial[FuriHalSerialIdLpuart].context); + } + } +} + +static void furi_hal_serial_lpuart_dma_rx_isr(void* context) { + UNUSED(context); +#if FURI_HAL_SERIAL_LPUART_DMA_CHANNEL == LL_DMA_CHANNEL_7 + if(LL_DMA_IsActiveFlag_HT7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE)) { + LL_DMA_ClearFlag_HT7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE); + furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_index_write = + FURI_HAL_SERIAL_DMA_BUFFER_SIZE - + LL_DMA_GetDataLength( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + if((furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_index_read > + furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_index_write) || + (furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_index_read < + FURI_HAL_SERIAL_DMA_BUFFER_SIZE / 4)) { + if(furi_hal_serial[FuriHalSerialIdLpuart].rx_dma_callback) { + furi_hal_serial[FuriHalSerialIdLpuart].rx_dma_callback( + furi_hal_serial[FuriHalSerialIdLpuart].handle, + FuriHalSerialRxEventData, + furi_hal_serial_dma_bytes_available(FuriHalSerialIdLpuart), + furi_hal_serial[FuriHalSerialIdLpuart].context); + } + } + + } else if(LL_DMA_IsActiveFlag_TC7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE)) { + LL_DMA_ClearFlag_TC7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE); + + if(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_index_read < + FURI_HAL_SERIAL_DMA_BUFFER_SIZE * 3 / 4) { + if(furi_hal_serial[FuriHalSerialIdLpuart].rx_dma_callback) { + furi_hal_serial[FuriHalSerialIdLpuart].rx_dma_callback( + furi_hal_serial[FuriHalSerialIdLpuart].handle, + FuriHalSerialRxEventData, + furi_hal_serial_dma_bytes_available(FuriHalSerialIdLpuart), + furi_hal_serial[FuriHalSerialIdLpuart].context); + } + } + } +#else +#error Update this code. Would you kindly? +#endif +} + +static void furi_hal_serial_lpuart_init_dma_rx(void) { + /* LPUART1_RX_DMA Init */ + furi_check(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr == NULL); + furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_index_write = 0; + furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_index_read = 0; + furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr = malloc(FURI_HAL_SERIAL_DMA_BUFFER_SIZE); + LL_DMA_SetMemoryAddress( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, + FURI_HAL_SERIAL_LPUART_DMA_CHANNEL, + (uint32_t)furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr); + LL_DMA_SetPeriphAddress( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, + FURI_HAL_SERIAL_LPUART_DMA_CHANNEL, + (uint32_t) & (LPUART1->RDR)); + + LL_DMA_ConfigTransfer( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, + FURI_HAL_SERIAL_LPUART_DMA_CHANNEL, + LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | LL_DMA_MDATAALIGN_BYTE | + LL_DMA_PRIORITY_HIGH); + LL_DMA_SetDataLength( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, + FURI_HAL_SERIAL_LPUART_DMA_CHANNEL, + FURI_HAL_SERIAL_DMA_BUFFER_SIZE); + LL_DMA_SetPeriphRequest( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, + FURI_HAL_SERIAL_LPUART_DMA_CHANNEL, + LL_DMAMUX_REQ_LPUART1_RX); + + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch7, furi_hal_serial_lpuart_dma_rx_isr, NULL); + +#if FURI_HAL_SERIAL_LPUART_DMA_CHANNEL == LL_DMA_CHANNEL_7 + if(LL_DMA_IsActiveFlag_HT7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE)) + LL_DMA_ClearFlag_HT7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE); + if(LL_DMA_IsActiveFlag_TC7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE)) + LL_DMA_ClearFlag_TC7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE); + if(LL_DMA_IsActiveFlag_TE7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE)) + LL_DMA_ClearFlag_TE7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE); +#else +#error Update this code. Would you kindly? +#endif + + LL_DMA_EnableIT_TC(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + LL_DMA_EnableIT_HT(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + + LL_DMA_EnableChannel(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + LL_USART_EnableDMAReq_RX(LPUART1); + + LL_USART_EnableIT_IDLE(LPUART1); +} + +static void furi_hal_serial_lpuart_deinit_dma_rx(void) { + if(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr != NULL) { + LL_DMA_DisableChannel( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + LL_USART_DisableDMAReq_RX(LPUART1); + + LL_USART_DisableIT_IDLE(LPUART1); + LL_DMA_DisableIT_TC( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + LL_DMA_DisableIT_HT( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + + LL_DMA_ClearFlag_TC7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE); + LL_DMA_ClearFlag_HT7(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE); + + LL_DMA_DeInit(FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch7, NULL, NULL); + free(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr); + furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr = NULL; + } +} + +static void furi_hal_serial_lpuart_init(FuriHalSerialHandle* handle, uint32_t baud) { + furi_hal_bus_enable(FuriHalBusLPUART1); + LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); + + furi_hal_gpio_init_ex( + &gpio_ext_pc0, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn8LPUART1); + furi_hal_gpio_init_ex( + &gpio_ext_pc1, + GpioModeAltFunctionPushPull, + GpioPullUp, + GpioSpeedVeryHigh, + GpioAltFn8LPUART1); + + LL_LPUART_InitTypeDef LPUART_InitStruct; + LPUART_InitStruct.PrescalerValue = LL_LPUART_PRESCALER_DIV1; + LPUART_InitStruct.BaudRate = baud; + LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_8B; + LPUART_InitStruct.StopBits = LL_LPUART_STOPBITS_1; + LPUART_InitStruct.Parity = LL_LPUART_PARITY_NONE; + LPUART_InitStruct.TransferDirection = LL_LPUART_DIRECTION_TX_RX; + LPUART_InitStruct.HardwareFlowControl = LL_LPUART_HWCONTROL_NONE; + LL_LPUART_Init(LPUART1, &LPUART_InitStruct); + LL_LPUART_EnableFIFO(LPUART1); + + LL_LPUART_Enable(LPUART1); + + while(!LL_LPUART_IsActiveFlag_TEACK(LPUART1) || !LL_LPUART_IsActiveFlag_REACK(LPUART1)) + ; + + furi_hal_serial_set_br(handle, baud); + LL_LPUART_DisableIT_ERROR(LPUART1); + furi_hal_serial[handle->id].enabled = true; +} + +void furi_hal_serial_init(FuriHalSerialHandle* handle, uint32_t baud) { + furi_check(handle); + if(handle->id == FuriHalSerialIdLpuart) { + furi_hal_serial_lpuart_init(handle, baud); + } else if(handle->id == FuriHalSerialIdUsart) { + furi_hal_serial_usart_init(handle, baud); + } +} + +bool furi_hal_serial_is_baud_rate_supported(FuriHalSerialHandle* handle, uint32_t baud) { + furi_check(handle); + return baud >= 9600UL && baud <= 4000000UL; +} + +static uint32_t furi_hal_serial_get_prescaler(FuriHalSerialHandle* handle, uint32_t baud) { + uint32_t uartclk = LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE); + uint32_t divisor = (uartclk / baud); + uint32_t prescaler = 0; + if(handle->id == FuriHalSerialIdUsart) { + if(FURI_HAL_SERIAL_USART_OVERSAMPLING == LL_USART_OVERSAMPLING_16) { + divisor = (divisor / 16) >> 12; + } else { + divisor = (divisor / 8) >> 12; + } + if(divisor < 1) { + prescaler = LL_USART_PRESCALER_DIV1; + } else if(divisor < 2) { + prescaler = LL_USART_PRESCALER_DIV2; + } else if(divisor < 4) { + prescaler = LL_USART_PRESCALER_DIV4; + } else if(divisor < 6) { + prescaler = LL_USART_PRESCALER_DIV6; + } else if(divisor < 8) { + prescaler = LL_USART_PRESCALER_DIV8; + } else if(divisor < 10) { + prescaler = LL_USART_PRESCALER_DIV10; + } else if(divisor < 12) { + prescaler = LL_USART_PRESCALER_DIV12; + } else if(divisor < 16) { + prescaler = LL_USART_PRESCALER_DIV16; + } else if(divisor < 32) { + prescaler = LL_USART_PRESCALER_DIV32; + } else if(divisor < 64) { + prescaler = LL_USART_PRESCALER_DIV64; + } else if(divisor < 128) { + prescaler = LL_USART_PRESCALER_DIV128; + } else { + prescaler = LL_USART_PRESCALER_DIV256; + } + } else if(handle->id == FuriHalSerialIdLpuart) { + divisor >>= 12; + if(divisor < 1) { + prescaler = LL_LPUART_PRESCALER_DIV1; + } else if(divisor < 2) { + prescaler = LL_LPUART_PRESCALER_DIV2; + } else if(divisor < 4) { + prescaler = LL_LPUART_PRESCALER_DIV4; + } else if(divisor < 6) { + prescaler = LL_LPUART_PRESCALER_DIV6; + } else if(divisor < 8) { + prescaler = LL_LPUART_PRESCALER_DIV8; + } else if(divisor < 10) { + prescaler = LL_LPUART_PRESCALER_DIV10; + } else if(divisor < 12) { + prescaler = LL_LPUART_PRESCALER_DIV12; + } else if(divisor < 16) { + prescaler = LL_LPUART_PRESCALER_DIV16; + } else if(divisor < 32) { + prescaler = LL_LPUART_PRESCALER_DIV32; + } else if(divisor < 64) { + prescaler = LL_LPUART_PRESCALER_DIV64; + } else if(divisor < 128) { + prescaler = LL_LPUART_PRESCALER_DIV128; + } else { + prescaler = LL_LPUART_PRESCALER_DIV256; + } + } + + return prescaler; +} + +void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud) { + furi_check(handle); + uint32_t prescaler = furi_hal_serial_get_prescaler(handle, baud); + if(handle->id == FuriHalSerialIdUsart) { + if(LL_USART_IsEnabled(USART1)) { + // Wait for transfer complete flag + while(!LL_USART_IsActiveFlag_TC(USART1)) + ; + LL_USART_Disable(USART1); + uint32_t uartclk = LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE); + LL_USART_SetPrescaler(USART1, prescaler); + LL_USART_SetBaudRate( + USART1, uartclk, prescaler, FURI_HAL_SERIAL_USART_OVERSAMPLING, baud); + LL_USART_Enable(USART1); + } + } else if(handle->id == FuriHalSerialIdLpuart) { + if(LL_LPUART_IsEnabled(LPUART1)) { + // Wait for transfer complete flag + while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) + ; + LL_LPUART_Disable(LPUART1); + uint32_t uartclk = LL_RCC_GetLPUARTClockFreq(LL_RCC_LPUART1_CLKSOURCE); + LL_LPUART_SetPrescaler(LPUART1, prescaler); + LL_LPUART_SetBaudRate(LPUART1, uartclk, prescaler, baud); + LL_LPUART_Enable(LPUART1); + } + } +} + +void furi_hal_serial_deinit(FuriHalSerialHandle* handle) { + furi_check(handle); + furi_hal_serial_async_rx_configure(handle, NULL, NULL); + if(handle->id == FuriHalSerialIdUsart) { + if(furi_hal_bus_is_enabled(FuriHalBusUSART1)) { + furi_hal_bus_disable(FuriHalBusUSART1); + } + if(LL_USART_IsEnabled(USART1)) { + LL_USART_Disable(USART1); + } + furi_hal_serial_usart_deinit_dma_rx(); + furi_hal_gpio_init(&gpio_usart_tx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_usart_rx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else if(handle->id == FuriHalSerialIdLpuart) { + if(furi_hal_bus_is_enabled(FuriHalBusLPUART1)) { + furi_hal_bus_disable(FuriHalBusLPUART1); + } + if(LL_LPUART_IsEnabled(LPUART1)) { + LL_LPUART_Disable(LPUART1); + } + furi_hal_serial_lpuart_deinit_dma_rx(); + furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else { + furi_crash(); + } + furi_hal_serial[handle->id].enabled = false; +} + +void furi_hal_serial_suspend(FuriHalSerialHandle* handle) { + furi_check(handle); + if(handle->id == FuriHalSerialIdLpuart && LL_LPUART_IsEnabled(LPUART1)) { + LL_LPUART_Disable(LPUART1); + } else if(handle->id == FuriHalSerialIdUsart && LL_USART_IsEnabled(USART1)) { + LL_USART_Disable(USART1); + } + furi_hal_serial[handle->id].enabled = false; +} + +void furi_hal_serial_resume(FuriHalSerialHandle* handle) { + furi_check(handle); + if(!furi_hal_serial[handle->id].enabled) { + if(handle->id == FuriHalSerialIdLpuart) { + LL_LPUART_Enable(LPUART1); + } else if(handle->id == FuriHalSerialIdUsart) { + LL_USART_Enable(USART1); + } + furi_hal_serial[handle->id].enabled = true; + } +} + +void furi_hal_serial_tx(FuriHalSerialHandle* handle, const uint8_t* buffer, size_t buffer_size) { + furi_check(handle); + if(handle->id == FuriHalSerialIdUsart) { + if(LL_USART_IsEnabled(USART1) == 0) return; + + while(buffer_size > 0) { + while(!LL_USART_IsActiveFlag_TXE(USART1)) + ; + + LL_USART_TransmitData8(USART1, *buffer); + buffer++; + buffer_size--; + } + + } else if(handle->id == FuriHalSerialIdLpuart) { + if(LL_LPUART_IsEnabled(LPUART1) == 0) return; + + while(buffer_size > 0) { + while(!LL_LPUART_IsActiveFlag_TXE(LPUART1)) + ; + + LL_LPUART_TransmitData8(LPUART1, *buffer); + + buffer++; + buffer_size--; + } + } +} + +void furi_hal_serial_tx_wait_complete(FuriHalSerialHandle* handle) { + furi_check(handle); + if(handle->id == FuriHalSerialIdUsart) { + if(LL_USART_IsEnabled(USART1) == 0) return; + + while(!LL_USART_IsActiveFlag_TC(USART1)) + ; + } else if(handle->id == FuriHalSerialIdLpuart) { + if(LL_LPUART_IsEnabled(LPUART1) == 0) return; + + while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) + ; + } +} + +static void furi_hal_serial_event_init(FuriHalSerialHandle* handle, bool report_errors) { + if(handle->id == FuriHalSerialIdUsart) { + LL_USART_EnableIT_IDLE(USART1); + } else if(handle->id == FuriHalSerialIdLpuart) { + LL_LPUART_EnableIT_IDLE(LPUART1); + } + + if(report_errors) { + if(handle->id == FuriHalSerialIdUsart) { + LL_USART_EnableIT_ERROR(USART1); + } else if(handle->id == FuriHalSerialIdLpuart) { + LL_LPUART_EnableIT_ERROR(LPUART1); + } + } +} + +static void furi_hal_serial_event_deinit(FuriHalSerialHandle* handle) { + if(handle->id == FuriHalSerialIdUsart) { + if(LL_USART_IsEnabledIT_IDLE(USART1)) LL_USART_DisableIT_IDLE(USART1); + if(LL_USART_IsEnabledIT_ERROR(USART1)) LL_USART_DisableIT_ERROR(USART1); + } else if(handle->id == FuriHalSerialIdLpuart) { + if(LL_LPUART_IsEnabledIT_IDLE(LPUART1)) LL_LPUART_DisableIT_IDLE(LPUART1); + if(LL_LPUART_IsEnabledIT_ERROR(LPUART1)) LL_LPUART_DisableIT_ERROR(LPUART1); + } +} + +static void furi_hal_serial_async_rx_configure( + FuriHalSerialHandle* handle, + FuriHalSerialAsyncRxCallback callback, + void* context) { + if(handle->id == FuriHalSerialIdUsart) { + if(callback) { + furi_hal_serial_usart_deinit_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); + LL_USART_EnableIT_RXNE_RXFNE(USART1); + } else { + furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); + furi_hal_serial_usart_deinit_dma_rx(); + LL_USART_DisableIT_RXNE_RXFNE(USART1); + } + } else if(handle->id == FuriHalSerialIdLpuart) { + if(callback) { + furi_hal_serial_lpuart_deinit_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); + LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); + } else { + furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); + furi_hal_serial_lpuart_deinit_dma_rx(); + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + } + } + furi_hal_serial[handle->id].rx_byte_callback = callback; + furi_hal_serial[handle->id].handle = handle; + furi_hal_serial[handle->id].rx_dma_callback = NULL; + furi_hal_serial[handle->id].context = context; +} + +void furi_hal_serial_async_rx_start( + FuriHalSerialHandle* handle, + FuriHalSerialAsyncRxCallback callback, + void* context, + bool report_errors) { + furi_check(handle); + furi_check(callback); + + furi_hal_serial_event_init(handle, report_errors); + furi_hal_serial_async_rx_configure(handle, callback, context); + + // Assign different functions to different UARTs + furi_check( + furi_hal_serial[FuriHalSerialIdUsart].rx_byte_callback != + furi_hal_serial[FuriHalSerialIdLpuart].rx_byte_callback); +} + +void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle) { + furi_check(handle); + furi_hal_serial_event_deinit(handle); + furi_hal_serial_async_rx_configure(handle, NULL, NULL); +} + +uint8_t furi_hal_serial_async_rx(FuriHalSerialHandle* handle) { + furi_check(FURI_IS_IRQ_MODE()); + furi_assert(handle->id < FuriHalSerialIdMax); + + if(handle->id == FuriHalSerialIdUsart) { + return LL_USART_ReceiveData8(USART1); + } + return LL_LPUART_ReceiveData8(LPUART1); +} + +static size_t furi_hal_serial_dma_bytes_available(FuriHalSerialId ch) { + size_t dma_remain = 0; + if(ch == FuriHalSerialIdUsart) { + dma_remain = LL_DMA_GetDataLength( + FURI_HAL_SERIAL_USART_DMA_INSTANCE, FURI_HAL_SERIAL_USART_DMA_CHANNEL); + } else if(ch == FuriHalSerialIdLpuart) { + dma_remain = LL_DMA_GetDataLength( + FURI_HAL_SERIAL_LPUART_DMA_INSTANCE, FURI_HAL_SERIAL_LPUART_DMA_CHANNEL); + } else { + furi_crash(); + } + + furi_hal_serial[ch].buffer_rx_index_write = FURI_HAL_SERIAL_DMA_BUFFER_SIZE - dma_remain; + if(furi_hal_serial[ch].buffer_rx_index_write >= furi_hal_serial[ch].buffer_rx_index_read) { + return furi_hal_serial[ch].buffer_rx_index_write - + furi_hal_serial[ch].buffer_rx_index_read; + } else { + return FURI_HAL_SERIAL_DMA_BUFFER_SIZE - furi_hal_serial[ch].buffer_rx_index_read + + furi_hal_serial[ch].buffer_rx_index_write; + } +} + +static uint8_t furi_hal_serial_dma_rx_read_byte(FuriHalSerialHandle* handle) { + uint8_t data = 0; + data = + furi_hal_serial[handle->id].buffer_rx_ptr[furi_hal_serial[handle->id].buffer_rx_index_read]; + furi_hal_serial[handle->id].buffer_rx_index_read++; + if(furi_hal_serial[handle->id].buffer_rx_index_read >= FURI_HAL_SERIAL_DMA_BUFFER_SIZE) { + furi_hal_serial[handle->id].buffer_rx_index_read = 0; + } + return data; +} + +size_t furi_hal_serial_dma_rx(FuriHalSerialHandle* handle, uint8_t* data, size_t len) { + furi_check(FURI_IS_IRQ_MODE()); + furi_assert(furi_hal_serial[handle->id].buffer_rx_ptr != NULL); + size_t i = 0; + size_t available = furi_hal_serial_dma_bytes_available(handle->id); + if(available < len) { + len = available; + } + for(i = 0; i < len; i++) { + data[i] = furi_hal_serial_dma_rx_read_byte(handle); + } + return i; +} + +static void furi_hal_serial_dma_configure( + FuriHalSerialHandle* handle, + FuriHalSerialDmaRxCallback callback, + void* context) { + furi_check(handle); + + if(handle->id == FuriHalSerialIdUsart) { + if(callback) { + furi_hal_serial_usart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); + } else { + LL_USART_DisableIT_RXNE_RXFNE(USART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); + furi_hal_serial_usart_deinit_dma_rx(); + } + } else if(handle->id == FuriHalSerialIdLpuart) { + if(callback) { + furi_hal_serial_lpuart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); + } else { + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); + furi_hal_serial_lpuart_deinit_dma_rx(); + } + } + furi_hal_serial[handle->id].rx_byte_callback = NULL; + furi_hal_serial[handle->id].handle = handle; + furi_hal_serial[handle->id].rx_dma_callback = callback; + furi_hal_serial[handle->id].context = context; +} + +void furi_hal_serial_dma_rx_start( + FuriHalSerialHandle* handle, + FuriHalSerialDmaRxCallback callback, + void* context, + bool report_errors) { + furi_check(handle); + furi_check(callback); + + furi_hal_serial_event_init(handle, report_errors); + furi_hal_serial_dma_configure(handle, callback, context); + + // Assign different functions to different UARTs + furi_check( + furi_hal_serial[FuriHalSerialIdUsart].rx_dma_callback != + furi_hal_serial[FuriHalSerialIdLpuart].rx_dma_callback); +} + +void furi_hal_serial_dma_rx_stop(FuriHalSerialHandle* handle) { + furi_check(handle); + furi_hal_serial_event_deinit(handle); + furi_hal_serial_dma_configure(handle, NULL, NULL); +} + +void furi_hal_serial_enable_direction( + FuriHalSerialHandle* handle, + FuriHalSerialDirection direction) { + furi_check(handle); + furi_check(handle->id < FuriHalSerialIdMax); + furi_check(direction < FuriHalSerialDirectionMax); + + USART_TypeDef* periph = furi_hal_serial_config[handle->id].periph; + furi_hal_serial_config[handle->id].enable[direction](periph); + + const GpioPin* gpio = furi_hal_serial_config[handle->id].gpio[direction]; + const GpioAltFn alt_fn = furi_hal_serial_config[handle->id].alt_fn; + + furi_hal_gpio_init_ex( + gpio, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, alt_fn); +} + +void furi_hal_serial_disable_direction( + FuriHalSerialHandle* handle, + FuriHalSerialDirection direction) { + furi_check(handle); + furi_check(handle->id < FuriHalSerialIdMax); + furi_check(direction < FuriHalSerialDirectionMax); + + USART_TypeDef* periph = furi_hal_serial_config[handle->id].periph; + furi_hal_serial_config[handle->id].disable[direction](periph); + + const GpioPin* gpio = furi_hal_serial_config[handle->id].gpio[direction]; + + furi_hal_gpio_init(gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +const GpioPin* + furi_hal_serial_get_gpio_pin(FuriHalSerialHandle* handle, FuriHalSerialDirection direction) { + furi_check(handle); + furi_check(handle->id < FuriHalSerialIdMax); + furi_check(direction < FuriHalSerialDirectionMax); + + return furi_hal_serial_config[handle->id].gpio[direction]; +} diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h new file mode 100644 index 00000000000..975406670fb --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_serial.h @@ -0,0 +1,234 @@ +/** + * @file furi_hal_serial.h + * + * Serial HAL API + */ +#pragma once + +#include +#include + +#include "furi_hal_serial_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Initialize Serial + * + * Configures GPIO, configures and enables transceiver. + * + * @param handle Serial handle + * @param baud baud rate + */ +void furi_hal_serial_init(FuriHalSerialHandle* handle, uint32_t baud); + +/** De-initialize Serial + * + * Configures GPIO to analog, clears callback and callback context, disables + * hardware + * + * @param handle Serial handle + */ +void furi_hal_serial_deinit(FuriHalSerialHandle* handle); + +/** Suspend operation + * + * Suspend hardware, settings and callbacks are preserved + * + * @param handle Serial handle + */ +void furi_hal_serial_suspend(FuriHalSerialHandle* handle); + +/** Resume operation + * + * Resumes hardware from suspended state + * + * @param handle Serial handle + */ +void furi_hal_serial_resume(FuriHalSerialHandle* handle); + +/** + * @brief Determine whether a certain baud rate is supported + * + * @param handle Serial handle + * @param baud baud rate to be checked + * @returns true if baud rate is supported, false otherwise. + */ +bool furi_hal_serial_is_baud_rate_supported(FuriHalSerialHandle* handle, uint32_t baud); + +/** Changes baud rate + * + * @param handle Serial handle + * @param baud baud rate + */ +void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud); + +/** Transmits data in semi-blocking mode + * + * Fills transmission pipe with data, returns as soon as all bytes from buffer + * are in the pipe. + * + * Real transmission will be completed later. Use + * `furi_hal_serial_tx_wait_complete` to wait for completion if you need it. + * + * @param handle Serial handle + * @param buffer data + * @param buffer_size data size (in bytes) + */ +void furi_hal_serial_tx(FuriHalSerialHandle* handle, const uint8_t* buffer, size_t buffer_size); + +/** Wait until transmission is completed + * + * Ensures that all data has been sent. + * + * @param handle Serial handle + */ +void furi_hal_serial_tx_wait_complete(FuriHalSerialHandle* handle); + +/** Serial RX events */ +typedef enum { + FuriHalSerialRxEventData = (1 << 0), /**< Data: new data available */ + FuriHalSerialRxEventIdle = (1 << 1), /**< Idle: bus idle detected */ + FuriHalSerialRxEventFrameError = (1 << 2), /**< Framing Error: incorrect frame detected */ + FuriHalSerialRxEventNoiseError = (1 << 3), /**< Noise Error: noise on the line detected */ + FuriHalSerialRxEventOverrunError = (1 << 4), /**< Overrun Error: no space for received data */ +} FuriHalSerialRxEvent; + +/** Receive callback + * + * @warning Callback will be called in interrupt context, ensure thread + * safety on your side. + * @param handle Serial handle + * @param event FuriHalSerialRxEvent + * @param context Callback context provided earlier + */ +typedef void (*FuriHalSerialAsyncRxCallback)( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + void* context); + +/** Start and sets Serial Receive callback + * + * @warning Callback will be called in interrupt context, ensure thread + * safety on your side + * + * @param handle Serial handle + * @param callback callback pointer + * @param context callback context + * @param[in] report_errors report RX error + */ +void furi_hal_serial_async_rx_start( + FuriHalSerialHandle* handle, + FuriHalSerialAsyncRxCallback callback, + void* context, + bool report_errors); + +/** Stop Serial Receive + * + * @param handle Serial handle + */ +void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle); + +/** Get data Serial receive + * + * @warning This function must be called only from the callback + * FuriHalSerialAsyncRxCallback + * + * @param handle Serial handle + * + * @return data + */ +uint8_t furi_hal_serial_async_rx(FuriHalSerialHandle* handle); + +/* DMA based Serial API */ + +#define FURI_HAL_SERIAL_DMA_BUFFER_SIZE (256u) + +/** Receive DMA callback + * + * @warning DMA Callback will be called in interrupt context, ensure thread + * safety on your side. + * + * @param handle Serial handle + * @param event FuriHalSerialDmaRxEvent + * @param data_len Received data + * @param context Callback context provided earlier + */ +typedef void (*FuriHalSerialDmaRxCallback)( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + size_t data_len, + void* context); + +/** + * @brief Enable an input/output directon + * + * Takes over the respective pin by reconfiguring it to + * the appropriate alternative function. + * + * @param handle Serial handle + * @param direction Direction to enable + */ +void furi_hal_serial_enable_direction( + FuriHalSerialHandle* handle, + FuriHalSerialDirection direction); + +/** + * @brief Disable an input/output directon + * + * Releases the respective pin by reconfiguring it to + * initial state, making possible its use for other purposes. + * + * @param handle Serial handle + * @param direction Direction to disable + */ +void furi_hal_serial_disable_direction( + FuriHalSerialHandle* handle, + FuriHalSerialDirection direction); + +/** + * @brief Get the GPIO pin associated with a serial + * + * @param handle Serial handle + * @param direction Direction to query + * @returns pointer to the respective pin instance + */ +const GpioPin* + furi_hal_serial_get_gpio_pin(FuriHalSerialHandle* handle, FuriHalSerialDirection direction); + +/** Start and sets Serial event callback receive DMA + * + * @param handle Serial handle + * @param callback callback pointer + * @param context callback context + * @param[in] report_errors report RX error + */ +void furi_hal_serial_dma_rx_start( + FuriHalSerialHandle* handle, + FuriHalSerialDmaRxCallback callback, + void* context, + bool report_errors); + +/** Stop Serial receive DMA + * + * @param handle Serial handle + */ +void furi_hal_serial_dma_rx_stop(FuriHalSerialHandle* handle); + +/** Get data Serial receive DMA + * + * @warning This function must be called only from the callback + * FuriHalSerialDmaRxCallback + * + * @param handle Serial handle + * @param data pointer to data buffer + * @param len get data size (in bytes) + * + * @return size actual data receive (in bytes) + */ +size_t furi_hal_serial_dma_rx(FuriHalSerialHandle* handle, uint8_t* data, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/furi_hal/furi_hal_serial_control.c b/targets/f7/furi_hal/furi_hal_serial_control.c new file mode 100644 index 00000000000..37454823bcc --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_serial_control.c @@ -0,0 +1,360 @@ +#include "furi_hal_serial_control.h" +#include "furi_hal_serial_types_i.h" +#include "furi_hal_serial.h" + +#include +#include + +#define TAG "FuriHalSerialControl" + +typedef enum { + FuriHalSerialControlMessageTypeStop, + FuriHalSerialControlMessageTypeAcquire, + FuriHalSerialControlMessageTypeRelease, + FuriHalSerialControlMessageTypeIsBusy, + FuriHalSerialControlMessageTypeLogging, + FuriHalSerialControlMessageTypeExpansionSetCallback, + FuriHalSerialControlMessageTypeExpansionIrq, +} FuriHalSerialControlMessageType; + +typedef struct { + FuriHalSerialControlMessageType type; + FuriApiLock api_lock; + void* input; + void* output; +} FuriHalSerialControlMessage; + +typedef struct { + const FuriHalSerialId id; + const uint32_t baud_rate; +} FuriHalSerialControlMessageInputLogging; + +typedef struct { + const FuriHalSerialId id; + const FuriHalSerialControlExpansionCallback callback; + void* context; +} FuriHalSerialControlMessageExpCallback; + +typedef struct { + FuriHalSerialHandle handles[FuriHalSerialIdMax]; + FuriMessageQueue* queue; + FuriThread* thread; + + // Logging + FuriHalSerialId log_config_serial_id; + uint32_t log_config_serial_baud_rate; + FuriLogHandler log_handler; + FuriHalSerialHandle* log_serial; + + // Expansion detection + FuriHalSerialControlExpansionCallback expansion_cb; + void* expansion_ctx; +} FuriHalSerialControl; + +FuriHalSerialControl* furi_hal_serial_control = NULL; + +static void furi_hal_serial_control_log_callback(const uint8_t* data, size_t size, void* context) { + FuriHalSerialHandle* handle = context; + furi_hal_serial_tx(handle, data, size); +} + +static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) { + if(furi_hal_serial_control->log_serial) { + furi_log_remove_handler(furi_hal_serial_control->log_handler); + furi_hal_serial_deinit(furi_hal_serial_control->log_serial); + furi_hal_serial_control->log_serial = NULL; + } + + if(handle) { + furi_hal_serial_control->log_serial = handle; + furi_hal_serial_init( + furi_hal_serial_control->log_serial, + furi_hal_serial_control->log_config_serial_baud_rate); + furi_hal_serial_control->log_handler.callback = furi_hal_serial_control_log_callback; + furi_hal_serial_control->log_handler.context = furi_hal_serial_control->log_serial; + furi_log_add_handler(furi_hal_serial_control->log_handler); + } +} + +static void furi_hal_serial_control_expansion_irq_callback(void* context) { + UNUSED(context); + + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeExpansionIrq; + message.api_lock = NULL; + furi_message_queue_put(furi_hal_serial_control->queue, &message, 0); +} + +static bool furi_hal_serial_control_handler_stop(void* input, void* output) { + UNUSED(input); + UNUSED(output); + return false; +} + +static bool furi_hal_serial_control_handler_acquire(void* input, void* output) { + FuriHalSerialId serial_id = *(FuriHalSerialId*)input; + if(furi_hal_serial_control->handles[serial_id].in_use) { + *(FuriHalSerialHandle**)output = NULL; + } else { + // Logging + if(furi_hal_serial_control->log_config_serial_id == serial_id) { + furi_hal_serial_control_log_set_handle(NULL); + } + // Return handle + furi_hal_serial_control->handles[serial_id].in_use = true; + *(FuriHalSerialHandle**)output = &furi_hal_serial_control->handles[serial_id]; + } + + return true; +} + +static bool furi_hal_serial_control_handler_release(void* input, void* output) { + UNUSED(output); + + FuriHalSerialHandle* handle = *(FuriHalSerialHandle**)input; + furi_assert(handle->in_use); + furi_hal_serial_deinit(handle); + handle->in_use = false; + + // Return back logging + if(furi_hal_serial_control->log_config_serial_id == handle->id) { + furi_hal_serial_control_log_set_handle(handle); + } + + return true; +} + +static bool furi_hal_serial_control_handler_is_busy(void* input, void* output) { + FuriHalSerialId serial_id = *(FuriHalSerialId*)input; + *(bool*)output = furi_hal_serial_control->handles[serial_id].in_use; + + return true; +} + +static bool furi_hal_serial_control_handler_logging(void* input, void* output) { + UNUSED(output); + + // Set new configuration + FuriHalSerialControlMessageInputLogging* message_input = input; + furi_hal_serial_control->log_config_serial_id = message_input->id; + furi_hal_serial_control->log_config_serial_baud_rate = message_input->baud_rate; + // Apply new configuration + FuriHalSerialHandle* handle = NULL; + if(furi_hal_serial_control->log_config_serial_id < FuriHalSerialIdMax) { + if(!furi_hal_serial_control->handles[furi_hal_serial_control->log_config_serial_id].in_use) { + handle = + &furi_hal_serial_control->handles[furi_hal_serial_control->log_config_serial_id]; + } + } + + furi_hal_serial_control_log_set_handle(handle); + + return true; +} + +static bool furi_hal_serial_control_handler_expansion_set_callback(void* input, void* output) { + UNUSED(output); + + FuriHalSerialControlMessageExpCallback* message_input = input; + FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[message_input->id]; + const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx); + + if(message_input->callback) { + furi_check(furi_hal_serial_control->expansion_cb == NULL); + + furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx); + furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL); + furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow); + } else { + furi_check(furi_hal_serial_control->expansion_cb != NULL); + + furi_hal_gpio_remove_int_callback(gpio); + furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx); + } + + furi_hal_serial_control->expansion_cb = message_input->callback; + furi_hal_serial_control->expansion_ctx = message_input->context; + + return true; +} + +static bool furi_hal_serial_control_handler_expansion_irq(void* input, void* output) { + UNUSED(input); + UNUSED(output); + + if(furi_hal_serial_control->expansion_cb) { + void* context = furi_hal_serial_control->expansion_ctx; + furi_hal_serial_control->expansion_cb(context); + } + + return true; +} + +typedef bool (*FuriHalSerialControlCommandHandler)(void* input, void* output); + +static const FuriHalSerialControlCommandHandler furi_hal_serial_control_handlers[] = { + [FuriHalSerialControlMessageTypeStop] = furi_hal_serial_control_handler_stop, + [FuriHalSerialControlMessageTypeAcquire] = furi_hal_serial_control_handler_acquire, + [FuriHalSerialControlMessageTypeRelease] = furi_hal_serial_control_handler_release, + [FuriHalSerialControlMessageTypeIsBusy] = furi_hal_serial_control_handler_is_busy, + [FuriHalSerialControlMessageTypeLogging] = furi_hal_serial_control_handler_logging, + [FuriHalSerialControlMessageTypeExpansionSetCallback] = + furi_hal_serial_control_handler_expansion_set_callback, + [FuriHalSerialControlMessageTypeExpansionIrq] = furi_hal_serial_control_handler_expansion_irq, +}; + +static int32_t furi_hal_serial_control_thread(void* args) { + UNUSED(args); + + bool should_continue = true; + while(should_continue || furi_message_queue_get_count(furi_hal_serial_control->queue) > 0) { + FuriHalSerialControlMessage message = {0}; + FuriStatus status = + furi_message_queue_get(furi_hal_serial_control->queue, &message, FuriWaitForever); + furi_check(status == FuriStatusOk); + furi_check(message.type < COUNT_OF(furi_hal_serial_control_handlers)); + + should_continue = + furi_hal_serial_control_handlers[message.type](message.input, message.output); + + if(message.api_lock != NULL) { + api_lock_unlock(message.api_lock); + } + } + + return 0; +} + +void furi_hal_serial_control_init(void) { + furi_check(furi_hal_serial_control == NULL); + // Allocate resources + furi_hal_serial_control = malloc(sizeof(FuriHalSerialControl)); + furi_hal_serial_control->handles[FuriHalSerialIdUsart].id = FuriHalSerialIdUsart; + furi_hal_serial_control->handles[FuriHalSerialIdLpuart].id = FuriHalSerialIdLpuart; + furi_hal_serial_control->queue = + furi_message_queue_alloc(8, sizeof(FuriHalSerialControlMessage)); + furi_hal_serial_control->thread = + furi_thread_alloc_ex("SerialControlDriver", 512, furi_hal_serial_control_thread, NULL); + furi_thread_mark_as_service(furi_hal_serial_control->thread); + furi_thread_set_priority(furi_hal_serial_control->thread, FuriThreadPriorityHighest); + furi_hal_serial_control->log_config_serial_id = FuriHalSerialIdMax; + // Start control plane thread + furi_thread_start(furi_hal_serial_control->thread); +} + +void furi_hal_serial_control_deinit(void) { + furi_check(furi_hal_serial_control); + // Stop control plane thread + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeStop; + message.api_lock = NULL; + furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever); + furi_thread_join(furi_hal_serial_control->thread); + // Release resources + furi_thread_free(furi_hal_serial_control->thread); + furi_message_queue_free(furi_hal_serial_control->queue); + free(furi_hal_serial_control); +} + +void furi_hal_serial_control_suspend(void) { + furi_check(furi_hal_serial_control); + + for(size_t i = 0; i < FuriHalSerialIdMax; i++) { + furi_hal_serial_tx_wait_complete(&furi_hal_serial_control->handles[i]); + furi_hal_serial_suspend(&furi_hal_serial_control->handles[i]); + } +} + +void furi_hal_serial_control_resume(void) { + furi_check(furi_hal_serial_control); + + for(size_t i = 0; i < FuriHalSerialIdMax; i++) { + furi_hal_serial_resume(&furi_hal_serial_control->handles[i]); + } +} + +FuriHalSerialHandle* furi_hal_serial_control_acquire(FuriHalSerialId serial_id) { + furi_check(furi_hal_serial_control); + + FuriHalSerialHandle* output = NULL; + + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeAcquire; + message.api_lock = api_lock_alloc_locked(); + message.input = &serial_id; + message.output = &output; + furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + + return output; +} + +void furi_hal_serial_control_release(FuriHalSerialHandle* handle) { + furi_check(furi_hal_serial_control); + furi_check(handle); + + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeRelease; + message.api_lock = api_lock_alloc_locked(); + message.input = &handle; + furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); +} + +bool furi_hal_serial_control_is_busy(FuriHalSerialId serial_id) { + furi_check(furi_hal_serial_control); + + bool result = false; + + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeIsBusy; + message.api_lock = api_lock_alloc_locked(); + message.input = &serial_id; + message.output = &result; + furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + + return result; +} + +void furi_hal_serial_control_set_logging_config(FuriHalSerialId serial_id, uint32_t baud_rate) { + furi_check(serial_id <= FuriHalSerialIdMax); + furi_check(baud_rate >= 9600 && baud_rate <= 4000000); + + // Very special case of updater, where RTC initialized before kernel start + if(!furi_hal_serial_control) return; + + furi_check(furi_hal_serial_is_baud_rate_supported( + &furi_hal_serial_control->handles[serial_id], baud_rate)); + + FuriHalSerialControlMessageInputLogging message_input = { + .id = serial_id, + .baud_rate = baud_rate, + }; + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeLogging; + message.api_lock = api_lock_alloc_locked(); + message.input = &message_input; + furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); +} + +void furi_hal_serial_control_set_expansion_callback( + FuriHalSerialId serial_id, + FuriHalSerialControlExpansionCallback callback, + void* context) { + furi_check(serial_id <= FuriHalSerialIdMax); + furi_check(furi_hal_serial_control); + + FuriHalSerialControlMessageExpCallback message_input = { + .id = serial_id, + .callback = callback, + .context = context, + }; + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeExpansionSetCallback; + message.api_lock = api_lock_alloc_locked(); + message.input = &message_input; + furi_message_queue_put(furi_hal_serial_control->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); +} diff --git a/targets/f7/furi_hal/furi_hal_serial_control.h b/targets/f7/furi_hal/furi_hal_serial_control.h new file mode 100644 index 00000000000..463f431815e --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_serial_control.h @@ -0,0 +1,83 @@ +#pragma once + +#include "furi_hal_serial_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Initialize Serial Control */ +void furi_hal_serial_control_init(void); + +/** De-Initialize Serial Control */ +void furi_hal_serial_control_deinit(void); + +/** Suspend All Serial Interfaces + * + * @warning this is internal method, can only be used in suppress tick + * callback + */ +void furi_hal_serial_control_suspend(void); + +/** Resume All Serial Interfaces + * + * @warning this is internal method, can only be used in suppress tick + * callback + */ +void furi_hal_serial_control_resume(void); + +/** Acquire Serial Interface Handler + * + * @param[in] serial_id The serial transceiver identifier + * + * @return The Serial Interface Handle or null if interfaces is in use + */ +FuriHalSerialHandle* furi_hal_serial_control_acquire(FuriHalSerialId serial_id); + +/** Release Serial Interface Handler + * + * @param handle The handle + */ +void furi_hal_serial_control_release(FuriHalSerialHandle* handle); + +/** Acquire Serial Interface Handler + * + * @param[in] serial_id The serial transceiver identifier + * + * @return true if handle is acquired by someone + */ +bool furi_hal_serial_control_is_busy(FuriHalSerialId serial_id); + +/** Acquire Serial Interface Handler + * + * @param[in] serial_id The serial transceiver identifier. Use FuriHalSerialIdMax to disable logging. + * @param[in] baud_rate The baud rate + * + * @return The Serial Interface Handle or null if interfaces is in use + */ +void furi_hal_serial_control_set_logging_config(FuriHalSerialId serial_id, uint32_t baud_rate); + +/** + * @brief Expansion module detection callback type. + * + * @param[in,out] context Pointer to the user-defined context object. + */ +typedef void (*FuriHalSerialControlExpansionCallback)(void* context); + +/** + * @brief Enable expansion module detection for a given serial interface. + * + * Passing NULL as the callback parameter disables external module detection. + * + * @param[in] serial_id Identifier of the serial interface to be used. + * @param[in] callback Pointer to the callback function to be called upon module detection. + * @param[in,out] context Pointer to the user-defined context object. Will be passed to the callback function. + */ +void furi_hal_serial_control_set_expansion_callback( + FuriHalSerialId serial_id, + FuriHalSerialControlExpansionCallback callback, + void* context); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/furi_hal/furi_hal_serial_types.h b/targets/f7/furi_hal/furi_hal_serial_types.h new file mode 100644 index 00000000000..9f10102e10a --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_serial_types.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +/** + * UART channels + */ +typedef enum { + FuriHalSerialIdUsart, + FuriHalSerialIdLpuart, + + FuriHalSerialIdMax, +} FuriHalSerialId; + +typedef enum { + FuriHalSerialDirectionTx, + FuriHalSerialDirectionRx, + + FuriHalSerialDirectionMax, +} FuriHalSerialDirection; + +typedef struct FuriHalSerialHandle FuriHalSerialHandle; diff --git a/targets/f7/furi_hal/furi_hal_serial_types_i.h b/targets/f7/furi_hal/furi_hal_serial_types_i.h new file mode 100644 index 00000000000..9528e35eb0f --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_serial_types_i.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +struct FuriHalSerialHandle { + FuriHalSerialId id; + bool in_use; +}; diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index a00ca7bf6d4..e73a325aabc 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -17,13 +17,13 @@ #define TAG "FuriHalSubGhz" -static uint32_t furi_hal_subghz_debug_gpio_buff[2]; +static uint32_t furi_hal_subghz_debug_gpio_buff[2] = {0}; /* DMA Channels definition */ -#define SUBGHZ_DMA DMA2 -#define SUBGHZ_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1 -#define SUBGHZ_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2 -#define SUBGHZ_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1 +#define SUBGHZ_DMA (DMA2) +#define SUBGHZ_DMA_CH1_CHANNEL (LL_DMA_CHANNEL_1) +#define SUBGHZ_DMA_CH2_CHANNEL (LL_DMA_CHANNEL_2) +#define SUBGHZ_DMA_CH1_IRQ (FuriHalInterruptIdDma2Ch1) #define SUBGHZ_DMA_CH1_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH1_CHANNEL #define SUBGHZ_DMA_CH2_DEF SUBGHZ_DMA, SUBGHZ_DMA_CH2_CHANNEL @@ -36,8 +36,6 @@ typedef enum { SubGhzStateAsyncRx, /**< Async RX started */ SubGhzStateAsyncTx, /**< Async TX started, DMA and timer is on */ - SubGhzStateAsyncTxLast, /**< Async TX continue, DMA completed and timer got last value to go */ - SubGhzStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ } SubGhzState; @@ -79,6 +77,10 @@ void furi_hal_subghz_init() { &FURI_HAL_SUBGHZ_TX_GPIO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); #endif +#ifdef FURI_HAL_SUBGHZ_ASYNC_MIRROR_GPIO + furi_hal_subghz_set_async_mirror_pin(&FURI_HAL_SUBGHZ_ASYNC_MIRROR_GPIO); +#endif + // Reset furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); cc1101_reset(&furi_hal_spi_bus_handle_subghz); @@ -158,8 +160,8 @@ void furi_hal_subghz_dump_state() { void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data) { //load config + furi_hal_subghz_reset(); furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); uint32_t i = 0; uint8_t pa[8] = {0}; while(preset_data[i]) { @@ -187,8 +189,8 @@ void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data) { } void furi_hal_subghz_load_registers(const uint8_t* data) { + furi_hal_subghz_reset(); furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); - cc1101_reset(&furi_hal_spi_bus_handle_subghz); uint32_t i = 0; while(data[i]) { cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i], data[i + 1]); @@ -266,6 +268,7 @@ void furi_hal_subghz_reset() { furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); cc1101_reset(&furi_hal_spi_bus_handle_subghz); + // Warning: push pull cc1101 clock output on GD0 cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHighImpedance); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } @@ -273,12 +276,16 @@ void furi_hal_subghz_reset() { void furi_hal_subghz_idle() { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + //waiting for the chip to switch to IDLE mode + furi_check(cc1101_wait_status_state(&furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } void furi_hal_subghz_rx() { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + //waiting for the chip to switch to Rx mode + furi_check(cc1101_wait_status_state(&furi_hal_spi_bus_handle_subghz, CC1101StateRX, 10000)); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } @@ -286,6 +293,8 @@ bool furi_hal_subghz_tx() { if(furi_hal_subghz.regulation != SubGhzRegulationTxRx) return false; furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); cc1101_switch_to_tx(&furi_hal_spi_bus_handle_subghz); + //waiting for the chip to switch to Tx mode + furi_check(cc1101_wait_status_state(&furi_hal_spi_bus_handle_subghz, CC1101StateTX, 10000)); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); return true; } @@ -348,10 +357,7 @@ uint32_t furi_hal_subghz_set_frequency(uint32_t value) { uint32_t real_frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, value); cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); - while(true) { - CC1101Status status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); - if(status.STATE == CC1101StateIDLE) break; - } + furi_check(cc1101_wait_status_state(&furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); return real_frequency; @@ -382,6 +388,7 @@ void furi_hal_subghz_set_path(FuriHalSubGhzPath path) { static bool furi_hal_subghz_start_debug() { bool ret = false; if(furi_hal_subghz.async_mirror_pin != NULL) { + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false); furi_hal_gpio_init( furi_hal_subghz.async_mirror_pin, GpioModeOutputPushPull, @@ -522,73 +529,120 @@ void furi_hal_subghz_stop_async_rx() { furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } +typedef enum { + FuriHalSubGhzAsyncTxMiddlewareStateIdle, + FuriHalSubGhzAsyncTxMiddlewareStateReset, + FuriHalSubGhzAsyncTxMiddlewareStateRun, +} FuriHalSubGhzAsyncTxMiddlewareState; + +typedef struct { + FuriHalSubGhzAsyncTxMiddlewareState state; + bool is_odd_level; + uint32_t adder_duration; +} FuriHalSubGhzAsyncTxMiddleware; + typedef struct { uint32_t* buffer; - LevelDuration carry_ld; FuriHalSubGhzAsyncTxCallback callback; void* callback_context; uint64_t duty_high; uint64_t duty_low; + FuriHalSubGhzAsyncTxMiddleware middleware; } FuriHalSubGhzAsyncTx; static FuriHalSubGhzAsyncTx furi_hal_subghz_async_tx = {0}; +void furi_hal_subghz_async_tx_middleware_idle(FuriHalSubGhzAsyncTxMiddleware* middleware) { + middleware->state = FuriHalSubGhzAsyncTxMiddlewareStateIdle; + middleware->is_odd_level = false; + middleware->adder_duration = 0; +} + +static inline uint32_t furi_hal_subghz_async_tx_middleware_get_duration( + FuriHalSubGhzAsyncTxMiddleware* middleware, + FuriHalSubGhzAsyncTxCallback callback) { + uint32_t ret = 0; + bool is_level = false; + + if(middleware->state == FuriHalSubGhzAsyncTxMiddlewareStateReset) return 0; + + while(1) { + LevelDuration ld = callback(furi_hal_subghz_async_tx.callback_context); + if(level_duration_is_reset(ld)) { + middleware->state = FuriHalSubGhzAsyncTxMiddlewareStateReset; + if(!middleware->is_odd_level) { + return 0; + } else { + return middleware->adder_duration; + } + } else if(level_duration_is_wait(ld)) { + middleware->is_odd_level = !middleware->is_odd_level; + ret = middleware->adder_duration + FURI_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; + middleware->adder_duration = 0; + return ret; + } + + is_level = level_duration_get_level(ld); + + if(middleware->state == FuriHalSubGhzAsyncTxMiddlewareStateIdle) { + if(is_level != middleware->is_odd_level) { + middleware->state = FuriHalSubGhzAsyncTxMiddlewareStateRun; + middleware->is_odd_level = is_level; + middleware->adder_duration = 0; + } else { + continue; + } + } + + if(middleware->state == FuriHalSubGhzAsyncTxMiddlewareStateRun) { + if(is_level == middleware->is_odd_level) { + middleware->adder_duration += level_duration_get_duration(ld); + continue; + } else { + middleware->is_odd_level = is_level; + ret = middleware->adder_duration; + middleware->adder_duration = level_duration_get_duration(ld); + return ret; + } + } + } +} + static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); - while(samples > 0) { - bool is_odd = samples % 2; - LevelDuration ld; - if(level_duration_is_reset(furi_hal_subghz_async_tx.carry_ld)) { - ld = furi_hal_subghz_async_tx.callback(furi_hal_subghz_async_tx.callback_context); - } else { - ld = furi_hal_subghz_async_tx.carry_ld; - furi_hal_subghz_async_tx.carry_ld = level_duration_reset(); - } - if(level_duration_is_wait(ld)) { - *buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; - buffer++; - samples--; - } else if(level_duration_is_reset(ld)) { + while(samples > 0) { + volatile uint32_t duration = furi_hal_subghz_async_tx_middleware_get_duration( + &furi_hal_subghz_async_tx.middleware, furi_hal_subghz_async_tx.callback); + if(duration == 0) { *buffer = 0; buffer++; samples--; LL_DMA_DisableIT_HT(SUBGHZ_DMA_CH1_DEF); LL_DMA_DisableIT_TC(SUBGHZ_DMA_CH1_DEF); - LL_TIM_EnableIT_UPDATE(TIM2); + if(LL_DMA_IsActiveFlag_HT1(SUBGHZ_DMA)) { + LL_DMA_ClearFlag_HT1(SUBGHZ_DMA); + } + if(LL_DMA_IsActiveFlag_TC1(SUBGHZ_DMA)) { + LL_DMA_ClearFlag_TC1(SUBGHZ_DMA); + } break; } else { - bool level = level_duration_get_level(ld); - - // Inject guard time if level is incorrect - if(is_odd != level) { - *buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; - buffer++; - samples--; - if(is_odd) { - furi_hal_subghz_async_tx.duty_high += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; - } else { - furi_hal_subghz_async_tx.duty_low += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; - } - - // Special case: prevent buffer overflow if sample is last - if(samples == 0) { - furi_hal_subghz_async_tx.carry_ld = ld; - break; - } + // Lowest possible value is 2us + if(duration > 2) { + // Subtract 1 since we counting from 0 + *buffer = duration - 1; + } else { + *buffer = 1; } - - uint32_t duration = level_duration_get_duration(ld); - furi_assert(duration > 0); - *buffer = duration; buffer++; samples--; + } - if(is_odd) { - furi_hal_subghz_async_tx.duty_high += duration; - } else { - furi_hal_subghz_async_tx.duty_low += duration; - } + if(samples % 2) { + furi_hal_subghz_async_tx.duty_high += duration; + } else { + furi_hal_subghz_async_tx.duty_low += duration; } } } @@ -600,38 +654,19 @@ static void furi_hal_subghz_async_tx_dma_isr() { if(LL_DMA_IsActiveFlag_HT1(SUBGHZ_DMA)) { LL_DMA_ClearFlag_HT1(SUBGHZ_DMA); furi_hal_subghz_async_tx_refill( - furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); + furi_hal_subghz_async_tx.buffer, FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); } if(LL_DMA_IsActiveFlag_TC1(SUBGHZ_DMA)) { LL_DMA_ClearFlag_TC1(SUBGHZ_DMA); furi_hal_subghz_async_tx_refill( - furi_hal_subghz_async_tx.buffer + API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF, - API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); + furi_hal_subghz_async_tx.buffer + FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF, + FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF); } #else #error Update this code. Would you kindly? #endif } -static void furi_hal_subghz_async_tx_timer_isr() { - if(LL_TIM_IsActiveFlag_UPDATE(TIM2)) { - LL_TIM_ClearFlag_UPDATE(TIM2); - if(LL_TIM_GetAutoReload(TIM2) == 0) { - if(furi_hal_subghz.state == SubGhzStateAsyncTx) { - furi_hal_subghz.state = SubGhzStateAsyncTxLast; - LL_DMA_DisableChannel(SUBGHZ_DMA_CH1_DEF); - } else if(furi_hal_subghz.state == SubGhzStateAsyncTxLast) { - furi_hal_subghz.state = SubGhzStateAsyncTxEnd; - //forcibly pulls the pin to the ground so that there is no carrier - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); - LL_TIM_DisableCounter(TIM2); - } else { - furi_crash(); - } - } - } -} - bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* context) { furi_assert(furi_hal_subghz.state == SubGhzStateIdle); furi_assert(callback); @@ -648,11 +683,11 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_async_tx.duty_high = 0; furi_hal_subghz_async_tx.buffer = - malloc(API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); + malloc(FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); // Connect CC1101_GD0 to TIM2 as output furi_hal_gpio_init_ex( - &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullDown, GpioSpeedLow, GpioAltFn1TIM2); + &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); // Configure DMA LL_DMA_InitTypeDef dma_config = {0}; @@ -664,9 +699,10 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - dma_config.NbData = API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL; + dma_config.NbData = FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - dma_config.Priority = LL_DMA_MODE_NORMAL; + dma_config.Priority = + LL_DMA_PRIORITY_VERYHIGH; // Ensure that ARR is updated before anyone else try to check it LL_DMA_Init(SUBGHZ_DMA_CH1_DEF, &dma_config); furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, furi_hal_subghz_async_tx_dma_isr, NULL); LL_DMA_EnableIT_TC(SUBGHZ_DMA_CH1_DEF); @@ -676,14 +712,12 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_bus_enable(FuriHalBusTIM2); // Configure TIM2 - LL_TIM_InitTypeDef TIM_InitStruct = {0}; - TIM_InitStruct.Prescaler = 64 - 1; - TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; - TIM_InitStruct.Autoreload = 1000; - TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; - LL_TIM_Init(TIM2, &TIM_InitStruct); + LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetAutoReload(TIM2, 1000); + LL_TIM_SetPrescaler(TIM2, 64 - 1); LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); - LL_TIM_EnableARRPreload(TIM2); + LL_TIM_DisableARRPreload(TIM2); // Configure TIM2 CH2 LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; @@ -691,34 +725,23 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE; TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE; TIM_OC_InitStruct.CompareValue = 0; - TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_LOW; + TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH2, &TIM_OC_InitStruct); LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH2); LL_TIM_DisableMasterSlaveMode(TIM2); - furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_async_tx_timer_isr, NULL); - + furi_hal_subghz_async_tx_middleware_idle(&furi_hal_subghz_async_tx.middleware); furi_hal_subghz_async_tx_refill( - furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL); + furi_hal_subghz_async_tx.buffer, FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL); LL_TIM_EnableDMAReq_UPDATE(TIM2); LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); - // Start counter - LL_TIM_GenerateEvent_UPDATE(TIM2); -#ifdef FURI_HAL_SUBGHZ_TX_GPIO - furi_hal_gpio_write(&FURI_HAL_SUBGHZ_TX_GPIO, true); -#endif - furi_hal_subghz_tx(); - - LL_TIM_SetCounter(TIM2, 0); - LL_TIM_EnableCounter(TIM2); - // Start debug if(furi_hal_subghz_start_debug()) { const GpioPin* gpio = furi_hal_subghz.async_mirror_pin; - furi_hal_subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; - furi_hal_subghz_debug_gpio_buff[1] = gpio->pin; + furi_hal_subghz_debug_gpio_buff[0] = gpio->pin; + furi_hal_subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); @@ -730,33 +753,41 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; dma_config.NbData = 2; dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; + dma_config.Priority = LL_DMA_PRIORITY_HIGH; // Ensure that it's updated after ARR LL_DMA_Init(SUBGHZ_DMA_CH2_DEF, &dma_config); LL_DMA_SetDataLength(SUBGHZ_DMA_CH2_DEF, 2); LL_DMA_EnableChannel(SUBGHZ_DMA_CH2_DEF); } + // Start counter +#ifdef FURI_HAL_SUBGHZ_TX_GPIO + furi_hal_gpio_write(&FURI_HAL_SUBGHZ_TX_GPIO, true); +#endif + furi_hal_subghz_tx(); + + LL_TIM_SetCounter(TIM2, 0); + LL_TIM_EnableCounter(TIM2); + return true; } bool furi_hal_subghz_is_async_tx_complete() { - return furi_hal_subghz.state == SubGhzStateAsyncTxEnd; + return (furi_hal_subghz.state == SubGhzStateAsyncTx) && (LL_TIM_GetAutoReload(TIM2) == 0); } void furi_hal_subghz_stop_async_tx() { - furi_assert( - furi_hal_subghz.state == SubGhzStateAsyncTx || - furi_hal_subghz.state == SubGhzStateAsyncTxLast || - furi_hal_subghz.state == SubGhzStateAsyncTxEnd); + furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); // Shutdown radio furi_hal_subghz_idle(); + + // Deinitialize GPIO + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); #ifdef FURI_HAL_SUBGHZ_TX_GPIO furi_hal_gpio_write(&FURI_HAL_SUBGHZ_TX_GPIO, false); #endif // Deinitialize Timer - FURI_CRITICAL_ENTER(); furi_hal_bus_disable(FuriHalBusTIM2); furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); @@ -765,16 +796,11 @@ void furi_hal_subghz_stop_async_tx() { furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, NULL, NULL); - // Deinitialize GPIO - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - // Stop debug if(furi_hal_subghz_stop_debug()) { LL_DMA_DisableChannel(SUBGHZ_DMA_CH2_DEF); } - FURI_CRITICAL_EXIT(); - free(furi_hal_subghz_async_tx.buffer); float duty_cycle = diff --git a/targets/f7/furi_hal/furi_hal_subghz.h b/targets/f7/furi_hal/furi_hal_subghz.h index 855ce31619c..757f7089ac5 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.h +++ b/targets/f7/furi_hal/furi_hal_subghz.h @@ -17,10 +17,10 @@ extern "C" { #endif -/** Low level buffer dimensions and guard times */ -#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL (256) -#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2) -#define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 999 +/** Various subghz defines */ +#define FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL (256u) +#define FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2) +#define FURI_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME (999u) /** Switchable Radio Paths */ typedef enum { diff --git a/targets/f7/furi_hal/furi_hal_uart.c b/targets/f7/furi_hal/furi_hal_uart.c deleted file mode 100644 index 209c6be6a2e..00000000000 --- a/targets/f7/furi_hal/furi_hal_uart.c +++ /dev/null @@ -1,244 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include - -static bool furi_hal_usart_prev_enabled[2]; - -static void (*irq_cb[2])(uint8_t ev, uint8_t data, void* context); -static void* irq_ctx[2]; - -static void furi_hal_usart_init(uint32_t baud) { - furi_hal_bus_enable(FuriHalBusUSART1); - LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK2); - - furi_hal_gpio_init_ex( - &gpio_usart_tx, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn7USART1); - furi_hal_gpio_init_ex( - &gpio_usart_rx, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn7USART1); - - LL_USART_InitTypeDef USART_InitStruct; - USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1; - USART_InitStruct.BaudRate = baud; - USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; - USART_InitStruct.StopBits = LL_USART_STOPBITS_1; - USART_InitStruct.Parity = LL_USART_PARITY_NONE; - USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; - USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; - USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16; - LL_USART_Init(USART1, &USART_InitStruct); - LL_USART_EnableFIFO(USART1); - LL_USART_ConfigAsyncMode(USART1); - - LL_USART_Enable(USART1); - - while(!LL_USART_IsActiveFlag_TEACK(USART1) || !LL_USART_IsActiveFlag_REACK(USART1)) - ; - - LL_USART_DisableIT_ERROR(USART1); - - NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); -} - -static void furi_hal_lpuart_init(uint32_t baud) { - furi_hal_bus_enable(FuriHalBusLPUART1); - LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1); - - furi_hal_gpio_init_ex( - &gpio_ext_pc0, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn8LPUART1); - furi_hal_gpio_init_ex( - &gpio_ext_pc1, - GpioModeAltFunctionPushPull, - GpioPullUp, - GpioSpeedVeryHigh, - GpioAltFn8LPUART1); - - LL_LPUART_InitTypeDef LPUART_InitStruct; - LPUART_InitStruct.PrescalerValue = LL_LPUART_PRESCALER_DIV1; - LPUART_InitStruct.BaudRate = 115200; - LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_8B; - LPUART_InitStruct.StopBits = LL_LPUART_STOPBITS_1; - LPUART_InitStruct.Parity = LL_LPUART_PARITY_NONE; - LPUART_InitStruct.TransferDirection = LL_LPUART_DIRECTION_TX_RX; - LPUART_InitStruct.HardwareFlowControl = LL_LPUART_HWCONTROL_NONE; - LL_LPUART_Init(LPUART1, &LPUART_InitStruct); - LL_LPUART_EnableFIFO(LPUART1); - - LL_LPUART_Enable(LPUART1); - - while(!LL_LPUART_IsActiveFlag_TEACK(LPUART1) || !LL_LPUART_IsActiveFlag_REACK(LPUART1)) - ; - - furi_hal_uart_set_br(FuriHalUartIdLPUART1, baud); - LL_LPUART_DisableIT_ERROR(LPUART1); - - NVIC_SetPriority(LPUART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); -} - -void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud) { - if(ch == FuriHalUartIdLPUART1) { - furi_hal_lpuart_init(baud); - } else if(ch == FuriHalUartIdUSART1) { - furi_hal_usart_init(baud); - } -} - -void furi_hal_uart_set_br(FuriHalUartId ch, uint32_t baud) { - if(ch == FuriHalUartIdUSART1) { - if(LL_USART_IsEnabled(USART1)) { - // Wait for transfer complete flag - while(!LL_USART_IsActiveFlag_TC(USART1)) - ; - LL_USART_Disable(USART1); - uint32_t uartclk = LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE); - LL_USART_SetBaudRate( - USART1, uartclk, LL_USART_PRESCALER_DIV1, LL_USART_OVERSAMPLING_16, baud); - LL_USART_Enable(USART1); - } - } else if(ch == FuriHalUartIdLPUART1) { - if(LL_LPUART_IsEnabled(LPUART1)) { - // Wait for transfer complete flag - while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) - ; - LL_LPUART_Disable(LPUART1); - uint32_t uartclk = LL_RCC_GetLPUARTClockFreq(LL_RCC_LPUART1_CLKSOURCE); - if(uartclk / baud > 4095) { - LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV32); - LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV32, baud); - } else { - LL_LPUART_SetPrescaler(LPUART1, LL_LPUART_PRESCALER_DIV1); - LL_LPUART_SetBaudRate(LPUART1, uartclk, LL_LPUART_PRESCALER_DIV1, baud); - } - LL_LPUART_Enable(LPUART1); - } - } -} - -void furi_hal_uart_deinit(FuriHalUartId ch) { - furi_hal_uart_set_irq_cb(ch, NULL, NULL); - if(ch == FuriHalUartIdUSART1) { - if(furi_hal_bus_is_enabled(FuriHalBusUSART1)) { - furi_hal_bus_disable(FuriHalBusUSART1); - } - furi_hal_gpio_init(&gpio_usart_tx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_usart_rx, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - } else if(ch == FuriHalUartIdLPUART1) { - if(furi_hal_bus_is_enabled(FuriHalBusLPUART1)) { - furi_hal_bus_disable(FuriHalBusLPUART1); - } - furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - } -} - -void furi_hal_uart_suspend(FuriHalUartId channel) { - if(channel == FuriHalUartIdLPUART1 && LL_LPUART_IsEnabled(LPUART1)) { - LL_LPUART_Disable(LPUART1); - furi_hal_usart_prev_enabled[channel] = true; - } else if(channel == FuriHalUartIdUSART1 && LL_USART_IsEnabled(USART1)) { - LL_USART_Disable(USART1); - furi_hal_usart_prev_enabled[channel] = true; - } -} - -void furi_hal_uart_resume(FuriHalUartId channel) { - if(!furi_hal_usart_prev_enabled[channel]) { - return; - } else if(channel == FuriHalUartIdLPUART1) { - LL_LPUART_Enable(LPUART1); - } else if(channel == FuriHalUartIdUSART1) { - LL_USART_Enable(USART1); - } - - furi_hal_usart_prev_enabled[channel] = false; -} - -void furi_hal_uart_tx(FuriHalUartId ch, uint8_t* buffer, size_t buffer_size) { - if(ch == FuriHalUartIdUSART1) { - if(LL_USART_IsEnabled(USART1) == 0) return; - - while(buffer_size > 0) { - while(!LL_USART_IsActiveFlag_TXE(USART1)) - ; - - LL_USART_TransmitData8(USART1, *buffer); - buffer++; - buffer_size--; - } - - } else if(ch == FuriHalUartIdLPUART1) { - if(LL_LPUART_IsEnabled(LPUART1) == 0) return; - - while(buffer_size > 0) { - while(!LL_LPUART_IsActiveFlag_TXE(LPUART1)) - ; - - LL_LPUART_TransmitData8(LPUART1, *buffer); - - buffer++; - buffer_size--; - } - } -} - -void furi_hal_uart_set_irq_cb( - FuriHalUartId ch, - void (*cb)(UartIrqEvent ev, uint8_t data, void* ctx), - void* ctx) { - if(cb == NULL) { - if(ch == FuriHalUartIdUSART1) { - NVIC_DisableIRQ(USART1_IRQn); - LL_USART_DisableIT_RXNE_RXFNE(USART1); - } else if(ch == FuriHalUartIdLPUART1) { - NVIC_DisableIRQ(LPUART1_IRQn); - LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); - } - irq_cb[ch] = cb; - irq_ctx[ch] = ctx; - } else { - irq_ctx[ch] = ctx; - irq_cb[ch] = cb; - if(ch == FuriHalUartIdUSART1) { - NVIC_EnableIRQ(USART1_IRQn); - LL_USART_EnableIT_RXNE_RXFNE(USART1); - } else if(ch == FuriHalUartIdLPUART1) { - NVIC_EnableIRQ(LPUART1_IRQn); - LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); - } - } -} - -void LPUART1_IRQHandler(void) { - if(LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1)) { - uint8_t data = LL_LPUART_ReceiveData8(LPUART1); - irq_cb[FuriHalUartIdLPUART1](UartIrqEventRXNE, data, irq_ctx[FuriHalUartIdLPUART1]); - } else if(LL_LPUART_IsActiveFlag_ORE(LPUART1)) { - LL_LPUART_ClearFlag_ORE(LPUART1); - } -} - -void USART1_IRQHandler(void) { - if(LL_USART_IsActiveFlag_RXNE_RXFNE(USART1)) { - uint8_t data = LL_USART_ReceiveData8(USART1); - irq_cb[FuriHalUartIdUSART1](UartIrqEventRXNE, data, irq_ctx[FuriHalUartIdUSART1]); - } else if(LL_USART_IsActiveFlag_ORE(USART1)) { - LL_USART_ClearFlag_ORE(USART1); - } -} diff --git a/targets/f7/furi_hal/furi_hal_uart.h b/targets/f7/furi_hal/furi_hal_uart.h deleted file mode 100644 index 07211db8bc2..00000000000 --- a/targets/f7/furi_hal/furi_hal_uart.h +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @file furi_hal_uart.h - * @version 1.0 - * @date 2021-11-19 - * - * UART HAL api interface - */ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * UART channels - */ -typedef enum { - FuriHalUartIdUSART1, - FuriHalUartIdLPUART1, -} FuriHalUartId; - -/** - * UART events - */ -typedef enum { - UartIrqEventRXNE, -} UartIrqEvent; - -/** - * Init UART - * Configures GPIO to UART function, сonfigures UART hardware, enables UART hardware - * @param channel UART channel - * @param baud baudrate - */ -void furi_hal_uart_init(FuriHalUartId channel, uint32_t baud); - -/** - * Deinit UART - * Configures GPIO to analog, clears callback and callback context, disables UART hardware - * @param channel UART channel - */ -void furi_hal_uart_deinit(FuriHalUartId channel); - -/** - * Suspend UART operation - * Disables UART hardware, settings and callbacks are preserved - * @param channel UART channel - */ -void furi_hal_uart_suspend(FuriHalUartId channel); - -/** - * Resume UART operation - * Resumes UART hardware from suspended state - * @param channel UART channel - */ -void furi_hal_uart_resume(FuriHalUartId channel); - -/** - * Changes UART baudrate - * @param channel UART channel - * @param baud baudrate - */ -void furi_hal_uart_set_br(FuriHalUartId channel, uint32_t baud); - -/** - * Transmits data - * @param channel UART channel - * @param buffer data - * @param buffer_size data size (in bytes) - */ -void furi_hal_uart_tx(FuriHalUartId channel, uint8_t* buffer, size_t buffer_size); - -/** - * Sets UART event callback - * @param channel UART channel - * @param callback callback pointer - * @param context callback context - */ -void furi_hal_uart_set_irq_cb( - FuriHalUartId channel, - void (*callback)(UartIrqEvent event, uint8_t data, void* context), - void* context); - -#ifdef __cplusplus -} -#endif diff --git a/targets/f7/furi_hal/furi_hal_version_device.c b/targets/f7/furi_hal/furi_hal_version_device.c index be3f4bf3f3c..89728998402 100644 --- a/targets/f7/furi_hal/furi_hal_version_device.c +++ b/targets/f7/furi_hal/furi_hal_version_device.c @@ -23,3 +23,11 @@ const char* furi_hal_version_get_ic_id() { const char* furi_hal_version_get_mic_id() { return "210-175991"; } + +const char* furi_hal_version_get_srrc_id() { + return "2023DJ16420"; +} + +const char* furi_hal_version_get_ncc_id() { + return "CCAJ23LP34D0T3"; +} \ No newline at end of file diff --git a/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h index e6fd9eb1cc0..4f8aad6bd64 100644 --- a/targets/furi_hal_include/furi_hal.h +++ b/targets/furi_hal_include/furi_hal.h @@ -14,7 +14,6 @@ struct STOP_EXTERNING_ME {}; #include #include #include -#include #include #include #include @@ -36,7 +35,8 @@ struct STOP_EXTERNING_ME {}; #include #include #include -#include +#include +#include #include #include #include diff --git a/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h index 5edda6ba197..ebe0fe6149b 100644 --- a/targets/furi_hal_include/furi_hal_power.h +++ b/targets/furi_hal_include/furi_hal_power.h @@ -58,7 +58,7 @@ void furi_hal_power_insomnia_enter(); */ void furi_hal_power_insomnia_exit(); -/** Check if sleep availble +/** Check if sleep available * * @return true if available */ diff --git a/targets/furi_hal_include/furi_hal_version.h b/targets/furi_hal_include/furi_hal_version.h index 98d011cb389..8e7059fbc47 100644 --- a/targets/furi_hal_include/furi_hal_version.h +++ b/targets/furi_hal_include/furi_hal_version.h @@ -92,6 +92,18 @@ const char* furi_hal_version_get_ic_id(); */ const char* furi_hal_version_get_mic_id(); +/** Get SRRC id + * + * @return SRRC id as C-string + */ +const char* furi_hal_version_get_srrc_id(); + +/** Get NCC id + * + * @return NCC id as C-string + */ +const char* furi_hal_version_get_ncc_id(); + /** Get OTP version * * @return OTP Version