diff --git a/.ci_files/rgb.patch b/.ci_files/rgb.patch index d4f98aaec2..a9908125f4 100644 --- a/.ci_files/rgb.patch +++ b/.ci_files/rgb.patch @@ -1,5 +1,5 @@ diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c -index 5769ced..c5d3088 100644 +index 9baa738..91ad7c1 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -9,6 +9,7 @@ @@ -19,7 +19,7 @@ index 5769ced..c5d3088 100644 } diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c -index 1955012..19d953d 100644 +index 2a1d988..dda86f3 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -3,6 +3,7 @@ @@ -30,16 +30,16 @@ index 1955012..19d953d 100644 #define MAX_NOTIFICATION_SETTINGS 4 -@@ -20,6 +21,8 @@ static const NotificationSequence sequence_note_c = { - NULL, - }; +@@ -13,6 +14,8 @@ typedef struct { + VariableItemList* variable_item_list; + } NotificationAppSettings; +static VariableItem* temp_item; + - #define CONTRAST_COUNT 11 - const char* const contrast_text[CONTRAST_COUNT] = { - "-5", -@@ -156,6 +159,59 @@ static void vibro_changed(VariableItem* item) { + static const NotificationSequence sequence_note_c = { + &message_note_c5, + &message_delay_100, +@@ -168,6 +171,59 @@ static void vibro_changed(VariableItem* item) { notification_message(app->notification, &sequence_single_vibro); } @@ -99,7 +99,7 @@ index 1955012..19d953d 100644 static uint32_t notification_app_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; -@@ -180,8 +236,40 @@ static NotificationAppSettings* alloc_settings() { +@@ -192,8 +248,40 @@ static NotificationAppSettings* alloc_settings() { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, contrast_text[value_index]); @@ -462,54 +462,6 @@ index 0000000..68dacda + */ +const char* rgb_backlight_get_color_text(uint8_t index); \ No newline at end of file -diff --git a/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c -index 83e1603..45798ca 100644 ---- a/targets/f7/furi_hal/furi_hal_light.c -+++ b/targets/f7/furi_hal/furi_hal_light.c -@@ -3,6 +3,7 @@ - #include - #include - #include -+#include - - #define LED_CURRENT_RED 50 - #define LED_CURRENT_GREEN 50 -@@ -31,22 +32,21 @@ void furi_hal_light_init() { - } - - void furi_hal_light_set(Light light, uint8_t value) { -- furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); -- if(light & LightRed) { -- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); -- } -- if(light & LightGreen) { -- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value); -- } -- if(light & LightBlue) { -- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value); -- } - if(light & LightBacklight) { -- uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite); -- lp5562_execute_ramp( -- &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100); -+ rgb_backlight_update(value, false); -+ } else { -+ furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); -+ if(light & LightRed) { -+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); -+ } -+ if(light & LightGreen) { -+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value); -+ } -+ if(light & LightBlue) { -+ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value); -+ } -+ furi_hal_i2c_release(&furi_hal_i2c_handle_power); - } -- furi_hal_i2c_release(&furi_hal_i2c_handle_power); - } - - void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period) { diff --git a/lib/drivers/SK6805.c b/lib/drivers/SK6805.c new file mode 100644 index 0000000..572e1df @@ -675,3 +627,51 @@ index 0000000..7c58956 + +#endif /* SK6805_H_ */ \ No newline at end of file +diff --git a/targets/f7/furi_hal/furi_hal_light.c b/targets/f7/furi_hal/furi_hal_light.c +index 83e1603..45798ca 100644 +--- a/targets/f7/furi_hal/furi_hal_light.c ++++ b/targets/f7/furi_hal/furi_hal_light.c +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + + #define LED_CURRENT_RED 50 + #define LED_CURRENT_GREEN 50 +@@ -31,22 +32,21 @@ void furi_hal_light_init() { + } + + void furi_hal_light_set(Light light, uint8_t value) { +- furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); +- if(light & LightRed) { +- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); +- } +- if(light & LightGreen) { +- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value); +- } +- if(light & LightBlue) { +- lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value); +- } + if(light & LightBacklight) { +- uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite); +- lp5562_execute_ramp( +- &furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100); ++ rgb_backlight_update(value, false); ++ } else { ++ furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); ++ if(light & LightRed) { ++ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value); ++ } ++ if(light & LightGreen) { ++ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value); ++ } ++ if(light & LightBlue) { ++ lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value); ++ } ++ furi_hal_i2c_release(&furi_hal_i2c_handle_power); + } +- furi_hal_i2c_release(&furi_hal_i2c_handle_power); + } + + void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period) { diff --git a/.drone.yml b/.drone.yml index 0a225e722f..6a25fb5ca4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -375,7 +375,7 @@ trigger: - tag node: - typ: haupt + typ: dev1 --- kind: pipeline @@ -678,4 +678,4 @@ trigger: - push node: - typ: haupt + typ: dev1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4a40888d..9d3990a201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,47 @@ ## New changes -* NFC: Various NFC Mifare classic Read fixes (was caused by wrong logic in parsers) (mifare classic 4k, and others) (by @Leptopt1los) -* Apps: Fixed Unitemp and ESP32 Camera suite +* NFC: **EMV parser** added (by @Leptopt1los and @wosk | PR #700) +* NFC: Metromoney parser balance fix (by @Leptopt1los | PR #699) +* NFC/LFRFID: Stop emulation after 5 mins to avoid antenna damage (by @Leptopt1los) +* Archive: Fix two filebrowser bugs +* SubGHz: **Programming mode for Dea Mio** (right arrow button) +* SubGHz: **Keeloq fix emulation for multiple systems and extend add manually support** for 2 of them (Dea Mio, Genius Bravo, GSN, Normstahl) +* SubGHz: Fixed hopper state when entering Read via Freq analyzer +* SubGHz: Raw erase fixes (by @Willy-JL) +* SubGHz: Subghz save files with receive time (by @Willy-JL) +* NFC: Fix NFC V dumps with v3 (pre refactor saves) crashing at info page +* NFC: Zolotaya Korona Online parser added (by @Leptopt1los) +* NFC: Add NFC **NDEF parser** (by @Willy-JL) +* LF RFID: **Write T5577 with random and custom password** added (clear password via Extra actions) (by @Leptopt1los) +* SubGHz: Update honeywell protocol (by @Willy-JL) +* System: More contrast values for replacement displays (up to +8 or -8) +* USB/BLE HID: Add macOS Music app volume control * Apps: **Check out Apps updates by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW PR 3384: NFC: Display unread Mifare Classic bytes as question marks - by TollyH +* OFW PR 3396: NFC: **fix application opening from browser** - by RebornedBrain (+ fix for leftover issues) +* OFW PR 3382: NFC UI refactor - by RebornedBrain +* OFW PR 3391: Rework more info scene for Ultralight cards - by RebornedBrain +* OFW PR 3401: it-IT-mac layout - by nminaylov +* OFW: Fix expansion protocol crash when fed lots of garbage +* OFW: 0.98.0-rc various fixes +* OFW: RFID CLI: better usage +* OFW: **Mf DESFire fixes** +* OFW: NFC UI refactor +* OFW: **Expansion module protocol** (+ expansion settings read and store in ram by @Willy-JL) +* OFW: Bugfix: Strip last parity bit from decoded FDX-B data +* OFW: FuriHal: interrupt priorities and documentation +* OFW: FuriHal: **UART refactoring** +* OFW: SubGhz: add `subghz tx_from_file` CLI cmd, major TX flow refactoring, various improvements and bug fixes +* OFW: Furi_hal_rtc: new function +* OFW: NFC UI refactor +* OFW: assets: checking limits on image size; ufbt: cdb target +* OFW: NFC: system dict skip when user dict is skipped fix (replaces our fix) +* OFW: FuriHal: fix start duration furi_hal_subghz_async_tx +* OFW: NFC: parsers minor cleanup +* OFW: NFC Ntag success write freeze when not saved card +* OFW: ufbt: fixed generated project paths on Windows

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) -- EMV simple data parser was removed with protocol with refactoring (OFW) - Option to unlock Slix-L (NFC V) with preset or custom password was removed with refactoring (OFW) - NFC CLI was removed with refactoring (OFW) - Current list of affected apps: https://github.com/xMasterX/all-the-plugins/tree/dev/apps_broken_by_last_refactors diff --git a/ReadMe.md b/ReadMe.md index 6b7c8c82a8..ee3d394bf9 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -79,9 +79,10 @@ - **NFC/RFID/iButton** * LFRFID/iButton Fuzzer plugins * Extra Mifare Classic keys + * EMV Protocol + Public data parser (by @Leptopt1los and @wosk) + * NFC/LFRFID - Stop emulation after 5 mins to avoid antenna damage (by @Leptopt1los) * NFC `Add manually` -> Mifare Classic with custom UID - * NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards) - * Picopass/iClass plugin (now with emulation support!) included in releases + * NFC parsers: Umarsh, Zolotaya Korona, Kazan, Metromoney, Moscow Social Card, Troika (reworked) and [many others](https://github.com/DarkFlippers/unleashed-firmware/tree/dev/applications/main/nfc/plugins/supported_cards) (by @Leptopt1los and @assasinfil) - **Quality of life & other features** - Customizable Flipper name **Update! Now can be changed in Settings->Desktop** (by @xMasterX and @Willy-JL) - Text Input UI element -> Cursor feature (by @Willy-JL) @@ -127,10 +128,11 @@ Encoders made by @assasinfil & @xMasterX: The majority of this project is developed and maintained by me, @xMasterX. I'm unemployed, and the only income I receive is from your donations. Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. +- @Leptopt1los - NFC, RFID, IR Assets (only ACs), Plugins, and many other things - @gid9798 - SubGHz, Plugins, many other things -- @assasinfil - SubGHz protocols, NFC parsers (working with @Leptopt1los) +- @assasinfil - SubGHz protocols, NFC parsers - @Svaarich - UI design and animations -- @amec0e & @Leptopt1los (only ACs) - Infrared assets +- @amec0e - Infrared assets - Community moderators in Telegram, Discord, and Reddit - And of course our GitHub community. Your PRs are a very important part of this firmware and open-source development. diff --git a/SConstruct b/SConstruct index 31391b1762..a2f40763e6 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 0000000000..9bc4b2fc29 --- /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 0000000000..e39b1eec5c --- /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 0000000000..a0b8b42e8b --- /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 4bede9ab45..0291c9e79d 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 0000000000..50fe1b9f4d --- /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 2dbaa4d868..e3e44291fa 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 4401cbb4d3..d5c2433ba0 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 a53c70424c..0e6509b62c 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -229,17 +229,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 { @@ -249,36 +249,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 { @@ -292,20 +292,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 { @@ -332,6 +332,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 d7afaa3c4f..7ae9ca03d5 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 352a6281fa..5bbfc9f954 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -17,18 +17,18 @@ #define TAG "SubGhzDeviceCc1101Ext" -#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 +#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO (&gpio_ext_pb2) #define SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO &gpio_ext_pc3 #define SUBGHZ_DEVICE_CC1101_EXT_FORCE_DANGEROUS_RANGE false #define SUBGHZ_DEVICE_CC1101_CONFIG_VER 1 /* 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 \ @@ -37,10 +37,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 { @@ -48,7 +48,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 @@ -58,13 +57,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 { @@ -283,8 +294,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]) { @@ -313,8 +324,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]); @@ -396,6 +407,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); @@ -404,6 +416,9 @@ 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); if(subghz_device_cc1101_ext->power_amp) { furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0); @@ -413,6 +428,9 @@ void subghz_device_cc1101_ext_idle() { 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); if(subghz_device_cc1101_ext->power_amp) { furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0); @@ -423,6 +441,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); if(subghz_device_cc1101_ext->power_amp) { furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 1); @@ -616,50 +637,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--; } @@ -688,20 +749,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); @@ -730,7 +777,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); @@ -746,16 +793,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); @@ -801,7 +847,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(); @@ -812,19 +857,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); @@ -833,17 +881,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/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index facf00a3af..520741fe27 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -153,7 +153,9 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { archive_get_items(browser, furi_string_get_cstr(browser->path)); - if(!archive_file_get_array_size(browser) && archive_is_home(browser)) { + ArchiveTabEnum tab = archive_get_tab(browser); + if(!archive_file_get_array_size(browser) && archive_is_home(browser) && + (tab != ArchiveTabBrowser)) { archive_switch_tab(browser, TAB_LEFT); } else { with_view_model( @@ -220,7 +222,8 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) { }, false); - if((items_cnt == 0) && (archive_is_home(browser))) { + ArchiveTabEnum tab = archive_get_tab(browser); + if((items_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser)) { archive_switch_tab(browser, TAB_LEFT); } diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index e298583ec8..de3aa8887e 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -585,6 +585,10 @@ static bool archive_view_input(InputEvent* event, void* context) { ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } + // Fix for empty folders, we can't select -1 item + if(model->item_idx < 0) { + model->item_idx = 0; + } if(is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); diff --git a/applications/main/bad_usb/resources/badusb/assets/layouts/it-IT-mac.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/it-IT-mac.kl new file mode 100644 index 0000000000..6c10e4266b Binary files /dev/null and b/applications/main/bad_usb/resources/badusb/assets/layouts/it-IT-mac.kl differ diff --git a/applications/main/gpio/application.fam b/applications/main/gpio/application.fam index 7639199217..607d97a278 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 020fbf79a1..06d377d39f 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 d54ffd3682..ce4cb6f550 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 8fcacd4039..f8b142c630 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 366c5cdc4e..8dff09cb80 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/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index 8eb78a9c7b..bd64c79068 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -1,6 +1,30 @@ #include "lfrfid_i.h" #include +//TODO: use .txt file in resources for passwords. +const uint32_t default_passwords[] = { + 0x51243648, 0x000D8787, 0x19920427, 0x50524F58, 0xF9DCEBA0, 0x65857569, 0x05D73B9F, 0x89A69E60, + 0x314159E0, 0xAA55BBBB, 0xA5B4C3D2, 0x1C0B5848, 0x00434343, 0x444E4752, 0x4E457854, 0x44B44CAE, + 0x88661858, 0xE9920427, 0x575F4F4B, 0x50520901, 0x20206666, 0x65857569, 0x5469616E, 0x7686962A, + 0xC0F5009A, 0x07CEE75D, 0xfeedbeef, 0xdeadc0de, 0x00000000, 0x11111111, 0x22222222, 0x33333333, + 0x44444444, 0x55555555, 0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xAAAAAAAA, 0xBBBBBBBB, + 0xCCCCCCCC, 0xDDDDDDDD, 0xEEEEEEEE, 0xFFFFFFFF, 0xa0a1a2a3, 0xb0b1b2b3, 0x50415353, 0x00000001, + 0x00000002, 0x0000000a, 0x0000000b, 0x01020304, 0x02030405, 0x03040506, 0x04050607, 0x05060708, + 0x06070809, 0x0708090A, 0x08090A0B, 0x090A0B0C, 0x0A0B0C0D, 0x0B0C0D0E, 0x0C0D0E0F, 0x01234567, + 0x12345678, 0x10000000, 0x20000000, 0x30000000, 0x40000000, 0x50000000, 0x60000000, 0x70000000, + 0x80000000, 0x90000000, 0xA0000000, 0xB0000000, 0xC0000000, 0xD0000000, 0xE0000000, 0xF0000000, + 0x10101010, 0x01010101, 0x11223344, 0x22334455, 0x33445566, 0x44556677, 0x55667788, 0x66778899, + 0x778899AA, 0x8899AABB, 0x99AABBCC, 0xAABBCCDD, 0xBBCCDDEE, 0xCCDDEEFF, 0x0CB7E7FC, 0xFABADA11, + 0x87654321, 0x12341234, 0x69696969, 0x12121212, 0x12344321, 0x1234ABCD, 0x11112222, 0x13131313, + 0x10041004, 0x31415926, 0xabcd1234, 0x20002000, 0x19721972, 0xaa55aa55, 0x55aa55aa, 0x4f271149, + 0x07d7bb0b, 0x9636ef8f, 0xb5f44686, 0x9E3779B9, 0xC6EF3720, 0x7854794A, 0xF1EA5EED, 0x69314718, + 0x57721566, 0x93C467E3, 0x27182818, 0x50415353}; + +const uint32_t* lfrfid_get_t5577_default_passwords(uint8_t* len) { + *len = sizeof(default_passwords) / sizeof(uint32_t); + return default_passwords; +} + static bool lfrfid_debug_custom_event_callback(void* context, uint32_t event) { furi_assert(context); LfRfid* app = context; diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index a57e40de93..af340008a8 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -25,11 +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 raw_analyze \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/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index fc9f861a5f..b574a4e499 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -66,6 +66,7 @@ enum LfRfidCustomEvent { LfRfidEventWriteTooLongToWrite, LfRfidEventRpcLoadFile, LfRfidEventRpcSessionClose, + LfRfidEventEmulationTimeExpired, }; typedef enum { @@ -98,6 +99,8 @@ struct LfRfid { uint8_t* old_key_data; uint8_t* new_key_data; + uint8_t password[4]; + RpcAppSystem* rpc_ctx; LfRfidRpcState rpc_state; @@ -145,3 +148,5 @@ void lfrfid_popup_timeout_callback(void* context); void lfrfid_widget_callback(GuiButtonType result, InputType type, void* context); void lfrfid_text_input_callback(void* context); + +const uint32_t* lfrfid_get_t5577_default_passwords(uint8_t* len); \ No newline at end of file diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c b/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c index e791e88ba2..71b6b7aeeb 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_clear_t5577.c @@ -1,30 +1,13 @@ #include "../lfrfid_i.h" +#include "tools/t5577.h" #define TAG "Clear T5577" static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) { Popup* popup = app->popup; char curr_buf[32] = {}; - //TODO: use .txt file in resources for passwords. - const uint32_t default_passwords[] = { - 0x51243648, 0x000D8787, 0x19920427, 0x50524F58, 0xF9DCEBA0, 0x65857569, 0x05D73B9F, - 0x89A69E60, 0x314159E0, 0xAA55BBBB, 0xA5B4C3D2, 0x1C0B5848, 0x00434343, 0x444E4752, - 0x4E457854, 0x44B44CAE, 0x88661858, 0xE9920427, 0x575F4F4B, 0x50520901, 0x20206666, - 0x65857569, 0x5469616E, 0x7686962A, 0xC0F5009A, 0x07CEE75D, 0xfeedbeef, 0xdeadc0de, - 0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, - 0x77777777, 0x88888888, 0x99999999, 0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD, - 0xEEEEEEEE, 0xFFFFFFFF, 0xa0a1a2a3, 0xb0b1b2b3, 0x50415353, 0x00000001, 0x00000002, - 0x0000000a, 0x0000000b, 0x01020304, 0x02030405, 0x03040506, 0x04050607, 0x05060708, - 0x06070809, 0x0708090A, 0x08090A0B, 0x090A0B0C, 0x0A0B0C0D, 0x0B0C0D0E, 0x0C0D0E0F, - 0x01234567, 0x12345678, 0x10000000, 0x20000000, 0x30000000, 0x40000000, 0x50000000, - 0x60000000, 0x70000000, 0x80000000, 0x90000000, 0xA0000000, 0xB0000000, 0xC0000000, - 0xD0000000, 0xE0000000, 0xF0000000, 0x10101010, 0x01010101, 0x11223344, 0x22334455, - 0x33445566, 0x44556677, 0x55667788, 0x66778899, 0x778899AA, 0x8899AABB, 0x99AABBCC, - 0xAABBCCDD, 0xBBCCDDEE, 0xCCDDEEFF, 0x0CB7E7FC, 0xFABADA11, 0x87654321, 0x12341234, - 0x69696969, 0x12121212, 0x12344321, 0x1234ABCD, 0x11112222, 0x13131313, 0x10041004, - 0x31415926, 0xabcd1234, 0x20002000, 0x19721972, 0xaa55aa55, 0x55aa55aa, 0x4f271149, - 0x07d7bb0b, 0x9636ef8f, 0xb5f44686, 0x9E3779B9, 0xC6EF3720, 0x7854794A, 0xF1EA5EED, - 0x69314718, 0x57721566, 0x93C467E3, 0x27182818, 0x50415353}; - const uint8_t default_passwords_len = sizeof(default_passwords) / sizeof(uint32_t); + + uint8_t default_passwords_len; + const uint32_t* default_passwords = lfrfid_get_t5577_default_passwords(&default_passwords_len); popup_set_header(popup, "Removing\npassword", 90, 36, AlignCenter, AlignCenter); popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); @@ -33,14 +16,26 @@ static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) { LFRFIDT5577 data = { .block[0] = 0b00000000000101001000000001000000, - .blocks_to_write = 1, + .block[7] = 0, + .mask = 0b10000001, }; + // Clear custom password + uint32_t custom_pass = (app->password[0] << 24) | (app->password[1] << 16) | + (app->password[2] << 8) | (app->password[3]); + snprintf(curr_buf, sizeof(curr_buf), "Custom password"); + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); + + t5577_write_with_mask(&data, 0, true, custom_pass); + + furi_delay_ms(8); + + // Clear default passwords for(uint8_t i = 0; i < default_passwords_len; i++) { snprintf(curr_buf, sizeof(curr_buf), "Pass %d of %d", i, default_passwords_len); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); - t5577_write_with_pass(&data, default_passwords[i]); + t5577_write_with_mask(&data, 0, true, default_passwords[i]); furi_delay_ms(8); } diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_config.h b/applications/main/lfrfid/scenes/lfrfid_scene_config.h index 7789e133e5..47844a8b38 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_config.h +++ b/applications/main/lfrfid/scenes/lfrfid_scene_config.h @@ -6,6 +6,7 @@ ADD_SCENE(lfrfid, exit_confirm, ExitConfirm) ADD_SCENE(lfrfid, delete_confirm, DeleteConfirm) ADD_SCENE(lfrfid, read_key_menu, ReadKeyMenu) ADD_SCENE(lfrfid, write, Write) +ADD_SCENE(lfrfid, write_and_set_pass, WriteAndSetPass) ADD_SCENE(lfrfid, write_success, WriteSuccess) ADD_SCENE(lfrfid, emulate, Emulate) ADD_SCENE(lfrfid, save_name, SaveName) @@ -17,6 +18,7 @@ ADD_SCENE(lfrfid, save_type, SaveType) ADD_SCENE(lfrfid, saved_info, SavedInfo) ADD_SCENE(lfrfid, clear_t5577, ClearT5577) ADD_SCENE(lfrfid, clear_t5577_confirm, ClearT5577Confirm) +ADD_SCENE(lfrfid, enter_password, EnterPassword) ADD_SCENE(lfrfid, delete_success, DeleteSuccess) ADD_SCENE(lfrfid, extra_actions, ExtraActions) ADD_SCENE(lfrfid, raw_info, RawInfo) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c b/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c index dc39189942..b729f4de0c 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_emulate.c @@ -1,5 +1,14 @@ #include "../lfrfid_i.h" +#define LFRFID_EMULATION_TIME_MAX_MS (5 * 60 * 1000) + +FuriTimer* timer_auto_exit; + +void lfrfid_scene_emulate_popup_callback(void* context) { + LfRfid* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventEmulationTimeExpired); +} + void lfrfid_scene_emulate_on_enter(void* context) { LfRfid* app = context; Popup* popup = app->popup; @@ -22,18 +31,38 @@ void lfrfid_scene_emulate_on_enter(void* context) { lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); notification_message(app->notifications, &sequence_blink_start_magenta); + timer_auto_exit = + furi_timer_alloc(lfrfid_scene_emulate_popup_callback, FuriTimerTypeOnce, app); + furi_timer_start(timer_auto_exit, LFRFID_EMULATION_TIME_MAX_MS); + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); } bool lfrfid_scene_emulate_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); + LfRfid* app = context; bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventEmulationTimeExpired) { + if(!scene_manager_previous_scene(app->scene_manager)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } else { + scene_manager_previous_scene(app->scene_manager); + } + consumed = true; + } + } + return consumed; } void lfrfid_scene_emulate_on_exit(void* context) { LfRfid* app = context; + + furi_timer_stop(timer_auto_exit); + furi_timer_free(timer_auto_exit); + notification_message(app->notifications, &sequence_blink_stop); popup_reset(app->popup); lfrfid_worker_stop(app->lfworker); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_enter_password.c b/applications/main/lfrfid/scenes/lfrfid_scene_enter_password.c new file mode 100644 index 0000000000..486be136c4 --- /dev/null +++ b/applications/main/lfrfid/scenes/lfrfid_scene_enter_password.c @@ -0,0 +1,55 @@ +#include "../lfrfid_i.h" +#include "gui/scene_manager.h" + +int next_scene; + +void lfrfid_scene_enter_password_on_enter(void* context) { + LfRfid* app = context; + ByteInput* byte_input = app->byte_input; + + // true - use password for write, false - use password for clear pass + next_scene = scene_manager_get_scene_state(app->scene_manager, LfRfidSceneEnterPassword); + + bool password_set = app->password[0] | app->password[1] | app->password[2] | app->password[3]; + + if(next_scene == LfRfidSceneWriteAndSetPass && !password_set) { + uint8_t password_list_size; + const uint32_t* password_list = lfrfid_get_t5577_default_passwords(&password_list_size); + uint32_t pass = password_list[furi_get_tick() % password_list_size]; + + for(uint8_t i = 0; i < 4; i++) app->password[i] = (pass >> (8 * i)) & 0xFF; + } + + byte_input_set_header_text(byte_input, "Enter the password in hex"); + + byte_input_set_result_callback( + byte_input, lfrfid_text_input_callback, NULL, app, app->password, 4); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewByteInput); +} + +bool lfrfid_scene_enter_password_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + SceneManager* scene_manager = app->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventNext) { + consumed = true; + + scene_manager_next_scene(scene_manager, next_scene); + scene_manager_set_scene_state(scene_manager, LfRfidSceneEnterPassword, 1); + } + } else if(event.type == SceneManagerEventTypeBack) { + uint32_t prev_scenes[] = {LfRfidSceneExtraActions, LfRfidSceneSavedKeyMenu}; + scene_manager_set_scene_state(scene_manager, LfRfidSceneEnterPassword, 0); + scene_manager_search_and_switch_to_previous_scene_one_of( + scene_manager, prev_scenes, sizeof(prev_scenes[0])); + } + + return consumed; +} + +void lfrfid_scene_enter_password_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c index 3eb34fde98..db44582360 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_extra_actions.c @@ -80,7 +80,9 @@ bool lfrfid_scene_extra_actions_on_event(void* context, SceneManagerEvent event) dolphin_deed(DolphinDeedRfidRead); consumed = true; } else if(event.event == SubmenuIndexClearT5577) { - scene_manager_next_scene(app->scene_manager, LfRfidSceneClearT5577Confirm); + scene_manager_set_scene_state( + app->scene_manager, LfRfidSceneEnterPassword, LfRfidSceneClearT5577Confirm); + scene_manager_next_scene(app->scene_manager, LfRfidSceneEnterPassword); consumed = true; } else if(event.event == SubmenuIndexRAW) { scene_manager_next_scene(app->scene_manager, LfRfidSceneRawName); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c b/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c index 206074e9b0..b4d6a6f439 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_saved_key_menu.c @@ -4,6 +4,7 @@ typedef enum { SubmenuIndexEmulate, SubmenuIndexWrite, + SubmenuIndexWriteAndSetPass, SubmenuIndexEdit, SubmenuIndexDelete, SubmenuIndexInfo, @@ -23,6 +24,12 @@ void lfrfid_scene_saved_key_menu_on_enter(void* context) { submenu, "Emulate", SubmenuIndexEmulate, lfrfid_scene_saved_key_menu_submenu_callback, app); submenu_add_item( submenu, "Write", SubmenuIndexWrite, lfrfid_scene_saved_key_menu_submenu_callback, app); + submenu_add_item( + submenu, + "Write and set pass", + SubmenuIndexWriteAndSetPass, + lfrfid_scene_saved_key_menu_submenu_callback, + app); submenu_add_item( submenu, "Edit", SubmenuIndexEdit, lfrfid_scene_saved_key_menu_submenu_callback, app); submenu_add_item( @@ -48,6 +55,11 @@ bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(app->scene_manager, LfRfidSceneWrite); consumed = true; + } else if(event.event == SubmenuIndexWriteAndSetPass) { + scene_manager_set_scene_state( + app->scene_manager, LfRfidSceneEnterPassword, LfRfidSceneWriteAndSetPass); + scene_manager_next_scene(app->scene_manager, LfRfidSceneEnterPassword); + consumed = true; } else if(event.event == SubmenuIndexEdit) { scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveData); consumed = true; diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_write_and_set_pass.c b/applications/main/lfrfid/scenes/lfrfid_scene_write_and_set_pass.c new file mode 100644 index 0000000000..275a3e8893 --- /dev/null +++ b/applications/main/lfrfid/scenes/lfrfid_scene_write_and_set_pass.c @@ -0,0 +1,84 @@ +#include "../lfrfid_i.h" +#include "gui/scene_manager.h" + +static void lfrfid_write_and_set_pass_callback(LFRFIDWorkerWriteResult result, void* context) { + LfRfid* app = context; + uint32_t event = 0; + + if(result == LFRFIDWorkerWriteOK) { + event = LfRfidEventWriteOK; + } else if(result == LFRFIDWorkerWriteProtocolCannotBeWritten) { + event = LfRfidEventWriteProtocolCannotBeWritten; + } else if(result == LFRFIDWorkerWriteFobCannotBeWritten) { + event = LfRfidEventWriteFobCannotBeWritten; + } else if(result == LFRFIDWorkerWriteTooLongToWrite) { + event = LfRfidEventWriteTooLongToWrite; + } + + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void lfrfid_scene_write_and_set_pass_on_enter(void* context) { + LfRfid* app = context; + Popup* popup = app->popup; + + popup_set_header(popup, "Writing\nwith password", 89, 30, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); + + view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + protocol_dict_get_data(app->dict, app->protocol_id, app->old_key_data, size); + + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_write_and_set_pass_start( + app->lfworker, (LFRFIDProtocol)app->protocol_id, lfrfid_write_and_set_pass_callback, app); + notification_message(app->notifications, &sequence_blink_start_magenta); +} + +bool lfrfid_scene_write_and_set_pass_on_event(void* context, SceneManagerEvent event) { + LfRfid* app = context; + Popup* popup = app->popup; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == LfRfidEventWriteOK) { + notification_message(app->notifications, &sequence_success); + scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess); + consumed = true; + } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) { + 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); + consumed = true; + } else if( + (event.event == LfRfidEventWriteFobCannotBeWritten) || + (event.event == LfRfidEventWriteTooLongToWrite)) { + 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, + "Make sure this\ncard is writable\nand not\nprotected.", + 3, + 17, + AlignLeft, + AlignTop); + notification_message(app->notifications, &sequence_blink_start_yellow); + consumed = true; + } + } + + return consumed; +} + +void lfrfid_scene_write_and_set_pass_on_exit(void* context) { + LfRfid* app = context; + notification_message(app->notifications, &sequence_blink_stop); + popup_reset(app->popup); + lfrfid_worker_stop(app->lfworker); + lfrfid_worker_stop_thread(app->lfworker); + + size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); + protocol_dict_set_data(app->dict, app->protocol_id, app->old_key_data, size); +} diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index fec3b6c768..ab9690ec1f 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -146,6 +146,15 @@ App( sources=["plugins/supported_cards/zolotaya_korona.c"], ) +App( + appid="zolotaya_korona_online_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="zolotaya_korona_online_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/zolotaya_korona_online.c"], +) + App( appid="hid_parser", apptype=FlipperAppType.PLUGIN, @@ -164,6 +173,24 @@ App( sources=["plugins/supported_cards/washcity.c"], ) +App( + appid="emv_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="emv_plugin_ep", + targets=["f7"], + requires=["nfc", "storage"], + sources=["plugins/supported_cards/emv.c", "helpers/nfc_emv_parser.c"], +) + +App( + appid="ndef_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="ndef_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ndef.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 16fbc47492..86fcdd3d3f 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -30,4 +30,6 @@ typedef enum { NfcCustomEventPollerFailure, NfcCustomEventListenerUpdate, + + NfcCustomEventEmulationTimeExpired, } NfcCustomEvent; diff --git a/applications/main/nfc/helpers/nfc_emv_parser.c b/applications/main/nfc/helpers/nfc_emv_parser.c index 30e102405e..f48a63d8f5 100644 --- a/applications/main/nfc/helpers/nfc_emv_parser.c +++ b/applications/main/nfc/helpers/nfc_emv_parser.c @@ -34,7 +34,7 @@ static bool nfc_emv_parser_search_data( bool nfc_emv_parser_get_aid_name( Storage* storage, - uint8_t* aid, + const uint8_t* aid, uint8_t aid_len, FuriString* aid_name) { furi_assert(storage); diff --git a/applications/main/nfc/helpers/nfc_emv_parser.h b/applications/main/nfc/helpers/nfc_emv_parser.h index c636ca77d5..03edab0a82 100644 --- a/applications/main/nfc/helpers/nfc_emv_parser.h +++ b/applications/main/nfc/helpers/nfc_emv_parser.h @@ -13,7 +13,7 @@ */ bool nfc_emv_parser_get_aid_name( Storage* storage, - uint8_t* aid, + const uint8_t* aid, uint8_t aid_len, FuriString* aid_name); diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c new file mode 100644 index 0000000000..e543291cc1 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -0,0 +1,115 @@ +#include "emv.h" +#include "emv_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +static void nfc_scene_info_on_enter_emv(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv); + + FuriString* temp_str = furi_string_alloc(); + // furi_string_cat_printf( + // temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_emv_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_emv(NfcApp* instance) { + // Jump to advanced scene right away + scene_manager_next_scene(instance->scene_manager, NfcSceneEmvMoreInfo); +} + +static NfcCommand nfc_scene_read_poller_callback_emv(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolEmv); + + NfcApp* instance = context; + const EmvPollerEvent* emv_event = event.event_data; + + if(emv_event->type == EmvPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolEmv, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_emv(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_emv, instance); +} + +static void nfc_scene_read_success_on_enter_emv(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv); + + FuriString* temp_str = furi_string_alloc(); + // furi_string_cat_printf( + // temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_emv_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +// static void nfc_scene_emulate_on_enter_emv(NfcApp* instance) { +// const Iso14443_4aData* iso14443_4a_data = +// nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + +// instance->listener = +// nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); +// nfc_listener_start( +// instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +// } + +const NfcProtocolSupportBase nfc_protocol_support_emv = { + .features = NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_emv, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_emv, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_emv, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_emv, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.h b/applications/main/nfc/helpers/protocol_support/emv/emv.h new file mode 100644 index 0000000000..c68564f363 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_emv; diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c new file mode 100644 index 0000000000..b897db7875 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -0,0 +1,186 @@ +#include "emv_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" +#include "nfc/nfc_app_i.h" + +void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str) { + nfc_render_emv_header(str); + nfc_render_emv_uid( + data->iso14443_4a_data->iso14443_3a_data->uid, + data->iso14443_4a_data->iso14443_3a_data->uid_len, + str); + + if(format_type == NfcProtocolFormatTypeFull) nfc_render_emv_extra(data, str); +} + +void nfc_render_emv_header(FuriString* str) { + furi_string_cat_printf(str, "\e#%s\n", "EMV"); +} + +void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* str) { + if(uid_len == 0) return; + + furi_string_cat_printf(str, "UID: "); + + for(uint8_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(str, "%02X ", uid[i]); + } + + furi_string_cat_printf(str, "\n"); +} + +void nfc_render_emv_aid(const uint8_t* uid, const uint8_t uid_len, FuriString* str) { + if(uid_len == 0) return; + + furi_string_cat_printf(str, "UID: "); + + for(uint8_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(str, "%02X ", uid[i]); + } + + furi_string_cat_printf(str, "\n"); +} + +void nfc_render_emv_data(const EmvData* data, FuriString* str) { + nfc_render_emv_pan(data->emv_application.pan, data->emv_application.pan_len, str); + nfc_render_emv_name(data->emv_application.name, str); +} + +void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str) { + if(len == 0) return; + + FuriString* card_number = furi_string_alloc(); + for(uint8_t i = 0; i < len; i++) { + if((i % 2 == 0) && (i != 0)) furi_string_cat_printf(card_number, " "); + furi_string_cat_printf(card_number, "%02X", data[i]); + } + + // Cut padding 'F' from card number + furi_string_trim(card_number, "F"); + furi_string_cat(str, card_number); + furi_string_free(card_number); + + furi_string_cat_printf(str, "\n"); +} + +void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str) { + if(apl->exp_month == 0) return; + furi_string_cat_printf(str, "Exp: %02X/%02X\n", apl->exp_month, apl->exp_year); +} + +void nfc_render_emv_currency(uint16_t cur_code, FuriString* str) { + if(!cur_code) return; + + furi_string_cat_printf(str, "Currency code: %04X\n", cur_code); +} + +void nfc_render_emv_country(uint16_t country_code, FuriString* str) { + if(!country_code) return; + + furi_string_cat_printf(str, "Country code: %04X\n", country_code); +} + +void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) { + const uint8_t len = apl->aid_len; + + if(!len) { + furi_string_cat_printf(str, "No Pay Application found\n"); + return; + } + + furi_string_cat_printf(str, "AID: "); + + for(uint8_t i = 0; i < len; i++) furi_string_cat_printf(str, "%02X", apl->aid[i]); + + furi_string_cat_printf(str, "\n"); +} + +static void nfc_render_emv_pin_try_counter(uint8_t counter, FuriString* str) { + if(counter == 0xff) return; + furi_string_cat_printf(str, "PIN attempts left: %d\n", counter); +} + +void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) { + if(apl->transaction_counter) + furi_string_cat_printf(str, "Transactions count: %d\n", apl->transaction_counter); + if(apl->last_online_atc) + furi_string_cat_printf(str, "Last Online ATC: %d\n", apl->last_online_atc); + + const uint8_t len = apl->active_tr; + if(!len) { + furi_string_cat_printf(str, "No transactions info\n"); + return; + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* tmp = furi_string_alloc(); + + furi_string_cat_printf(str, "Transactions:\n"); + for(int i = 0; i < len; i++) { + //if(!apl->trans[i].amount) continue; - NO Skip here pls + // transaction counter + furi_string_cat_printf(str, "\e#%d: ", apl->trans[i].atc); + + // Print transaction amount + if(!apl->trans[i].amount) { + furi_string_cat_printf(str, "???"); + } else { + uint8_t* a = (uint8_t*)&apl->trans[i].amount; + bool top = true; + for(int x = 0; x < 6; x++) { + // cents + if(x == 5) { + furi_string_cat_printf(str, ".%02X", a[x]); + break; + } + if(a[x]) { + if(top) { + furi_string_cat_printf(str, "%X", a[x]); + top = false; + } else { + furi_string_cat_printf(str, "%02X", a[x]); + } + } + } + } + + if(apl->trans[i].currency) { + furi_string_set_str(tmp, "UNK"); + nfc_emv_parser_get_currency_name(storage, apl->trans[i].currency, tmp); + furi_string_cat_printf(str, " %s\n", furi_string_get_cstr(tmp)); + } + + if(apl->trans[i].country) { + furi_string_set_str(tmp, "UNK"); + nfc_emv_parser_get_country_name(storage, apl->trans[i].country, tmp); + furi_string_cat_printf(str, "Country: %s\n", furi_string_get_cstr(tmp)); + } + + if(apl->trans[i].date) + furi_string_cat_printf( + str, + "%02lx.%02lx.%02lx ", + apl->trans[i].date >> 16, + (apl->trans[i].date >> 8) & 0xff, + apl->trans[i].date & 0xff); + + if(apl->trans[i].time) + furi_string_cat_printf( + str, + "%02lx:%02lx:%02lx\n", + apl->trans[i].time & 0xff, + (apl->trans[i].time >> 8) & 0xff, + apl->trans[i].time >> 16); + } + + furi_string_free(tmp); + furi_record_close(RECORD_STORAGE); +} + +void nfc_render_emv_extra(const EmvData* data, FuriString* str) { + nfc_render_emv_application(&data->emv_application, str); + + nfc_render_emv_currency(data->emv_application.currency_code, str); + nfc_render_emv_country(data->emv_application.country_code, str); + nfc_render_emv_pin_try_counter(data->emv_application.pin_try_counter, str); +} diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h new file mode 100644 index 0000000000..855acdc4a8 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" +#include + +void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str); + +void nfc_render_emv_data(const EmvData* data, FuriString* str); + +void nfc_render_emv_pan(const uint8_t* data, const uint8_t len, FuriString* str); + +void nfc_render_emv_name(const char* data, FuriString* str); + +void nfc_render_emv_application(const EmvApplication* data, FuriString* str); + +void nfc_render_emv_extra(const EmvData* data, FuriString* str); + +void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str); + +void nfc_render_emv_country(uint16_t country_code, FuriString* str); + +void nfc_render_emv_currency(uint16_t cur_code, FuriString* str); + +void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str); + +void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* str); + +void nfc_render_emv_header(FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index f9c8491216..affa33b865 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) { const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); @@ -58,8 +59,8 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c index c0d502d038..99e2113017 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c @@ -13,6 +13,8 @@ static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) { const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str); @@ -95,8 +97,8 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEmulate) { +static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c index 7306f1072d..810242fbc6 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c @@ -6,13 +6,17 @@ void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const d } } +void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str) { + const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3'; + furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-A)\n", iso_type); +} + void nfc_render_iso14443_3a_info( const Iso14443_3aData* data, NfcProtocolFormatType format_type, FuriString* str) { if(format_type == NfcProtocolFormatTypeFull) { - const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3'; - furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type); + nfc_render_iso14443_tech_type(data, str); } nfc_render_iso14443_3a_brief(data, str); @@ -30,5 +34,5 @@ void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str) void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) { furi_string_cat_printf(str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); - furi_string_cat_printf(str, "SAK: %02X", data->sak); + furi_string_cat_printf(str, "\nSAK: %02X", data->sak); } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h index 14b91d221d..34e347aa35 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h @@ -9,6 +9,8 @@ void nfc_render_iso14443_3a_info( NfcProtocolFormatType format_type, FuriString* str); +void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str); + void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size); void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c index fee2318462..43b541111a 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c @@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) { const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str); @@ -59,8 +60,8 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) { furi_string_free(temp_str); } -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } @@ -68,7 +69,7 @@ bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t return false; } -static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) { +static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) { return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h index 53fe6b3927..6c7c2a0bc8 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h @@ -4,4 +4,4 @@ #include "iso14443_3b.h" -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event); +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c index 2e81d57a48..ec7efa84f9 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c @@ -6,7 +6,7 @@ void nfc_render_iso14443_3b_info( FuriString* str) { if(format_type == NfcProtocolFormatTypeFull) { const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3'; - furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type); + furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-B)\n", iso_type); } furi_string_cat_printf(str, "UID:"); @@ -20,7 +20,7 @@ void nfc_render_iso14443_3b_info( if(format_type != NfcProtocolFormatTypeFull) return; - furi_string_cat_printf(str, "\n\e#Protocol info\n"); + furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n"); if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) { furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); @@ -68,7 +68,7 @@ void nfc_render_iso14443_3b_info( iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not "; furi_string_cat_printf(str, "CID: %ssupported", cid_support_str); - furi_string_cat_printf(str, "\n\e#Application data\nRaw:"); + furi_string_cat_printf(str, "\n::::::::::::[Application data]::::::::::::\nRaw:"); size_t app_data_size; const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c index 0a3a592e17..17435ccd46 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c @@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) { const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str); @@ -99,8 +100,8 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEmulate) { +static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c index a963e744b9..4ff07d5963 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c @@ -14,11 +14,12 @@ void nfc_render_iso14443_4a_info( } void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) { + nfc_render_iso14443_tech_type(iso14443_4a_get_base_data(data), str); nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str); } void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) { - furi_string_cat_printf(str, "\n\e#Protocol info\n"); + furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n"); if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) { furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); @@ -72,7 +73,7 @@ void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count); if(hist_bytes_count > 0) { - furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:"); + furi_string_cat_printf(str, "\n:::::::::::::[Historical bytes]:::::::::::::\nRaw:"); for(size_t i = 0; i < hist_bytes_count; ++i) { furi_string_cat_printf(str, " %02X", hist_bytes[i]); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c index a0c70a22e9..8038e04912 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c @@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) { const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str); @@ -64,8 +65,8 @@ static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) { UNUSED(instance); } -static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEmulate) { +static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); return true; } @@ -73,7 +74,7 @@ static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t return false; } -static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { +static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); } diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c index 7f861a0326..7efd102f1a 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) { const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_reset(instance->widget); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); furi_string_free(temp_str); } +static void nfc_scene_more_info_on_enter_iso15693_3(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); + + FuriString* temp_str = furi_string_alloc(); + nfc_render_iso15693_3_system_info(data, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); +} + static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolIso15693_3); @@ -94,8 +108,8 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance); } -static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } @@ -104,13 +118,19 @@ static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t } const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { - .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid | + NfcProtocolFeatureMoreInfo, .scene_info = { .on_enter = nfc_scene_info_on_enter_iso15693_3, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, .scene_read = { .on_enter = nfc_scene_read_on_enter_iso15693_3, 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 92bdb22dc9..07b96d7018 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,50 +18,25 @@ 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); } } -void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { - furi_string_cat(str, "\n\e#General info\n"); - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { - furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref); - } - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { - furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi); - } - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { - furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref); - } - - furi_string_cat(str, "\e#Lock bits\n"); - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { - furi_string_cat_printf( - str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); - } - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { - furi_string_cat_printf( - str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); - } - +void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) { if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { furi_string_cat(str, "\e#Memory data\n\e*--------------------\n"); @@ -88,5 +63,34 @@ void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { "(Data is too big. Showing only the first %u bytes.)", display_block_count * block_size); } + } else { + furi_string_cat(str, "\e#No available data\n"); + } +} + +void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { + furi_string_cat(str, "\n::::::::::::::::[General info]:::::::::::::::::\n"); + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref); + } + + furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n"); + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf( + str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf( + str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); } } diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h index d531fd2eb0..87100102ae 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h @@ -12,3 +12,5 @@ void nfc_render_iso15693_3_info( void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str); void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str); + +void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str); 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 3e0468cd91..8ac4a2c6a9 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 @@ -21,8 +21,11 @@ static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); 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( @@ -97,8 +100,9 @@ static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) { nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance); } -static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) { - if(event == NfcCustomEventPollerIncomplete) { +static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && + event.event == NfcCustomEventPollerIncomplete) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); } @@ -119,13 +123,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( @@ -166,9 +172,9 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } -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); +static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); dolphin_deed(DolphinDeedNfcDetectReader); return true; } @@ -176,27 +182,29 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t e return false; } -static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { +static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { bool consumed = false; - if(event == SubmenuIndexDetectReader) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); - consumed = true; - } else if(event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); - consumed = true; - } else if(event == SubmenuIndexUpdate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); - consumed = true; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); + consumed = true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); + consumed = true; + } else if(event.event == SubmenuIndexUpdate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + consumed = true; + } } return consumed; } -static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) { +static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { bool consumed = false; - if(event == NfcCustomEventTextInputDone) { + if(event.type == SceneManagerEventTypeCustom && event.event == NfcCustomEventTextInputDone) { mf_classic_key_cache_save( instance->mfc_key_cache, nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic)); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c index 5bd4a6b6dd..0382b3333a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c @@ -18,13 +18,48 @@ void nfc_render_mf_classic_info( furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total); } +static void + mf_classic_render_raw_data(const uint8_t* data, size_t size, bool data_read, FuriString* str) { + furi_assert((size % 2) == 0); + + for(size_t i = 0; i < size; i += 2) { + if(data_read) { + furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]); + } else { + furi_string_cat_printf(str, "???? "); + } + } +} + +static void + mf_classic_render_block(const MfClassicData* data, uint8_t block_num, FuriString* str) { + if(mf_classic_is_sector_trailer(block_num)) { + uint8_t sec_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num); + + // Render key A + bool key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeA); + mf_classic_render_raw_data(sec_tr->key_a.data, sizeof(MfClassicKey), key_read, str); + + // Render access bits + bool access_bits_read = mf_classic_is_block_read(data, block_num); + mf_classic_render_raw_data( + sec_tr->access_bits.data, sizeof(MfClassicAccessBits), access_bits_read, str); + + // Render key B + key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeB); + mf_classic_render_raw_data(sec_tr->key_b.data, sizeof(MfClassicKey), key_read, str); + } else { + const uint8_t* block_data = data->block[block_num].data; + bool block_read = mf_classic_is_block_read(data, block_num); + mf_classic_render_raw_data(block_data, sizeof(MfClassicBlock), block_read, str); + } +} + void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) { uint16_t total_blocks = mf_classic_get_total_block_num(data->type); for(size_t i = 0; i < total_blocks; i++) { - for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) { - furi_string_cat_printf( - str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]); - } + mf_classic_render_block(data, i, str); } } diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c index bc05c2a4c3..ef51d98e0b 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -14,8 +14,10 @@ static void nfc_scene_info_on_enter_mf_desfire(NfcApp* instance) { const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); 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_desfire_info(data, NfcProtocolFormatTypeFull, temp_str); widget_add_text_scroll_element( @@ -56,6 +58,7 @@ static void nfc_scene_read_success_on_enter_mf_desfire(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_desfire_info(data, NfcProtocolFormatTypeShort, temp_str); widget_add_text_scroll_element( 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 4a8d4d7447..f0a46dd87d 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 @@ -2,6 +2,7 @@ #include "mf_ultralight_render.h" #include +#include #include "nfc/nfc_app_i.h" @@ -15,11 +16,18 @@ enum { SubmenuIndexWrite, }; +enum { + NfcSceneMoreInfoStateASCII, + NfcSceneMoreInfoStateRawData, +}; + static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, temp_str); @@ -35,11 +43,62 @@ static void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) { const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight); furi_string_reset(instance->text_box_store); - nfc_render_mf_ultralight_dump(mfu, instance->text_box_store); + uint32_t scene_state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo); + + if(scene_state == NfcSceneMoreInfoStateASCII) { + pretty_format_bytes_hex_canonical( + instance->text_box_store, + MF_ULTRALIGHT_PAGE_SIZE, + PRETTY_FORMAT_FONT_MONOSPACE, + (uint8_t*)mfu->page, + mfu->pages_read * MF_ULTRALIGHT_PAGE_SIZE); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Raw Data", + nfc_protocol_support_common_widget_callback, + instance); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Info", + nfc_protocol_support_common_widget_callback, + instance); + } else if(scene_state == NfcSceneMoreInfoStateRawData) { + nfc_render_mf_ultralight_dump(mfu, instance->text_box_store); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "ASCII", + nfc_protocol_support_common_widget_callback, + instance); + } +} + +static bool nfc_scene_more_info_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; - text_box_set_font(instance->text_box, TextBoxFontHex); - text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); + if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) || + (event.type == SceneManagerEventTypeBack)) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII); + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData); + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + consumed = true; + } + return consumed; } static NfcCommand @@ -132,15 +191,17 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* 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); +bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); + } else if((event.event == NfcCustomEventPollerIncomplete)) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } } return true; } @@ -202,14 +263,17 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } -static bool - nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexUnlock) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); - return true; - } else if(event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); - return true; +static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( + NfcApp* instance, + SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); + return true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); + return true; + } } return false; } @@ -225,7 +289,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { .scene_more_info = { .on_enter = nfc_scene_more_info_on_enter_mf_ultralight, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_more_info_on_event_mf_ultralight, }, .scene_read = { diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c index 5296f48071..1bc508adce 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -36,10 +36,11 @@ void nfc_render_mf_ultralight_info( } void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) { + furi_string_cat_printf(str, "\e*"); for(size_t i = 0; i < data->pages_read; i++) { const uint8_t* page_data = data->page[i].data; for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) { - furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]); + furi_string_cat_printf(str, " %02X%02X", page_data[j], page_data[j + 1]); } } } 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 bc152a7107..b8af5736d2 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -74,7 +74,7 @@ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) nfc_protocol_support_scenes[scene].on_exit(instance); } -static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { +bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { return nfc_protocol_support[protocol]->features & feature; } @@ -131,16 +131,15 @@ static bool nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) { bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { - const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event); - } + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event); return consumed; } static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) { text_box_reset(instance->text_box); + widget_reset(instance->widget); furi_string_reset(instance->text_box_store); } @@ -188,8 +187,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } else { const NfcProtocol protocol = instance->protocols_detected[instance->protocols_detected_selected_idx]; - consumed = - nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); + consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); } } else if(event.event == NfcCustomEventPollerFailure) { nfc_poller_stop(instance->poller); @@ -202,7 +200,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } 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); + consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { nfc_poller_stop(instance->poller); @@ -287,8 +285,7 @@ static bool consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = - nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event); + consumed = nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -391,12 +388,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", @@ -409,15 +409,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, @@ -456,8 +453,7 @@ static bool consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = - nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event); + consumed = nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -523,8 +519,8 @@ static bool DolphinDeedNfcSave); const NfcProtocol protocol = instance->protocols_detected[instance->protocols_detected_selected_idx]; - consumed = nfc_protocol_support[protocol]->scene_save_name.on_event( - instance, event.event); + consumed = + nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event); } else { consumed = scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcSceneStart); @@ -565,11 +561,11 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { FuriString* temp_str = furi_string_alloc(); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); + widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64); if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { widget_add_string_element( - widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID"); + widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating UID"); size_t uid_len; const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); @@ -581,14 +577,25 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { furi_string_trim(temp_str); } 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)); - furi_string_cat_printf(temp_str, "\n%s", furi_string_get_cstr(instance->file_name)); + widget_add_string_element( + widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating"); + if(!furi_string_empty(instance->file_name)) { + furi_string_printf( + temp_str, + "%s\n%s", + nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull), + furi_string_get_cstr(instance->file_name)); + } else { + furi_string_printf( + temp_str, + "Unsaved\n%s", + nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + furi_string_replace_str(temp_str, "Mifare", "MIFARE"); + } } widget_add_text_box_element( - widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false); + widget, 56, 33, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false); furi_string_free(temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h index b6bfde45c0..855642c621 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h @@ -76,6 +76,7 @@ #pragma once #include +#include #include "nfc_protocol_support_common.h" @@ -111,3 +112,5 @@ bool nfc_protocol_support_on_event( * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). */ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context); + +bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h index 69a6d34d29..eec736ca29 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h @@ -7,6 +7,7 @@ #include #include "../../nfc_app.h" +#include "../../nfc_app_i.h" /** * @brief Scene entry handler. @@ -19,10 +20,10 @@ typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance); * @brief Scene event handler. * * @param[in,out] instance pointer to the NFC application instance. - * @param[in] event custom event that has occurred. + * @param[in] event scene manager event that has occurred. * @returns true if the event was handled, false otherwise. */ -typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event); +typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, SceneManagerEvent event); /** * @brief Abstract scene interface. diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c index 215ffc4553..6b42a1660d 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -18,6 +18,7 @@ #include "mf_ultralight/mf_ultralight.h" #include "mf_classic/mf_classic.h" #include "mf_desfire/mf_desfire.h" +#include "emv/emv.h" #include "slix/slix.h" #include "st25tb/st25tb.h" @@ -41,5 +42,6 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, [NfcProtocolSlix] = &nfc_protocol_support_slix, [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, + [NfcProtocolEmv] = &nfc_protocol_support_emv, /* Add new protocol support implementations here */ }; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c index f3a8551255..8c38f84756 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c @@ -35,8 +35,8 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) { UNUSED(instance); } -bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) { +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event) { UNUSED(instance); UNUSED(event); - return true; + return event.type != SceneManagerEventTypeBack; } diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h index 40ba40c8ec..3230f1a7e4 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h @@ -7,6 +7,7 @@ #include #include "nfc/nfc_app.h" +#include "nfc/nfc_app_i.h" /** * @brief Common submenu indices. @@ -82,4 +83,4 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance); * @param[in] event custom event type that has occurred. * @returns always true. */ -bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event); +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event); diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index ad858a75fc..35592eaa12 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_slix(NfcApp* instance) { const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_reset(instance->widget); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); furi_string_free(temp_str); } +static void nfc_scene_more_info_on_enter_slix(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); + + FuriString* temp_str = furi_string_alloc(); + nfc_render_iso15693_3_system_info(slix_get_base_data(data), temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); +} + static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolSlix); @@ -91,8 +105,8 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) { nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance); } -static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } @@ -101,13 +115,18 @@ static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) } const NfcProtocolSupportBase nfc_protocol_support_slix = { - .features = NfcProtocolFeatureEmulateFull, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, .scene_info = { .on_enter = nfc_scene_info_on_enter_slix, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, .scene_read = { .on_enter = nfc_scene_read_on_enter_slix, diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.c b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c index 80f953db97..1be460194f 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix_render.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c @@ -1,14 +1,12 @@ #include "slix_render.h" -#include "../iso15693_3/iso15693_3_render.h" - void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) { nfc_render_iso15693_3_brief(slix_get_base_data(data), str); if(format_type != NfcProtocolFormatTypeFull) return; const SlixType slix_type = slix_get_type(data); - furi_string_cat(str, "\n\e#Passwords\n"); + furi_string_cat(str, "\n::::::::::::::::::[Passwords]:::::::::::::::::\n"); static const char* slix_password_names[] = { "Read", @@ -25,7 +23,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ } } - furi_string_cat(str, "\e#Lock bits\n"); + furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n"); if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { furi_string_cat_printf( @@ -38,7 +36,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ const SlixProtection protection = data->system_info.protection; - furi_string_cat(str, "\e#Page protection\n"); + furi_string_cat(str, "::::::::::::[Page protection]::::::::::::\n"); furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer); const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un"; @@ -52,12 +50,12 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ } if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { - furi_string_cat(str, "\e#Privacy\n"); + furi_string_cat(str, "::::::::::::::::::::[Privacy]::::::::::::::::::::::\n"); furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis"); } if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { - furi_string_cat(str, "\e#Signature\n"); + furi_string_cat(str, ":::::::::::::::::::[Signature]::::::::::::::::::\n"); for(uint32_t i = 0; i < 4; ++i) { furi_string_cat_printf(str, "%02X ", data->signature[i]); } diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.h b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h index 98ae6dc97f..bfc216382e 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix_render.h +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h @@ -3,5 +3,6 @@ #include #include "../nfc_protocol_support_render_common.h" +#include "../iso15693_3/iso15693_3_render.h" void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index e22af48b34..0305d614c8 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) { const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str); @@ -60,8 +61,8 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 183f498951..c0ca2a917f 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -1,4 +1,5 @@ #include "nfc_app_i.h" +#include "helpers/protocol_support/nfc_protocol_support.h" #include @@ -445,6 +446,15 @@ void nfc_app_reset_detected_protocols(NfcApp* instance) { instance->protocols_detected_num = 0; } +void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string) { + furi_assert(instance); + furi_assert(string); + + if(!furi_string_empty(instance->file_name)) { + furi_string_cat_printf(string, "Name:%s\n", furi_string_get_cstr(instance->file_name)); + } +} + static bool nfc_is_hal_ready() { if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { // No connection to the chip, show an error screen @@ -466,6 +476,15 @@ static bool nfc_is_hal_ready() { } } +static void nfc_show_initial_scene_for_device(NfcApp* nfc) { + NfcProtocol prot = nfc_device_get_protocol(nfc->nfc_device); + uint32_t scene = nfc_protocol_support_has_feature( + prot, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ? + NfcSceneEmulate : + NfcSceneSavedMenu; + scene_manager_next_scene(nfc->scene_manager, scene); +} + int32_t nfc_app(void* p) { if(!nfc_is_hal_ready()) return 0; @@ -485,7 +504,7 @@ int32_t nfc_app(void* p) { furi_string_set(nfc->file_path, args); if(nfc_load_file(nfc, nfc->file_path, false)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate); + nfc_show_initial_scene_for_device(nfc); } else { view_dispatcher_stop(nfc->view_dispatcher); } diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 943d722f82..ca3510fb48 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -30,6 +30,7 @@ #include "helpers/mf_ultralight_auth.h" #include "helpers/mf_user_dict.h" #include "helpers/mfkey32_logger.h" +#include "helpers/nfc_emv_parser.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" @@ -192,3 +193,5 @@ void nfc_make_app_folder(NfcApp* instance); void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count); void nfc_app_reset_detected_protocols(NfcApp* instance); + +void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string); diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c new file mode 100644 index 0000000000..a8253edffc --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -0,0 +1,134 @@ +/* + * Parser for EMV cards. + * + * Copyright 2023 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 "core/string.h" +#include "furi_hal_rtc.h" +#include "helpers/nfc_emv_parser.h" +#include "nfc_supported_card_plugin.h" + +#include "protocols/emv/emv.h" +#include "protocols/nfc_protocol.h" +#include + +#include +#include + +#define TAG "EMV" + +bool emv_get_currency_name(uint16_t cur_code, FuriString* currency_name) { + if(!cur_code) return false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool succsess = nfc_emv_parser_get_currency_name(storage, cur_code, currency_name); + + furi_record_close(RECORD_STORAGE); + return succsess; +} + +bool emv_get_country_name(uint16_t country_code, FuriString* country_name) { + if(!country_code) return false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool succsess = nfc_emv_parser_get_country_name(storage, country_code, country_name); + + furi_record_close(RECORD_STORAGE); + return succsess; +} + +bool emv_get_aid_name(const EmvApplication* apl, FuriString* aid_name) { + const uint8_t len = apl->aid_len; + + if(!len) return false; + + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool succsess = nfc_emv_parser_get_aid_name(storage, apl->aid, len, aid_name); + + furi_record_close(RECORD_STORAGE); + return succsess; +} + +static bool emv_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + bool parsed = false; + + const EmvData* data = nfc_device_get_data(device, NfcProtocolEmv); + const EmvApplication app = data->emv_application; + + do { + if(app.name_found) + furi_string_cat_printf(parsed_data, "\e#%s\n", app.name); + else + furi_string_cat_printf(parsed_data, "\e#%s\n", "EMV"); + + if(app.pan_len) { + FuriString* pan = furi_string_alloc(); + for(uint8_t i = 0; i < app.pan_len; i += 2) { + furi_string_cat_printf(pan, "%02X%02X ", app.pan[i], app.pan[i + 1]); + } + + // Cut padding 'F' from card number + size_t end = furi_string_search_rchar(pan, 'F'); + if(end) furi_string_left(pan, end); + furi_string_cat(parsed_data, pan); + furi_string_free(pan); + } + + if(app.exp_month | app.exp_year) + furi_string_cat_printf(parsed_data, "\nExp: %02X/%02X\n", app.exp_month, app.exp_year); + + FuriString* str = furi_string_alloc(); + bool storage_readed = emv_get_country_name(app.country_code, str); + + if(storage_readed) + furi_string_cat_printf(parsed_data, "Country: %s\n", furi_string_get_cstr(str)); + + storage_readed = emv_get_currency_name(app.currency_code, str); + if(storage_readed) + furi_string_cat_printf(parsed_data, "Currency: %s\n", furi_string_get_cstr(str)); + + if(app.pin_try_counter != 0xFF) + furi_string_cat_printf(parsed_data, "PIN attempts left: %d\n", app.pin_try_counter); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin emv_plugin = { + .protocol = NfcProtocolEmv, + .verify = NULL, + .read = NULL, + .parse = emv_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor emv_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &emv_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* emv_plugin_ep() { + return &emv_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index f14c113693..a1bcbb1f83 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -328,6 +328,8 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { last_trip.minute); } + furi_string_free(tariff_name); + parsed = true; } while(false); diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c index 063f8ffccf..2b33da153b 100644 --- a/applications/main/nfc/plugins/supported_cards/metromoney.c +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -147,7 +147,7 @@ static bool metromoney_parse(const NfcDevice* device, FuriString* parsed_data) { const uint8_t* block_start_ptr = &data->block[start_block_num + ticket_block_number].data[0]; - uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4); + uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4) - 100; uint32_t balance_lari = balance / 100; uint8_t balance_tetri = balance % 100; diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c new file mode 100644 index 0000000000..a40f59a162 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -0,0 +1,475 @@ +// Parser for NDEF format data +// Supports multiple NDEF messages and records in same tag +// Parsed types: URI (+ Phone, Mail), Text, BT MAC, Contact, WiFi, Empty +// Documentation and sources indicated where relevant +// Made by @Willy-JL + +#include "nfc_supported_card_plugin.h" + +#include +#include +#include + +#define TAG "NDEF" + +static bool is_text(const uint8_t* buf, size_t len) { + for(size_t i = 0; i < len; i++) { + const char c = buf[i]; + if((c < ' ' || c > '~') && c != '\r' && c != '\n') { + return false; + } + } + return true; +} + +static void + print_data(FuriString* str, const char* prefix, const uint8_t* buf, size_t len, bool force_hex) { + if(prefix) furi_string_cat_printf(str, "%s: ", prefix); + if(!force_hex && is_text(buf, len)) { + char* tmp = malloc(len + 1); + memcpy(tmp, buf, len); + tmp[len] = '\0'; + furi_string_cat_printf(str, "%s", tmp); + free(tmp); + } else { + for(uint8_t i = 0; i < len; i++) { + furi_string_cat_printf(str, "%02X ", buf[i]); + } + } + furi_string_cat(str, "\n"); +} + +static void parse_ndef_uri(FuriString* str, const uint8_t* payload, uint32_t payload_len) { + // https://learn.adafruit.com/adafruit-pn532-rfid-nfc/ndef#uri-records-0x55-slash-u-607763 + const char* prepends[] = { + [0x00] = "", + [0x01] = "http://www.", + [0x02] = "https://www.", + [0x03] = "http://", + [0x04] = "https://", + [0x05] = "tel:", + [0x06] = "mailto:", + [0x07] = "ftp://anonymous:anonymous@", + [0x08] = "ftp://ftp.", + [0x09] = "ftps://", + [0x0A] = "sftp://", + [0x0B] = "smb://", + [0x0C] = "nfs://", + [0x0D] = "ftp://", + [0x0E] = "dav://", + [0x0F] = "news:", + [0x10] = "telnet://", + [0x11] = "imap:", + [0x12] = "rtsp://", + [0x13] = "urn:", + [0x14] = "pop:", + [0x15] = "sip:", + [0x16] = "sips:", + [0x17] = "tftp:", + [0x18] = "btspp://", + [0x19] = "btl2cap://", + [0x1A] = "btgoep://", + [0x1B] = "tcpobex://", + [0x1C] = "irdaobex://", + [0x1D] = "file://", + [0x1E] = "urn:epc:id:", + [0x1F] = "urn:epc:tag:", + [0x20] = "urn:epc:pat:", + [0x21] = "urn:epc:raw:", + [0x22] = "urn:epc:", + [0x23] = "urn:nfc:", + }; + const char* prepend = ""; + uint8_t prepend_type = payload[0]; + if(prepend_type < COUNT_OF(prepends)) { + prepend = prepends[prepend_type]; + } + size_t prepend_len = strlen(prepend); + + size_t uri_len = prepend_len + (payload_len - 1); + char* const uri_buf = malloc(uri_len); + memcpy(uri_buf, prepend, prepend_len); + memcpy(uri_buf + prepend_len, payload + 1, payload_len - 1); + char* uri = uri_buf; + + const char* type = "URI"; + if(strncmp(uri, "http", strlen("http")) == 0) { + type = "URL"; + } else if(strncmp(uri, "tel:", strlen("tel:")) == 0) { + type = "Phone"; + uri += strlen("tel:"); + uri_len -= strlen("tel:"); + } else if(strncmp(uri, "mailto:", strlen("mailto:")) == 0) { + type = "Mail"; + uri += strlen("mailto:"); + uri_len -= strlen("mailto:"); + } + + furi_string_cat_printf(str, "%s\n", type); + print_data(str, NULL, (uint8_t*)uri, uri_len, false); + free(uri_buf); +} + +static void parse_ndef_text(FuriString* str, const uint8_t* payload, uint32_t payload_len) { + furi_string_cat(str, "Text\n"); + print_data(str, NULL, payload + 3, payload_len - 3, false); +} + +static void parse_ndef_bt(FuriString* str, const uint8_t* payload, uint32_t payload_len) { + furi_string_cat(str, "BT MAC\n"); + print_data(str, NULL, payload + 2, payload_len - 2, true); +} + +static void parse_ndef_vcard(FuriString* str, const uint8_t* payload, uint32_t payload_len) { + char* tmp = malloc(payload_len + 1); + memcpy(tmp, payload, payload_len); + tmp[payload_len] = '\0'; + FuriString* fmt = furi_string_alloc_set(tmp); + free(tmp); + + furi_string_trim(fmt); + if(furi_string_start_with(fmt, "BEGIN:VCARD")) { + furi_string_right(fmt, furi_string_search_char(fmt, '\n')); + if(furi_string_end_with(fmt, "END:VCARD")) { + furi_string_left(fmt, furi_string_search_rchar(fmt, '\n')); + } + furi_string_trim(fmt); + if(furi_string_start_with(fmt, "VERSION:")) { + furi_string_right(fmt, furi_string_search_char(fmt, '\n')); + furi_string_trim(fmt); + } + } + + furi_string_cat(str, "Contact\n"); + print_data(str, NULL, (uint8_t*)furi_string_get_cstr(fmt), furi_string_size(fmt), false); + furi_string_free(fmt); +} + +static void parse_ndef_wifi(FuriString* str, const uint8_t* payload, uint32_t payload_len) { +// https://android.googlesource.com/platform/packages/apps/Nfc/+/refs/heads/main/src/com/android/nfc/NfcWifiProtectedSetup.java +#define CREDENTIAL_FIELD_ID (0x100E) +#define SSID_FIELD_ID (0x1045) +#define NETWORK_KEY_FIELD_ID (0x1027) +#define AUTH_TYPE_FIELD_ID (0x1003) +#define AUTH_TYPE_EXPECTED_SIZE (2) +#define AUTH_TYPE_OPEN (0x0001) +#define AUTH_TYPE_WPA_PSK (0x0002) +#define AUTH_TYPE_WPA_EAP (0x0008) +#define AUTH_TYPE_WPA2_EAP (0x0010) +#define AUTH_TYPE_WPA2_PSK (0x0020) +#define AUTH_TYPE_WPA_AND_WPA2_PSK (0x0022) +#define MAX_NETWORK_KEY_SIZE_BYTES (64) + + size_t i = 0; + while(i < payload_len) { + uint16_t field_id = nfc_util_bytes2num(payload + i, 2); + i += 2; + uint16_t field_len = nfc_util_bytes2num(payload + i, 2); + i += 2; + + if(field_id == CREDENTIAL_FIELD_ID) { + furi_string_cat(str, "WiFi\n"); + size_t start_position = i; + while(i < start_position + field_len) { + uint16_t cfg_id = nfc_util_bytes2num(payload + i, 2); + i += 2; + uint16_t cfg_len = nfc_util_bytes2num(payload + i, 2); + i += 2; + + if(i + cfg_len > start_position + field_len) { + return; + } + + switch(cfg_id) { + case SSID_FIELD_ID: + print_data(str, "SSID", payload + i, cfg_len, false); + i += cfg_len; + break; + case NETWORK_KEY_FIELD_ID: + if(cfg_len > MAX_NETWORK_KEY_SIZE_BYTES) { + return; + } + print_data(str, "PWD", payload + i, cfg_len, false); + i += cfg_len; + break; + case AUTH_TYPE_FIELD_ID: + if(cfg_len != AUTH_TYPE_EXPECTED_SIZE) { + return; + } + short auth_type = nfc_util_bytes2num(payload + i, 2); + i += 2; + const char* auth; + switch(auth_type) { + case AUTH_TYPE_OPEN: + auth = "Open"; + break; + case AUTH_TYPE_WPA_PSK: + auth = "WPA Personal"; + break; + case AUTH_TYPE_WPA_EAP: + auth = "WPA Enterprise"; + break; + case AUTH_TYPE_WPA2_EAP: + auth = "WPA2 Enterprise"; + break; + case AUTH_TYPE_WPA2_PSK: + auth = "WPA2 Personal"; + break; + case AUTH_TYPE_WPA_AND_WPA2_PSK: + auth = "WPA/WPA2 Personal"; + break; + default: + auth = "Unknown"; + break; + } + print_data(str, "AUTH", (uint8_t*)auth, strlen(auth), false); + break; + default: + i += cfg_len; + break; + } + } + return; + } + i += field_len; + } +} + +static void parse_ndef_payload( + FuriString* str, + uint8_t tnf, + const char* type, + uint8_t type_len, + const uint8_t* payload, + uint32_t payload_len) { + if(!payload_len) { + furi_string_cat(str, "Empty\n"); + return; + } + switch(tnf) { + case 0x01: // NFC Forum well-known type [NFC RTD] + if(strncmp("U", type, type_len) == 0) { + parse_ndef_uri(str, payload, payload_len); + } else if(strncmp("T", type, type_len) == 0) { + parse_ndef_text(str, payload, payload_len); + } else { + print_data(str, "Well-known type", (uint8_t*)type, type_len, false); + print_data(str, "Payload", payload, payload_len, false); + } + break; + case 0x02: // Media-type [RFC 2046] + if(strncmp("application/vnd.bluetooth.ep.oob", type, type_len) == 0) { + parse_ndef_bt(str, payload, payload_len); + } else if(strncmp("text/vcard", type, type_len) == 0) { + parse_ndef_vcard(str, payload, payload_len); + } else if(strncmp("application/vnd.wfa.wsc", type, type_len) == 0) { + parse_ndef_wifi(str, payload, payload_len); + } else { + print_data(str, "Media Type", (uint8_t*)type, type_len, false); + print_data(str, "Payload", payload, payload_len, false); + } + break; + case 0x00: // Empty + case 0x03: // Absolute URI [RFC 3986] + case 0x04: // NFC Forum external type [NFC RTD] + case 0x05: // Unknown + case 0x06: // Unchanged + case 0x07: // Reserved + default: // Unknown + // Dump data without parsing + print_data(str, "Type name format", &tnf, 1, true); + print_data(str, "Type", (uint8_t*)type, type_len, false); + print_data(str, "Payload", payload, payload_len, false); + break; + } +} + +static const uint8_t* parse_ndef_message( + FuriString* str, + size_t message_num, + const uint8_t* cur, + const uint8_t* message_end) { + // NDEF message and record documentation: + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/protocols/nfc/index.html#ndef-message-and-record-format + size_t record_num = 0; + bool last_record = false; + while(cur < message_end) { + // Flags and TNF + uint8_t flags_tnf = *cur++; + // Message Begin should only be set on first record + if(record_num++ && flags_tnf & (1 << 7)) break; + // Message End should only be set on last record + if(last_record) break; + if(flags_tnf & (1 << 6)) last_record = true; + // Chunked Flag not supported + if(flags_tnf & (1 << 5)) break; + // Payload Length field of 1 vs 4 bytes + bool short_record = flags_tnf & (1 << 4); + // Is payload ID length and value present + bool id_present = flags_tnf & (1 << 3); + // Type Name Format 3 bit value + uint8_t tnf = flags_tnf & 0b00000111; + + // Type Length + uint8_t type_len = *cur++; + + // Payload Length + uint32_t payload_len; + if(short_record) { + payload_len = *cur++; + } else { + payload_len = nfc_util_bytes2num(cur, 4); + cur += 4; + } + + // ID Length + uint8_t id_len = 0; + if(id_present) { + id_len = *cur++; + } + + // Payload Type + char* type = NULL; + if(type_len) { + type = malloc(type_len); + memcpy(type, cur, type_len); + cur += type_len; + } + + // Payload ID + cur += id_len; + + furi_string_cat_printf(str, "\e*> M:%d R:%d - ", message_num, record_num); + parse_ndef_payload(str, tnf, type, type_len, cur, payload_len); + cur += payload_len; + + free(type); + furi_string_trim(str, "\n"); + furi_string_cat(str, "\n\n"); + } + return cur; +} + +static bool ndef_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + // Memory layout documentation: + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nfc/doc/type_2_tag.html#id2 + + // Double check static values layout + // First 4 static reserved pages for UID, internal and lock bytes + // (Not sure if NDEF cata can be found in cards with different layout) + if(data->page[0].data[0] != 0x04) break; + if(data->page[2].data[1] != 0x48) break; // Internal + if(data->page[2].data[2] != 0x00) break; // Lock bytes + if(data->page[2].data[3] != 0x00) break; // ... + if(data->page[3].data[0] != 0xE1) break; // Capability container + if(data->page[3].data[1] != 0x10) break; // ... + + // Data content starts here at 5th page + const uint8_t* cur = &data->page[4].data[0]; + const uint8_t* end = &data->page[0].data[0] + + (mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE); + size_t message_num = 0; + + // Parse as TLV (see docs above) + while(cur < end) { + switch(*cur++) { + case 0x03: { // NDEF message + if(cur >= end) break; + uint16_t len; + if(*cur < 0xFF) { // 1 byte length + len = *cur++; + } else { // 3 byte length (0xFF marker + 2 byte integer) + if(cur + 2 >= end) { + cur = end; + break; + } + len = nfc_util_bytes2num(++cur, 2); + cur += 2; + } + if(cur + len >= end) { + cur = end; + break; + } + + if(message_num++ == 0) { + furi_string_printf( + parsed_data, + "\e#NDEF Format Data\nCard type: %s\n", + mf_ultralight_get_device_name(data, NfcDeviceNameTypeFull)); + } + + const uint8_t* message_end = cur + len; + cur = parse_ndef_message(parsed_data, message_num, cur, message_end); + if(cur != message_end) cur = end; + + break; + } + + case 0xFE: // TLV end + cur = end; + if(message_num != 0) parsed = true; + break; + + case 0x00: // Padding, has no length, skip + break; + + case 0x01: // Lock control + case 0x02: // Memory control + case 0xFD: // Proprietary + // We don't care, skip this TLV block + if(cur >= end) break; + if(*cur < 0xFF) { // 1 byte length + cur += *cur + 1; // Shift by TLV length + } else { // 3 byte length (0xFF marker + 2 byte integer) + if(cur + 2 >= end) { + cur = end; + break; + } + cur += nfc_util_bytes2num(cur + 1, 2) + 3; // Shift by TLV length + } + break; + + default: // Unknown, bail to avoid problems + cur = end; + break; + } + } + + if(parsed) { + furi_string_trim(parsed_data, "\n"); + furi_string_cat(parsed_data, "\n"); + } else { + furi_string_reset(parsed_data); + } + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin ndef_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = ndef_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor ndef_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &ndef_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* ndef_plugin_ep() { + return &ndef_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index 79a840d9a4..86ea91f997 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -161,7 +161,7 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, - "\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u USD", + "\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u EUR", uid_len * 2, card_number, balance_usd, diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c index 2e4f515984..1778cd38e8 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c @@ -34,11 +34,6 @@ #define PURSE_SECTOR_NUM (6) #define INFO_SECTOR_NUM (15) -typedef struct { - uint64_t a; - uint64_t b; -} MfClassicKeyPair; - // Sector 15 data. Byte [11] contains the mistake. If byte [11] was 0xEF, bytes [1-18] means "ЗАО Золотая Корона" static const uint8_t info_sector_signature[] = {0xE2, 0x87, 0x80, 0x8E, 0x20, 0x87, 0xAE, 0xAB, 0xAE, 0xF2, 0xA0, 0xEF, 0x20, 0x8A, @@ -76,19 +71,24 @@ void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; } -uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes) { +uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) { furi_assert(src); + furi_assert(len_bytes <= 9); - uint64_t res = 0; + uint64_t result = 0; + *is_bcd = true; for(uint8_t i = 0; i < len_bytes; i++) { - res *= 10; - res += src[i] / 16; - res *= 10; - res += src[i] % 16; + if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false; + + result *= 10; + result += src[i] / 16; + + result *= 10; + result += src[i] % 16; } - return res; + return result; } static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_data) { @@ -121,12 +121,12 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da // INFO SECTOR // block 1 - const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1); + const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1, &verified); // block 2 block_start_ptr = &data->block[start_info_block_number + 2].data[4]; - const uint64_t card_number = - bytes2num_bcd(block_start_ptr, 9) * 10 + bytes2num_bcd(block_start_ptr + 9, 1) / 10; + const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &verified); + const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &verified) / 10; // TRIP SECTOR const uint8_t start_trip_block_number = @@ -157,7 +157,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da block_start_ptr = &data->block[start_trip_block_number + 2].data[0]; const char validator_first_letter = nfc_util_bytes2num_little_endian(block_start_ptr + 1, 1); - const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3); + const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3, &verified); const uint32_t last_trip_timestamp = nfc_util_bytes2num_little_endian(block_start_ptr + 6, 4); const uint8_t track_number = nfc_util_bytes2num_little_endian(block_start_ptr + 10, 1); @@ -174,15 +174,16 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da block_start_ptr = &data->block[start_purse_block_number].data[0]; // block 0 - uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4); + const uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4); uint32_t balance_rub = balance / 100; uint8_t balance_kop = balance % 100; furi_string_cat_printf( parsed_data, - "\e#Zolotaya korona\nCard number: %llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR", - card_number, + "\e#Zolotaya korona\nCard number: %u%015llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR", + card_number_prefix, + card_number_postfix, region_number, balance_rub, balance_kop, diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c new file mode 100644 index 0000000000..eb992763e3 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c @@ -0,0 +1,166 @@ +/* + * Parser for Zolotaya Korona Online card (Russia). + * Tariffs research by DNZ1393 + * + * Copyright 2023 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 "furi_hal_rtc.h" +#include "nfc_supported_card_plugin.h" + +#include "protocols/mf_classic/mf_classic.h" +#include + +#include +#include +#include + +#define TAG "Zolotaya Korona Online" + +#define TRIP_SECTOR_NUM (4) +#define INFO_SECTOR_NUM (15) + +uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) { + furi_assert(src); + furi_assert(len_bytes <= 9); + + uint64_t result = 0; + *is_bcd = true; + + for(uint8_t i = 0; i < len_bytes; i++) { + if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false; + + result *= 10; + result += src[i] / 16; + + result *= 10; + result += src[i] % 16; + } + + return result; +} + +bool parse_online_card_tariff(uint16_t tariff_num, FuriString* tariff_name) { + bool tariff_parsed = false; + + switch(tariff_num) { + case 0x0100: + furi_string_set_str(tariff_name, "Standart (online)"); + tariff_parsed = true; + break; + case 0x0101: + case 0x0121: + furi_string_set_str(tariff_name, "Standart (airtag)"); + tariff_parsed = true; + break; + case 0x0401: + furi_string_set_str(tariff_name, "Student (50%% discount)"); + tariff_parsed = true; + break; + case 0x0402: + furi_string_set_str(tariff_name, "Student (travel)"); + tariff_parsed = true; + break; + case 0x0002: + furi_string_set_str(tariff_name, "School (50%% discount)"); + tariff_parsed = true; + break; + case 0x0505: + furi_string_set_str(tariff_name, "Social (large families)"); + tariff_parsed = true; + break; + case 0x0528: + furi_string_set_str(tariff_name, "Social (handicapped)"); + tariff_parsed = true; + break; + default: + furi_string_set_str(tariff_name, "Unknown"); + tariff_parsed = false; + break; + } + + return tariff_parsed; +} + +static bool zolotaya_korona_online_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify info sector data (card number prefix) + const uint8_t start_trip_block_number = + mf_classic_get_first_block_num_of_sector(TRIP_SECTOR_NUM); + const uint8_t start_info_block_number = + mf_classic_get_first_block_num_of_sector(INFO_SECTOR_NUM); + const uint8_t* block_start_ptr = &data->block[start_info_block_number].data[3]; + + // Validate card number + bool is_bcd; + const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &is_bcd); + if(!is_bcd) break; + if(card_number_prefix != 9643) break; + const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &is_bcd) / 10; + if(!is_bcd) break; + + // Parse data + FuriString* tariff_name = furi_string_alloc(); + + block_start_ptr = &data->block[start_info_block_number].data[1]; + const uint16_t tariff = nfc_util_bytes2num(block_start_ptr, 2); + parse_online_card_tariff(tariff, tariff_name); + + block_start_ptr = &data->block[start_trip_block_number].data[0]; + const uint8_t region_number = nfc_util_bytes2num(block_start_ptr, 1); + + furi_string_cat_printf( + parsed_data, + "\e#Zolotaya korona\nCard number: %u%015llu\nTariff: %02X.%02X: %s\nRegion: %u\n", + card_number_prefix, + card_number_postfix, + tariff / 256, + tariff % 256, + furi_string_get_cstr(tariff_name), + region_number); + + furi_string_free(tariff_name); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin zolotaya_korona_online_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, + .read = NULL, + .parse = zolotaya_korona_online_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor zolotaya_korona_online_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &zolotaya_korona_online_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep() { + return &zolotaya_korona_online_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a9887996d6..ad500b7172 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) @@ -36,6 +37,8 @@ ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass) ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) +ADD_SCENE(nfc, emv_more_info, EmvMoreInfo) + ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader) ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index c1a676168a..924ed78faf 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -51,8 +51,11 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { if(nfc_delete(nfc)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { - scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); + if(!scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneStart)) { + scene_manager_stop(nfc->scene_manager); + view_dispatcher_stop(nfc->view_dispatcher); + } } consumed = true; } diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index 73856c292a..d41e525493 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -31,6 +31,11 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); + + if(!consumed) { + scene_manager_stop(nfc->scene_manager); + view_dispatcher_stop(nfc->view_dispatcher); + } } } } diff --git a/applications/main/nfc/scenes/nfc_scene_emulate.c b/applications/main/nfc/scenes/nfc_scene_emulate.c index 6f217f3154..0f178f463a 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate.c @@ -1,13 +1,47 @@ #include "../helpers/protocol_support/nfc_protocol_support.h" +#include "nfc_app_i.h" + +#define NFC_EMULATION_TIME_MAX_MS (5 * 60 * 1000) + +FuriTimer* timer_auto_exit; + +void nfc_scene_emulate_timer_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventEmulationTimeExpired); +} + void nfc_scene_emulate_on_enter(void* context) { + NfcApp* instance = context; + nfc_protocol_support_on_enter(NfcProtocolSupportSceneEmulate, context); + + timer_auto_exit = + furi_timer_alloc(nfc_scene_emulate_timer_callback, FuriTimerTypeOnce, instance); + furi_timer_start(timer_auto_exit, NFC_EMULATION_TIME_MAX_MS); } bool nfc_scene_emulate_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventEmulationTimeExpired) { + if(!scene_manager_previous_scene(instance->scene_manager)) { + scene_manager_stop(instance->scene_manager); + view_dispatcher_stop(instance->view_dispatcher); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + return true; + } + } return nfc_protocol_support_on_event(NfcProtocolSupportSceneEmulate, context, event); } void nfc_scene_emulate_on_exit(void* context) { + furi_timer_stop(timer_auto_exit); + furi_timer_free(timer_auto_exit); nfc_protocol_support_on_exit(NfcProtocolSupportSceneEmulate, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_emv_more_info.c b/applications/main/nfc/scenes/nfc_scene_emv_more_info.c new file mode 100644 index 0000000000..0cddce20a1 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_emv_more_info.c @@ -0,0 +1,74 @@ +#include "../nfc_app_i.h" + +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/emv/emv_render.h" + +enum { + EmvMoreInfoStateMenu, + EmvMoreInfoStateItem, // MUST be last, states >= this correspond with submenu index +}; + +enum SubmenuIndex { + SubmenuIndexTransactions, + SubmenuIndexDynamic, // dynamic indices start here +}; + +void nfc_scene_emv_more_info_on_enter(void* context) { + NfcApp* nfc = context; + Submenu* submenu = nfc->submenu; + + text_box_set_font(nfc->text_box, TextBoxFontHex); + + submenu_add_item( + submenu, + "Transactions", + SubmenuIndexTransactions, + nfc_protocol_support_common_submenu_callback, + nfc); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_emv_more_info_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + bool consumed = false; + + const uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMoreInfo); + const EmvData* data = nfc_device_get_data(nfc->nfc_device, NfcProtocolEmv); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexTransactions) { + FuriString* temp_str = furi_string_alloc(); + nfc_render_emv_transactions(&data->emv_application, temp_str); + widget_add_text_scroll_element( + nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); + scene_manager_set_scene_state( + nfc->scene_manager, + NfcSceneEmvMoreInfo, + EmvMoreInfoStateItem + SubmenuIndexTransactions); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + if(state >= EmvMoreInfoStateItem) { + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); + scene_manager_set_scene_state( + nfc->scene_manager, NfcSceneEmvMoreInfo, EmvMoreInfoStateMenu); + } else { + // Return directly to the Info scene + scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneInfo); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_emv_more_info_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear views + widget_reset(nfc->widget); + submenu_reset(nfc->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 721919d2b1..d14f80b624 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 987f81837a..e2d3e6d72f 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 22727af122..328e39132f 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 @@ -173,7 +173,6 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); - instance->nfc_dict_context.is_card_present = true; } static void nfc_scene_mf_classic_dict_attack_notify_read(NfcApp* instance) { 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 8e07043e25..eb0aa7c3ae 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_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c index 79f1def1d1..da576a276c 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_ultralight_write.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c index b3c1beef5a..157d6ce1b3 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_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c index 9726ef283f..bb34190d20 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 @@ -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_save_confirm.c b/applications/main/nfc/scenes/nfc_scene_save_confirm.c new file mode 100644 index 0000000000..9d0a206d32 --- /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 9d2a380137..230e9a1a64 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -28,9 +28,16 @@ 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( + consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); + if(!consumed) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneSavedMenu); + } } } } diff --git a/applications/main/nfc/scenes/nfc_scene_select_protocol.c b/applications/main/nfc/scenes/nfc_scene_select_protocol.c index 7a5d125218..52b2664ec6 100644 --- a/applications/main/nfc/scenes/nfc_scene_select_protocol.c +++ b/applications/main/nfc/scenes/nfc_scene_select_protocol.c @@ -29,6 +29,8 @@ void nfc_scene_select_protocol_on_enter(void* context) { "%s %s", prefix, nfc_device_get_protocol_name(instance->protocols_detected[i])); + + furi_string_replace_str(temp_str, "Mifare", "MIFARE"); submenu_add_item( submenu, furi_string_get_cstr(temp_str), diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index ff82587df3..21c0d91dba 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 d832d27d65..4d7b324e0a 100644 --- a/applications/main/nfc/views/detect_reader.c +++ b/applications/main/nfc/views/detect_reader.c @@ -50,7 +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, 20, 23, &I_check_big_20x17); + 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 6838b345de..ad26123eae 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -24,12 +24,14 @@ typedef enum { SubmenuIndexSommer_FM_868, SubmenuIndexStilmatic, SubmenuIndexDTMNeo433, + SubmenuIndexDeaMio433, SubmenuIndexGibidi433, SubmenuIndexNiceMHouse_433_92, SubmenuIndexJCM_433_92, SubmenuIndexFAACRCXT_433_92, SubmenuIndexFAACRCXT_868, SubmenuIndexNormstahl_433_92, + SubmenuIndexGeniusBravo433, SubmenuIndexGSN, SubmenuIndexAprimatic, SubmenuIndexHCS101_433_92, diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 6551e0425b..c198f2fe29 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -81,7 +81,6 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { uint32_t frequency = 0; float rssi_temp = 0; uint32_t frequency_temp = 0; - CC1101Status status; FuriHalSpiBusHandle* spi_bus = instance->spi_bus; const SubGhzDevice* radio_device = instance->radio_device; @@ -143,9 +142,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { frequency = cc1101_set_frequency(spi_bus, current_frequency); cc1101_calibrate(spi_bus); - do { - status = cc1101_get_status(spi_bus); - } while(status.STATE != CC1101StateIDLE); + + furi_check(cc1101_wait_status_state(spi_bus, CC1101StateIDLE, 10000)); cc1101_switch_to_rx(spi_bus); furi_hal_spi_release(spi_bus); @@ -191,9 +189,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { frequency = cc1101_set_frequency(spi_bus, i); cc1101_calibrate(spi_bus); - do { - status = cc1101_get_status(spi_bus); - } while(status.STATE != CC1101StateIDLE); + + furi_check(cc1101_wait_status_state(spi_bus, CC1101StateIDLE, 10000)); cc1101_switch_to_rx(spi_bus); furi_hal_spi_release(spi_bus); diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index 27b7abbf48..308f6dbb33 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -63,6 +63,10 @@ bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent e #ifdef FURI_DEBUG subghz_last_settings_log(subghz->last_settings); #endif + // Disable Hopping before opening the receiver scene! + if(subghz->last_settings->enable_hopping) { + subghz->last_settings->enable_hopping = false; + } subghz_last_settings_save(subghz->last_settings); } diff --git a/applications/main/subghz/scenes/subghz_scene_need_saving.c b/applications/main/subghz/scenes/subghz_scene_need_saving.c index 8259b6e825..5d4d9fd4e9 100644 --- a/applications/main/subghz/scenes/subghz_scene_need_saving.c +++ b/applications/main/subghz/scenes/subghz_scene_need_saving.c @@ -50,6 +50,12 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE); if(state == SubGhzRxKeyStateExit) { + if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) { + if(!furi_string_empty(subghz->file_path_tmp)) { + subghz_delete_file(subghz); + } + } + subghz_txrx_set_preset( subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0); scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 22d78d68d6..b6ae9f9dc2 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -144,6 +144,11 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { if((subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) || (subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateBack)) { subghz_rx_key_state_set(subghz, SubGhzRxKeyStateExit); + if(subghz_scene_read_raw_update_filename(subghz)) { + furi_string_set(subghz->file_path_tmp, subghz->file_path); + } else { + furi_string_reset(subghz->file_path_tmp); + } scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNeedSaving); } else { //Restore default setting @@ -178,7 +183,8 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; case SubGhzCustomEventViewReadRAWErase: - if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) { + if((subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateAddKey) || + (subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateBack)) { if(subghz_scene_read_raw_update_filename(subghz)) { furi_string_set(subghz->file_path_tmp, subghz->file_path); subghz_delete_file(subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 807cc3ba23..5cf79eabc2 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -165,6 +165,9 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) if(subghz_txrx_protocol_is_serializable(subghz->txrx)) { subghz_file_name_clear(subghz); + subghz->save_datetime = + subghz_history_get_datetime(subghz->history, subghz->idx_menu_chosen); + subghz->save_datetime_set = true; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } return true; diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index e1bb6c33bb..c0768de4bb 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -6,44 +6,12 @@ #include #include -#define MAX_TEXT_INPUT_LEN 23 - void subghz_scene_save_name_text_input_callback(void* context) { furi_assert(context); SubGhz* subghz = context; view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneSaveName); } -void subghz_scene_save_name_get_timefilename( - FuriString* name, - const char* proto_name, - bool fulldate) { - FuriHalRtcDateTime datetime = {0}; - furi_hal_rtc_get_datetime(&datetime); - if(fulldate) { - furi_string_printf( - name, - "%s_%.4d%.2d%.2d-%.2d%.2d%.2d", - proto_name, - datetime.year, - datetime.month, - datetime.day, - datetime.hour, - datetime.minute, - datetime.second); - } else { - furi_string_printf( - name, - "%s_%.2d%.2d-%.2d%.2d%.2d", - proto_name, - datetime.month, - datetime.day, - datetime.hour, - datetime.minute, - datetime.second); - } -} - void subghz_scene_save_name_on_enter(void* context) { SubGhz* subghz = context; @@ -54,35 +22,33 @@ void subghz_scene_save_name_on_enter(void* context) { FuriString* file_name = furi_string_alloc(); FuriString* dir_name = furi_string_alloc(); + char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; + FuriHalRtcDateTime* datetime = subghz->save_datetime_set ? &subghz->save_datetime : NULL; + subghz->save_datetime_set = false; if(!subghz_path_is_file(subghz->file_path)) { - char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; - if(subghz->last_settings->timestamp_file_names) { - SubGhzProtocolDecoderBase* decoder_result = subghz_txrx_get_decoder(subghz->txrx); - if(decoder_result != 0x0) { - if(decoder_result != NULL) { - if(strlen(decoder_result->protocol->name) != 0) { - if(scene_manager_has_previous_scene( - subghz->scene_manager, SubGhzSceneSetType)) { - subghz_scene_save_name_get_timefilename(file_name, "S", true); - } else { - subghz_scene_save_name_get_timefilename( - file_name, decoder_result->protocol->name, false); - } - - } else { - subghz_scene_save_name_get_timefilename(file_name, "S", true); + SubGhzProtocolDecoderBase* decoder_result = subghz_txrx_get_decoder(subghz->txrx); + + bool skip_dec_is_present = false; + if(decoder_result != 0x0) { + if(decoder_result != NULL) { + if(strlen(decoder_result->protocol->name) != 0 && + subghz->last_settings->timestamp_file_names) { + if(!scene_manager_has_previous_scene( + subghz->scene_manager, SubGhzSceneSetType)) { + name_generator_make_auto_datetime( + file_name_buf, + SUBGHZ_MAX_LEN_NAME, + decoder_result->protocol->name, + datetime); + skip_dec_is_present = true; } - } else { - subghz_scene_save_name_get_timefilename(file_name, "S", true); } - } else { - subghz_scene_save_name_get_timefilename(file_name, "S", true); } - } else { - name_generator_make_auto( - file_name_buf, SUBGHZ_MAX_LEN_NAME, SUBGHZ_APP_FILENAME_PREFIX); - furi_string_set(file_name, file_name_buf); } + if(!skip_dec_is_present) { + name_generator_make_auto_datetime(file_name_buf, SUBGHZ_MAX_LEN_NAME, NULL, datetime); + } + furi_string_set(file_name, file_name_buf); furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER); //highlighting the entire filename by default dev_name_empty = true; @@ -96,7 +62,9 @@ void subghz_scene_save_name_on_enter(void* context) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == SubGhzCustomEventManagerSetRAW) { dev_name_empty = true; - subghz_scene_save_name_get_timefilename(file_name, "RAW", true); + name_generator_make_auto_datetime( + file_name_buf, SUBGHZ_MAX_LEN_NAME, "RAW", datetime); + furi_string_set(file_name, file_name_buf); } } furi_string_set(subghz->file_path, dir_name); @@ -109,7 +77,7 @@ void subghz_scene_save_name_on_enter(void* context) { subghz_scene_save_name_text_input_callback, subghz, subghz->file_name_tmp, - MAX_TEXT_INPUT_LEN, + SUBGHZ_MAX_LEN_NAME, dev_name_empty); ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( diff --git a/applications/main/subghz/scenes/subghz_scene_saved.c b/applications/main/subghz/scenes/subghz_scene_saved.c index af4c358357..95abd443e9 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved.c +++ b/applications/main/subghz/scenes/subghz_scene_saved.c @@ -24,5 +24,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_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 2d61818513..17f358fa6e 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -139,6 +139,12 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexIronLogic, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "KL: DEA Mio 433MHz", + SubmenuIndexDeaMio433, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "KL: DTM Neo 433MHz", @@ -193,6 +199,12 @@ void subghz_scene_set_type_on_enter(void* context) { SubmenuIndexFAACRCXT_868, subghz_scene_set_type_submenu_callback, subghz); + submenu_add_item( + subghz->submenu, + "KL: Genius Bravo 433MHz", + SubmenuIndexGeniusBravo433, + subghz_scene_set_type_submenu_callback, + subghz); submenu_add_item( subghz->submenu, "KL: Nice Mhouse 433MHz", @@ -747,6 +759,30 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } break; + case SubmenuIndexDeaMio433: + generated_protocol = subghz_txrx_gen_keeloq_protocol( + subghz->txrx, + "AM650", + 433920000, + (key & 0x0FFFF000) | 0x00000869, + 0x2, + 0x0003, + "Dea_Mio"); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; + case SubmenuIndexGeniusBravo433: + generated_protocol = subghz_txrx_gen_keeloq_protocol( + subghz->txrx, "AM650", 433920000, key & 0x00FFFFFF, 0x6, 0x0003, "Genius_Bravo"); + if(!generated_protocol) { + furi_string_set( + subghz->error_str, "Function requires\nan SD card with\nfresh databases."); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); + } + break; case SubmenuIndexJCM_433_92: generated_protocol = subghz_txrx_gen_keeloq_protocol( subghz->txrx, "AM650", 433920000, key & 0x00FFFFFF, 0x2, 0x0003, "JCM_Tech"); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index dc379296af..23b9c7d4c7 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -20,6 +20,8 @@ #include #include +#include + #define SUBGHZ_FREQUENCY_RANGE_STR \ "299999755...348000000 or 386999938...464000000 or 778999847...928000000" @@ -49,6 +51,26 @@ 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_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; @@ -323,14 +345,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_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); @@ -512,23 +527,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_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); @@ -573,6 +572,267 @@ 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 settings\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(); + // Reset custom settings + subghz_environment_reset_keeloq(environment); + faac_slh_reset_prog_mode(); + subghz_custom_btns_reset(); + // Free environment + subghz_environment_free(environment); +} + static void subghz_cli_command_print_usage() { printf("Usage:\r\n"); printf("subghz \r\n"); @@ -585,11 +845,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"); @@ -901,6 +1163,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_history.c b/applications/main/subghz/subghz_history.c index 048104f354..2f0371985b 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -131,6 +131,16 @@ const char* subghz_history_get_protocol_name(SubGhzHistory* instance, uint16_t i return furi_string_get_cstr(instance->tmp_string); } +FuriHalRtcDateTime subghz_history_get_datetime(SubGhzHistory* instance, uint16_t idx) { + furi_assert(instance); + SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); + if(item) { + return item->datetime; + } else { + return (FuriHalRtcDateTime){}; + } +} + FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx) { furi_assert(instance); SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); diff --git a/applications/main/subghz/subghz_history.h b/applications/main/subghz/subghz_history.h index cc63c0259c..2e940a8d89 100644 --- a/applications/main/subghz/subghz_history.h +++ b/applications/main/subghz/subghz_history.h @@ -70,6 +70,14 @@ uint8_t subghz_history_get_type_protocol(SubGhzHistory* instance, uint16_t idx); */ const char* subghz_history_get_protocol_name(SubGhzHistory* instance, uint16_t idx); +/** Get datetime from history[idx] + * + * @param instance - SubGhzHistory instance + * @param idx - record index + * @return datetime - FuriHalRtcDateTime received timestamp + */ +FuriHalRtcDateTime subghz_history_get_datetime(SubGhzHistory* instance, uint16_t idx); + /** Get string item menu to history[idx] * * @param instance - SubGhzHistory instance diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 4297699db8..09caed7a9e 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -79,6 +79,9 @@ struct SubGhz { SubGhzReadRAW* subghz_read_raw; bool raw_send_only; + bool save_datetime_set; + FuriHalRtcDateTime save_datetime; + SubGhzLastSettings* last_settings; SubGhzProtocolFlag filter; diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 07bad225da..5dd2680e24 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -126,6 +126,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->timestamp_file_names = false; instance->external_module_power_amp = false; instance->enable_hopping = false; + instance->delete_old_signals = false; instance->ignore_filter = 0x00; // See bin_raw_value in applications/main/subghz/scenes/subghz_scene_receiver_config.c instance->filter = SubGhzProtocolFlag_Decodable; diff --git a/applications/main/u2f/u2f_hid.c b/applications/main/u2f/u2f_hid.c index d7d7e6cf41..83c8a575f5 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 0b50090966..90631408a6 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 2c40fdc16a..4f93e08e6a 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -221,7 +221,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"); @@ -230,7 +235,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/expansion/application.fam b/applications/services/expansion/application.fam new file mode 100644 index 0000000000..1402e8413a --- /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 0000000000..48743808be --- /dev/null +++ b/applications/services/expansion/expansion.c @@ -0,0 +1,446 @@ +#include "expansion.h" +#include "expansion_i.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; + + ExpansionSettings settings; +}; + +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_settings_load(&instance->settings); + expansion_enable(instance); +} + +// Public API functions + +void expansion_enable(Expansion* instance) { + if(instance->settings.uart_index < FuriHalSerialIdMax) { + expansion_set_listen_serial(instance, 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"); +} + +ExpansionSettings* expansion_get_settings(Expansion* instance) { + return &instance->settings; +} diff --git a/applications/services/expansion/expansion.h b/applications/services/expansion/expansion.h new file mode 100644 index 0000000000..e169b3c15d --- /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_i.h b/applications/services/expansion/expansion_i.h new file mode 100644 index 0000000000..13a4962521 --- /dev/null +++ b/applications/services/expansion/expansion_i.h @@ -0,0 +1,6 @@ +#pragma once + +#include "expansion_settings.h" +#include "expansion.h" + +ExpansionSettings* expansion_get_settings(Expansion* instance); diff --git a/applications/services/expansion/expansion_protocol.h b/applications/services/expansion/expansion_protocol.h new file mode 100644 index 0000000000..6ed818f82d --- /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 0000000000..f350e2c561 --- /dev/null +++ b/applications/services/expansion/expansion_settings.c @@ -0,0 +1,34 @@ +#include "expansion_settings.h" + +#include +#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); + if(!saved_struct_load( + EXPANSION_SETTINGS_PATH, + settings, + sizeof(ExpansionSettings), + EXPANSION_SETTINGS_MAGIC, + EXPANSION_SETTINGS_VERSION)) { + settings->uart_index = FuriHalSerialIdMax; + } + return true; +} + +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 0000000000..e7663f1b95 --- /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 0000000000..23d6728e8e --- /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/rpc/rpc.c b/applications/services/rpc/rpc.c index 50c4b36086..909d0d65d4 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 way. 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 863bca355b..f7cda64f73 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 dd219e2dc4..98860332d6 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -265,7 +265,7 @@ static void rpc_system_gui_virtual_display_input_callback(InputEvent* event, voi RpcGuiSystem* rpc_gui = context; RpcSession* session = rpc_gui->session; - FURI_LOG_D(TAG, "VirtulDisplay: SendInputEvent"); + FURI_LOG_D(TAG, "VirtualDisplay: SendInputEvent"); PB_Main rpc_message = { .command_id = 0, @@ -317,7 +317,7 @@ static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, rpc_gui); if(request->content.gui_start_virtual_display_request.send_input) { - FURI_LOG_D(TAG, "VirtulDisplay: input forwarding requested"); + 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, @@ -464,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/expansion_settings_app/application.fam b/applications/settings/expansion_settings_app/application.fam new file mode 100644 index 0000000000..b253ad1744 --- /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 0000000000..12ead21601 --- /dev/null +++ b/applications/settings/expansion_settings_app/expansion_settings_app.c @@ -0,0 +1,88 @@ +#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)); + + app->gui = furi_record_open(RECORD_GUI); + app->expansion = furi_record_open(RECORD_EXPANSION); + app->settings = expansion_get_settings(app->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 0000000000..a404f9c1a5 --- /dev/null +++ b/applications/settings/expansion_settings_app/expansion_settings_app.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#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/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 195501210e..2a1d988aca 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -20,8 +20,11 @@ static const NotificationSequence sequence_note_c = { NULL, }; -#define CONTRAST_COUNT 11 +#define CONTRAST_COUNT 17 const char* const contrast_text[CONTRAST_COUNT] = { + "-8", + "-7", + "-6", "-5", "-4", "-3", @@ -33,8 +36,14 @@ const char* const contrast_text[CONTRAST_COUNT] = { "+3", "+4", "+5", + "+6", + "+7", + "+8", }; const int32_t contrast_value[CONTRAST_COUNT] = { + -8, + -7, + -6, -5, -4, -3, @@ -46,6 +55,9 @@ const int32_t contrast_value[CONTRAST_COUNT] = { 3, 4, 5, + 6, + 7, + 8, }; #define BACKLIGHT_COUNT 21 diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index e6118344b1..5d7f3193d8 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 measurement_units_value[] = { }; static void measurement_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, measurement_units_text[index]); locale_set_measurement_unit(measurement_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/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index bf7c399142..eea9e60b63 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -11,6 +11,7 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexKeyboard, HidSubmenuIndexNumpad, HidSubmenuIndexMedia, + HidSubmenuIndexMusicMacOs, HidSubmenuIndexMovie, HidSubmenuIndexTikShorts, HidSubmenuIndexMouse, @@ -39,6 +40,9 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMedia) { app->view_id = HidViewMedia; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); + } else if(index == HidSubmenuIndexMusicMacOs) { + app->view_id = HidViewMusicMacOs; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMusicMacOs); } else if(index == HidSubmenuIndexMovie) { app->view_id = HidViewMovie; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMovie); @@ -75,6 +79,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_numpad_set_connected_status(hid->hid_numpad, connected); hid_media_set_connected_status(hid->hid_media, connected); + hid_music_macos_set_connected_status(hid->hid_music_macos, connected); hid_movie_set_connected_status(hid->hid_movie, connected); hid_mouse_set_connected_status(hid->hid_mouse, connected); hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected); @@ -131,6 +136,12 @@ Hid* hid_alloc(HidTransport transport) { app->device_type_submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app); submenu_add_item( app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, + "Apple Music macOS", + HidSubmenuIndexMusicMacOs, + hid_submenu_callback, + app); submenu_add_item( app->device_type_submenu, "Movie", HidSubmenuIndexMovie, hid_submenu_callback, app); submenu_add_item( @@ -156,7 +167,11 @@ Hid* hid_alloc(HidTransport transport) { hid_submenu_callback, app); submenu_add_item( - app->device_type_submenu, "PushToTalk", HidSubmenuIndexPushToTalk, hid_submenu_callback, app); + app->device_type_submenu, + "PushToTalk", + HidSubmenuIndexPushToTalk, + hid_submenu_callback, + app); view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); @@ -192,7 +207,13 @@ Hid* hid_app_alloc_view(void* context) { view_set_previous_callback(hid_media_get_view(app->hid_media), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); - + + // Music MacOs view + app->hid_music_macos = hid_music_macos_alloc(app); + view_set_previous_callback(hid_music_macos_get_view(app->hid_music_macos), hid_menu_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMusicMacOs, hid_music_macos_get_view(app->hid_music_macos)); + // Movie view app->hid_movie = hid_movie_alloc(app); view_set_previous_callback(hid_movie_get_view(app->hid_movie), hid_menu_view); @@ -213,8 +234,7 @@ Hid* hid_app_alloc_view(void* context) { // Mouse clicker view app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); - view_set_previous_callback( - hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_menu_view); + view_set_previous_callback(hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseClicker, @@ -222,8 +242,7 @@ Hid* hid_app_alloc_view(void* context) { // Mouse jiggler view app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); - view_set_previous_callback( - hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_menu_view); + view_set_previous_callback(hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseJiggler, @@ -233,7 +252,7 @@ Hid* hid_app_alloc_view(void* context) { app->hid_ptt_menu = hid_ptt_menu_alloc(app); view_set_previous_callback(hid_ptt_menu_get_view(app->hid_ptt_menu), hid_menu_view); view_dispatcher_add_view( - app->view_dispatcher, HidViewPushToTalkMenu, hid_ptt_menu_get_view(app->hid_ptt_menu)); + app->view_dispatcher, HidViewPushToTalkMenu, hid_ptt_menu_get_view(app->hid_ptt_menu)); app->hid_ptt = hid_ptt_alloc(app); view_set_previous_callback(hid_ptt_get_view(app->hid_ptt), hid_ptt_menu_view); view_dispatcher_add_view( @@ -261,6 +280,8 @@ void hid_free(Hid* app) { hid_numpad_free(app->hid_numpad); view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia); hid_media_free(app->hid_media); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMusicMacOs); + hid_music_macos_free(app->hid_music_macos); view_dispatcher_remove_view(app->view_dispatcher, HidViewMovie); hid_movie_free(app->hid_movie); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index ccbbb02d72..3c0f0ae796 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -20,6 +20,7 @@ #include "views/hid_keyboard.h" #include "views/hid_numpad.h" #include "views/hid_media.h" +#include "views/hid_music_macos.h" #include "views/hid_movie.h" #include "views/hid_mouse.h" #include "views/hid_mouse_clicker.h" @@ -48,6 +49,7 @@ struct Hid { HidKeyboard* hid_keyboard; HidNumpad* hid_numpad; HidMedia* hid_media; + HidMusicMacos* hid_music_macos; HidMovie* hid_movie; HidMouse* hid_mouse; HidMouseClicker* hid_mouse_clicker; diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 961faec529..583ddb4b9a 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -4,6 +4,7 @@ typedef enum { HidViewKeyboard, HidViewNumpad, HidViewMedia, + HidViewMusicMacOs, HidViewMovie, HidViewMouse, HidViewMouseClicker, diff --git a/applications/system/hid_app/views/hid_music_macos.c b/applications/system/hid_app/views/hid_music_macos.c new file mode 100644 index 0000000000..d5dd4dab3d --- /dev/null +++ b/applications/system/hid_app/views/hid_music_macos.c @@ -0,0 +1,242 @@ +#include "hid_music_macos.h" +#include +#include +#include +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMusicMacos" + +struct HidMusicMacos { + View* view; + Hid* hid; +}; + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; + bool back_pressed; + HidTransport transport; +} HidMusicMacosModel; + +static void hid_music_macos_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { + canvas_draw_triangle(canvas, x, y, 5, 3, dir); + if(dir == CanvasDirectionBottomToTop) { + canvas_draw_dot(canvas, x, y - 1); + } else if(dir == CanvasDirectionTopToBottom) { + canvas_draw_dot(canvas, x, y + 1); + } else if(dir == CanvasDirectionRightToLeft) { + canvas_draw_dot(canvas, x - 1, y); + } else if(dir == CanvasDirectionLeftToRight) { + canvas_draw_dot(canvas, x + 1, y); + } +} + +static void hid_music_macos_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMusicMacosModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Music"); + canvas_set_font(canvas, FontSecondary); + + // Keypad circles + canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_music_macos_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft); + hid_music_macos_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft); + canvas_draw_line(canvas, 64, 26, 64, 30); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_music_macos_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight); + hid_music_macos_draw_arrow(canvas, 99, 28, CanvasDirectionLeftToRight); + canvas_draw_line(canvas, 102, 26, 102, 30); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + hid_music_macos_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight); + canvas_draw_line(canvas, 84, 26, 84, 30); + canvas_draw_line(canvas, 86, 26, 86, 30); + canvas_set_color(canvas, ColorBlack); + + // Exit + if(model->back_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +static void hid_music_macos_process_press(HidMusicMacos* hid_music_macos, InputEvent* event) { + with_view_model( + hid_music_macos->view, + HidMusicMacosModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = true; + hid_hal_keyboard_press( + hid_music_macos->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_UP_ARROW); + hid_hal_keyboard_release( + hid_music_macos->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + hid_hal_keyboard_press( + hid_music_macos->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_DOWN_ARROW); + hid_hal_keyboard_release( + hid_music_macos->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + hid_hal_consumer_key_press(hid_music_macos->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + hid_hal_consumer_key_press(hid_music_macos->hid, HID_CONSUMER_SCAN_NEXT_TRACK); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + hid_hal_consumer_key_press(hid_music_macos->hid, HID_CONSUMER_PLAY_PAUSE); + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + }, + true); +} + +static void hid_music_macos_process_release(HidMusicMacos* hid_music_macos, InputEvent* event) { + with_view_model( + hid_music_macos->view, + HidMusicMacosModel * model, + { + if(event->key == InputKeyUp) { + model->up_pressed = false; + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + hid_hal_consumer_key_release( + hid_music_macos->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + hid_hal_consumer_key_release(hid_music_macos->hid, HID_CONSUMER_SCAN_NEXT_TRACK); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + hid_hal_consumer_key_release(hid_music_macos->hid, HID_CONSUMER_PLAY_PAUSE); + } else if(event->key == InputKeyBack) { + model->back_pressed = false; + } + }, + true); +} + +static bool hid_music_macos_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMusicMacos* hid_music_macos = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + hid_hal_keyboard_release_all(hid_music_macos->hid); + } else { + consumed = true; + if(event->type == InputTypePress) { + hid_music_macos_process_press(hid_music_macos, event); + } else if(event->type == InputTypeRelease) { + hid_music_macos_process_release(hid_music_macos, event); + } + } + return consumed; +} + +HidMusicMacos* hid_music_macos_alloc(Hid* hid) { + HidMusicMacos* hid_music_macos = malloc(sizeof(HidMusicMacos)); + hid_music_macos->view = view_alloc(); + hid_music_macos->hid = hid; + view_set_context(hid_music_macos->view, hid_music_macos); + view_allocate_model(hid_music_macos->view, ViewModelTypeLocking, sizeof(HidMusicMacosModel)); + view_set_draw_callback(hid_music_macos->view, hid_music_macos_draw_callback); + view_set_input_callback(hid_music_macos->view, hid_music_macos_input_callback); + + with_view_model( + hid_music_macos->view, + HidMusicMacosModel * model, + { model->transport = hid->transport; }, + true); + + return hid_music_macos; +} + +void hid_music_macos_free(HidMusicMacos* hid_music_macos) { + furi_assert(hid_music_macos); + view_free(hid_music_macos->view); + free(hid_music_macos); +} + +View* hid_music_macos_get_view(HidMusicMacos* hid_music_macos) { + furi_assert(hid_music_macos); + return hid_music_macos->view; +} + +void hid_music_macos_set_connected_status(HidMusicMacos* hid_music_macos, bool connected) { + furi_assert(hid_music_macos); + with_view_model( + hid_music_macos->view, HidMusicMacosModel * model, { model->connected = connected; }, true); +} diff --git a/applications/system/hid_app/views/hid_music_macos.h b/applications/system/hid_app/views/hid_music_macos.h new file mode 100644 index 0000000000..9deac32eb7 --- /dev/null +++ b/applications/system/hid_app/views/hid_music_macos.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct HidMusicMacos HidMusicMacos; + +HidMusicMacos* hid_music_macos_alloc(); + +void hid_music_macos_free(HidMusicMacos* hid_music_macos); + +View* hid_music_macos_get_view(HidMusicMacos* hid_music_macos); + +void hid_music_macos_set_connected_status(HidMusicMacos* hid_music_macos, bool connected); diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 86e9f766f0..1d71490a20 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -27,8 +27,8 @@ typedef struct { bool ptt_pressed; bool mic_pressed; bool connected; - FuriString *os; - FuriString *app; + FuriString* os; + FuriString* app; size_t osIndex; size_t appIndex; size_t window_position; @@ -65,391 +65,454 @@ static void hid_ptt_stop_ptt_meet_zoom(HidPushToTalk* hid_ptt) { hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR); } static void hid_ptt_trigger_mute_macos_meet(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); } static void hid_ptt_trigger_mute_linux_meet(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); } static void hid_ptt_trigger_camera_macos_meet(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); } static void hid_ptt_trigger_camera_linux_meet(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E ); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); } static void hid_ptt_trigger_hand_macos_meet(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL |HID_KEYBOARD_H); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL |HID_KEYBOARD_H); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_H); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL | HID_KEYBOARD_H); } static void hid_ptt_trigger_hand_linux_meet(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT |HID_KEYBOARD_H); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT |HID_KEYBOARD_H); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT | HID_KEYBOARD_H); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT | HID_KEYBOARD_H); } static void hid_ptt_trigger_mute_macos_zoom(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A); } static void hid_ptt_trigger_mute_linux_zoom(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_A); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_A); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_A); } static void hid_ptt_trigger_camera_macos_zoom(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); } static void hid_ptt_trigger_camera_linux_zoom(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V); } static void hid_ptt_trigger_hand_zoom(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y); } // this one is widely used across different apps static void hid_ptt_trigger_cmd_shift_m(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); } // Hangouts HidPushToTalkAppIndexGoogleHangouts static void hid_ptt_trigger_mute_macos_hangouts(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D); } static void hid_ptt_trigger_mute_linux_hangouts(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D); } static void hid_ptt_trigger_camera_macos_hangouts(HidPushToTalk* hid_ptt) { // and hand in teams - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E); } static void hid_ptt_trigger_camera_linux_hangouts(HidPushToTalk* hid_ptt) { // and hand in teams - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E); } // Signal static void hid_ptt_trigger_mute_signal(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); } static void hid_ptt_trigger_camera_signal(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); } // skype static void hid_ptt_trigger_mute_linux_skype(HidPushToTalk* hid_ptt) { // and webex - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M); } static void hid_ptt_trigger_camera_macos_skype(HidPushToTalk* hid_ptt) { // and hand in teams - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); } static void hid_ptt_trigger_camera_linux_skype(HidPushToTalk* hid_ptt) { // and hand in teams - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K); } // slack call static void hid_ptt_trigger_mute_slack_call(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, HID_KEYBOARD_M); + hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_M); hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_M); } static void hid_ptt_trigger_camera_slack_call(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, HID_KEYBOARD_V); + hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_V); hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_V); } // slack hubble static void hid_ptt_trigger_mute_macos_slack_hubble(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); } static void hid_ptt_trigger_mute_linux_slack_hubble(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_press( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR); } // discord static void hid_ptt_trigger_mute_macos_discord(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_M); } static void hid_ptt_start_ptt_macos_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_P); } static void hid_ptt_stop_ptt_macos_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_P); } static void hid_ptt_trigger_mute_linux_discord(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_M); } static void hid_ptt_start_ptt_linux_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_P); } static void hid_ptt_stop_ptt_linux_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | + HID_KEYBOARD_P); } // teamspeak static void hid_ptt_trigger_mute_macos_teamspeak(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_M); } static void hid_ptt_start_ptt_macos_teamspeak(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_P); } static void hid_ptt_stop_ptt_macos_teamspeak(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_P); } static void hid_ptt_trigger_mute_linux_teamspeak(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_M); } static void hid_ptt_start_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_press( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_P); } static void hid_ptt_stop_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P); + hid_hal_keyboard_release( + hid_ptt->hid, + KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | + HID_KEYBOARD_P); } // teams static void hid_ptt_start_ptt_macos_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI|HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_SPACEBAR); } static void hid_ptt_start_ptt_linux_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL|HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_SPACEBAR); } static void hid_ptt_stop_ptt_macos_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI|HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_SPACEBAR); } static void hid_ptt_stop_ptt_linux_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL|HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_SPACEBAR); } static void hid_ptt_trigger_mute_linux_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M); } static void hid_ptt_trigger_camera_macos_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O); } static void hid_ptt_trigger_camera_linux_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT |HID_KEYBOARD_O); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O); } // Jamulus static void hid_ptt_trigger_mute_jamulus(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_M); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_M); hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_M); } // webex - static void hid_ptt_trigger_camera_webex(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V); } static void hid_ptt_trigger_hand_macos_webex(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); } static void hid_ptt_trigger_hand_linux_webex(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); - hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); + hid_hal_keyboard_release( + hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R); } -static void hid_ptt_menu_callback(void* context, uint32_t osIndex, FuriString* osLabel, uint32_t appIndex, FuriString* appLabel) { +static void hid_ptt_menu_callback( + void* context, + uint32_t osIndex, + FuriString* osLabel, + uint32_t appIndex, + FuriString* appLabel) { furi_assert(context); HidPushToTalk* hid_ptt = context; - with_view_model( - hid_ptt->view, HidPushToTalkModel * model, { + with_view_model( + hid_ptt->view, + HidPushToTalkModel * model, + { furi_string_set(model->os, osLabel); furi_string_set(model->app, appLabel); model->osIndex = osIndex; model->appIndex = appIndex; - model->callback_trigger_mute = NULL; + model->callback_trigger_mute = NULL; model->callback_trigger_camera = NULL; - model->callback_trigger_hand = NULL; - model->callback_start_ptt = NULL; - model->callback_stop_ptt = NULL; + model->callback_trigger_hand = NULL; + model->callback_start_ptt = NULL; + model->callback_stop_ptt = NULL; FURI_LOG_E(TAG, "appIndex: %lu", appIndex); if(osIndex == HidPushToTalkMacOS) { switch(appIndex) { case HidPushToTalkAppIndexDiscord: - model->callback_trigger_mute = hid_ptt_trigger_mute_macos_discord; - model->callback_start_ptt = hid_ptt_start_ptt_macos_discord; - model->callback_stop_ptt = hid_ptt_stop_ptt_macos_discord; + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_discord; + model->callback_start_ptt = hid_ptt_start_ptt_macos_discord; + model->callback_stop_ptt = hid_ptt_stop_ptt_macos_discord; break; case HidPushToTalkAppIndexFaceTime: - model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; - model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m; - model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m; + model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; + model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m; + model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m; break; case HidPushToTalkAppIndexGoogleHangouts: - model->callback_trigger_mute = hid_ptt_trigger_mute_macos_hangouts; + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_hangouts; model->callback_trigger_camera = hid_ptt_trigger_camera_macos_hangouts; - model->callback_start_ptt = hid_ptt_trigger_mute_macos_hangouts; - model->callback_stop_ptt = hid_ptt_trigger_mute_macos_hangouts; + model->callback_start_ptt = hid_ptt_trigger_mute_macos_hangouts; + model->callback_stop_ptt = hid_ptt_trigger_mute_macos_hangouts; break; case HidPushToTalkAppIndexGoogleMeet: - model->callback_trigger_mute = hid_ptt_trigger_mute_macos_meet; + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_meet; model->callback_trigger_camera = hid_ptt_trigger_camera_macos_meet; - model->callback_trigger_hand = hid_ptt_trigger_hand_macos_meet; - model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; - model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; + model->callback_trigger_hand = hid_ptt_trigger_hand_macos_meet; + model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; + model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; break; case HidPushToTalkAppIndexJamulus: - model->callback_trigger_mute = hid_ptt_trigger_mute_jamulus; - model->callback_start_ptt = hid_ptt_trigger_mute_jamulus; - model->callback_stop_ptt = hid_ptt_trigger_mute_jamulus; + model->callback_trigger_mute = hid_ptt_trigger_mute_jamulus; + model->callback_start_ptt = hid_ptt_trigger_mute_jamulus; + model->callback_stop_ptt = hid_ptt_trigger_mute_jamulus; break; case HidPushToTalkAppIndexTeams: - model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; + model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; model->callback_trigger_camera = hid_ptt_trigger_camera_macos_teams; - model->callback_trigger_hand = hid_ptt_trigger_camera_macos_skype; - model->callback_start_ptt = hid_ptt_start_ptt_macos_teams; - model->callback_stop_ptt = hid_ptt_stop_ptt_macos_teams; + model->callback_trigger_hand = hid_ptt_trigger_camera_macos_skype; + model->callback_start_ptt = hid_ptt_start_ptt_macos_teams; + model->callback_stop_ptt = hid_ptt_stop_ptt_macos_teams; break; case HidPushToTalkAppIndexTeamSpeak: - model->callback_trigger_mute = hid_ptt_trigger_mute_macos_teamspeak; - model->callback_start_ptt = hid_ptt_start_ptt_macos_teamspeak; - model->callback_stop_ptt = hid_ptt_stop_ptt_macos_teamspeak; + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_teamspeak; + model->callback_start_ptt = hid_ptt_start_ptt_macos_teamspeak; + model->callback_stop_ptt = hid_ptt_stop_ptt_macos_teamspeak; break; case HidPushToTalkAppIndexSignal: - model->callback_trigger_mute = hid_ptt_trigger_mute_signal; + model->callback_trigger_mute = hid_ptt_trigger_mute_signal; model->callback_trigger_camera = hid_ptt_trigger_camera_signal; - model->callback_start_ptt = hid_ptt_trigger_mute_signal; - model->callback_stop_ptt = hid_ptt_trigger_mute_signal; + model->callback_start_ptt = hid_ptt_trigger_mute_signal; + model->callback_stop_ptt = hid_ptt_trigger_mute_signal; break; case HidPushToTalkAppIndexSkype: - model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; + model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; model->callback_trigger_camera = hid_ptt_trigger_camera_macos_skype; - model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m; - model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m; + model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m; + model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m; break; case HidPushToTalkAppIndexSlackCall: - model->callback_trigger_mute = hid_ptt_trigger_mute_slack_call; + model->callback_trigger_mute = hid_ptt_trigger_mute_slack_call; model->callback_trigger_camera = hid_ptt_trigger_camera_slack_call; - model->callback_start_ptt = hid_ptt_trigger_mute_slack_call; - model->callback_stop_ptt = hid_ptt_trigger_mute_slack_call; + model->callback_start_ptt = hid_ptt_trigger_mute_slack_call; + model->callback_stop_ptt = hid_ptt_trigger_mute_slack_call; break; case HidPushToTalkAppIndexSlackHubble: - model->callback_trigger_mute = hid_ptt_trigger_mute_macos_slack_hubble; - model->callback_start_ptt = hid_ptt_trigger_mute_macos_slack_hubble; - model->callback_stop_ptt = hid_ptt_trigger_mute_macos_slack_hubble; + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_slack_hubble; + model->callback_start_ptt = hid_ptt_trigger_mute_macos_slack_hubble; + model->callback_stop_ptt = hid_ptt_trigger_mute_macos_slack_hubble; break; case HidPushToTalkAppIndexWebex: - model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; + model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m; model->callback_trigger_camera = hid_ptt_trigger_camera_webex; - model->callback_trigger_hand = hid_ptt_trigger_hand_macos_webex; - model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m; - model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m; + model->callback_trigger_hand = hid_ptt_trigger_hand_macos_webex; + model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m; + model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m; break; case HidPushToTalkAppIndexZoom: - model->callback_trigger_mute = hid_ptt_trigger_mute_macos_zoom; + model->callback_trigger_mute = hid_ptt_trigger_mute_macos_zoom; model->callback_trigger_camera = hid_ptt_trigger_camera_macos_zoom; - model->callback_trigger_hand = hid_ptt_trigger_hand_zoom; - model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; - model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; + model->callback_trigger_hand = hid_ptt_trigger_hand_zoom; + model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; + model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; break; } - } else if (osIndex == HidPushToTalkLinux) { + } else if(osIndex == HidPushToTalkLinux) { switch(appIndex) { case HidPushToTalkAppIndexDiscord: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_discord; - model->callback_start_ptt = hid_ptt_start_ptt_linux_discord; - model->callback_stop_ptt = hid_ptt_stop_ptt_linux_discord; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_discord; + model->callback_start_ptt = hid_ptt_start_ptt_linux_discord; + model->callback_stop_ptt = hid_ptt_stop_ptt_linux_discord; break; case HidPushToTalkAppIndexGoogleHangouts: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_hangouts; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_hangouts; model->callback_trigger_camera = hid_ptt_trigger_camera_linux_hangouts; - model->callback_start_ptt = hid_ptt_trigger_mute_linux_hangouts; - model->callback_stop_ptt = hid_ptt_trigger_mute_linux_hangouts; + model->callback_start_ptt = hid_ptt_trigger_mute_linux_hangouts; + model->callback_stop_ptt = hid_ptt_trigger_mute_linux_hangouts; break; case HidPushToTalkAppIndexGoogleMeet: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_meet; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_meet; model->callback_trigger_camera = hid_ptt_trigger_camera_linux_meet; - model->callback_trigger_hand = hid_ptt_trigger_hand_linux_meet; - model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; - model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; + model->callback_trigger_hand = hid_ptt_trigger_hand_linux_meet; + model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; + model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; break; case HidPushToTalkAppIndexJamulus: - model->callback_trigger_mute = hid_ptt_trigger_mute_jamulus; - model->callback_start_ptt = hid_ptt_trigger_mute_jamulus; - model->callback_stop_ptt = hid_ptt_trigger_mute_jamulus; + model->callback_trigger_mute = hid_ptt_trigger_mute_jamulus; + model->callback_start_ptt = hid_ptt_trigger_mute_jamulus; + model->callback_stop_ptt = hid_ptt_trigger_mute_jamulus; break; case HidPushToTalkAppIndexTeams: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_teams; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_teams; model->callback_trigger_camera = hid_ptt_trigger_camera_linux_teams; - model->callback_trigger_hand = hid_ptt_trigger_camera_linux_skype; - model->callback_start_ptt = hid_ptt_start_ptt_linux_teams; - model->callback_stop_ptt = hid_ptt_stop_ptt_linux_teams; + model->callback_trigger_hand = hid_ptt_trigger_camera_linux_skype; + model->callback_start_ptt = hid_ptt_start_ptt_linux_teams; + model->callback_stop_ptt = hid_ptt_stop_ptt_linux_teams; break; case HidPushToTalkAppIndexTeamSpeak: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_teamspeak; - model->callback_start_ptt = hid_ptt_start_ptt_linux_teamspeak; - model->callback_stop_ptt = hid_ptt_stop_ptt_linux_teamspeak; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_teamspeak; + model->callback_start_ptt = hid_ptt_start_ptt_linux_teamspeak; + model->callback_stop_ptt = hid_ptt_stop_ptt_linux_teamspeak; break; case HidPushToTalkAppIndexSignal: - model->callback_trigger_mute = hid_ptt_trigger_mute_signal; + model->callback_trigger_mute = hid_ptt_trigger_mute_signal; model->callback_trigger_camera = hid_ptt_trigger_camera_signal; - model->callback_start_ptt = hid_ptt_trigger_mute_signal; - model->callback_stop_ptt = hid_ptt_trigger_mute_signal; + model->callback_start_ptt = hid_ptt_trigger_mute_signal; + model->callback_stop_ptt = hid_ptt_trigger_mute_signal; break; case HidPushToTalkAppIndexSkype: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_skype; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_skype; model->callback_trigger_camera = hid_ptt_trigger_camera_linux_skype; - model->callback_start_ptt = hid_ptt_trigger_mute_linux_skype; - model->callback_stop_ptt = hid_ptt_trigger_mute_linux_skype; + model->callback_start_ptt = hid_ptt_trigger_mute_linux_skype; + model->callback_stop_ptt = hid_ptt_trigger_mute_linux_skype; break; case HidPushToTalkAppIndexSlackCall: - model->callback_trigger_mute = hid_ptt_trigger_mute_slack_call; + model->callback_trigger_mute = hid_ptt_trigger_mute_slack_call; model->callback_trigger_camera = hid_ptt_trigger_camera_slack_call; - model->callback_start_ptt = hid_ptt_trigger_mute_slack_call; - model->callback_stop_ptt = hid_ptt_trigger_mute_slack_call; + model->callback_start_ptt = hid_ptt_trigger_mute_slack_call; + model->callback_stop_ptt = hid_ptt_trigger_mute_slack_call; break; case HidPushToTalkAppIndexSlackHubble: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_slack_hubble; - model->callback_start_ptt = hid_ptt_trigger_mute_linux_slack_hubble; - model->callback_stop_ptt = hid_ptt_trigger_mute_linux_slack_hubble; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_slack_hubble; + model->callback_start_ptt = hid_ptt_trigger_mute_linux_slack_hubble; + model->callback_stop_ptt = hid_ptt_trigger_mute_linux_slack_hubble; break; case HidPushToTalkAppIndexZoom: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_zoom; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_zoom; model->callback_trigger_camera = hid_ptt_trigger_camera_linux_zoom; - model->callback_trigger_hand = hid_ptt_trigger_hand_zoom; - model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; - model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; + model->callback_trigger_hand = hid_ptt_trigger_hand_zoom; + model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom; + model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom; break; case HidPushToTalkAppIndexWebex: - model->callback_trigger_mute = hid_ptt_trigger_mute_linux_skype; + model->callback_trigger_mute = hid_ptt_trigger_mute_linux_skype; model->callback_trigger_camera = hid_ptt_trigger_camera_webex; - model->callback_trigger_hand = hid_ptt_trigger_hand_linux_webex; - model->callback_start_ptt = hid_ptt_trigger_mute_linux_skype; - model->callback_stop_ptt = hid_ptt_trigger_mute_linux_skype; + model->callback_trigger_hand = hid_ptt_trigger_hand_linux_webex; + model->callback_start_ptt = hid_ptt_trigger_mute_linux_skype; + model->callback_stop_ptt = hid_ptt_trigger_mute_linux_skype; break; } } - char *app_specific_help = ""; + char* app_specific_help = ""; switch(appIndex) { case HidPushToTalkAppIndexGoogleMeet: app_specific_help = "Google Meet:\n" "This feature is off by default in your audio settings " "and may not work for Windows users who use their screen " - "reader. In this situation, the spacebar performs a different action.\n\n" - ; + "reader. In this situation, the spacebar performs a different action.\n\n"; break; case HidPushToTalkAppIndexDiscord: app_specific_help = @@ -458,36 +521,36 @@ static void hid_ptt_menu_callback(void* context, uint32_t osIndex, FuriString* o "check the box next to Push to Talk.\n" "2. Scroll down to SHORTCUT, click Record Keybinder.\n" "3. Press PTT in the app to bind it." - "4. Go to Keybinds and assign mute button.\n\n" - ; + "4. Go to Keybinds and assign mute button.\n\n"; break; case HidPushToTalkAppIndexTeamSpeak: - app_specific_help = - "TeamSpeak:\n" - "To make keys working bind them in TeamSpeak settings.\n\n" - ; + app_specific_help = "TeamSpeak:\n" + "To make keys working bind them in TeamSpeak settings.\n\n"; break; case HidPushToTalkAppIndexTeams: app_specific_help = "Teams:\n" - "Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n" - ; + "Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n"; break; } - - FuriString *msg = furi_string_alloc(); - furi_string_cat_printf(msg, - "%sGeneral:\n" - "To operate properly flipper microphone " - "status must be in sync with your computer.\n" - "Hold > to change mic status.\n" - "Hold < to open this help.\n" - "Press BACK to switch mic on/off.\n" - "Hold 'o' for PTT mode (mic will be off once you release 'o')\n" - "Hold BACK to exit.", app_specific_help); - widget_add_text_scroll_element(hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg)); + + FuriString* msg = furi_string_alloc(); + furi_string_cat_printf( + msg, + "%sGeneral:\n" + "To operate properly flipper microphone " + "status must be in sync with your computer.\n" + "Hold > to change mic status.\n" + "Hold < to open this help.\n" + "Press BACK to switch mic on/off.\n" + "Hold 'o' for PTT mode (mic will be off once you release 'o')\n" + "Hold BACK to exit.", + app_specific_help); + widget_add_text_scroll_element( + hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg)); furi_string_free(msg); - }, true); + }, + true); view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalk); } @@ -500,8 +563,9 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st FuriString* disp_str; disp_str = furi_string_alloc_set(str); elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); - uint8_t x_pos = (canvas_width(canvas) - canvas_string_width(canvas,furi_string_get_cstr(disp_str))) / 2; - canvas_draw_str(canvas,x_pos,y,furi_string_get_cstr(disp_str)); + uint8_t x_pos = + (canvas_width(canvas) - canvas_string_width(canvas, furi_string_get_cstr(disp_str))) / 2; + canvas_draw_str(canvas, x_pos, y, furi_string_get_cstr(disp_str)); furi_string_free(disp_str); } @@ -523,7 +587,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { canvas_set_font(canvas, FontSecondary); hid_ptt_draw_text_centered(canvas, 73, model->app); hid_ptt_draw_text_centered(canvas, 84, model->os); - + // Help label canvas_draw_icon(canvas, 0, 88, &I_Help_top_64x17); canvas_draw_line(canvas, 4, 105, 4, 114); @@ -533,7 +597,6 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { canvas_draw_icon(canvas, 34, 108, &I_for_help_27x5); canvas_draw_icon(canvas, 0, 115, &I_Help_exit_64x9); canvas_draw_icon(canvas, 24, 115, &I_BtnBackV_9x9); - const uint8_t x_1 = 0; const uint8_t x_2 = x_1 + 19 + 4; @@ -542,7 +605,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { const uint8_t y_1 = 3; const uint8_t y_2 = y_1 + 19; const uint8_t y_3 = y_2 + 19; - + // Up canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); if(model->up_pressed) { @@ -563,11 +626,11 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Left / Help canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18); - if(model->left_pressed) { + if(model->left_pressed) { elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13); canvas_set_color(canvas, ColorWhite); } - if (model->callback_trigger_hand) { + if(model->callback_trigger_hand) { canvas_draw_icon(canvas, x_1 + 4, y_2 + 3, &I_Hand_8x10); } else { canvas_draw_icon(canvas, x_1 + 2, y_2 + 1, &I_BrokenButton_15x15); @@ -580,19 +643,18 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13); canvas_set_color(canvas, ColorWhite); } - if (model->callback_trigger_camera) { + if(model->callback_trigger_camera) { hid_ptt_draw_camera(canvas, x_3 + 4, y_2 + 5); } else { canvas_draw_icon(canvas, x_3 + 2, y_2 + 1, &I_BrokenButton_15x15); } canvas_set_color(canvas, ColorBlack); - // Back / Mic const uint8_t x_mic = x_3; canvas_draw_icon(canvas, x_mic, 0, &I_RoundButtonUnpressed_16x16); - - if (!(!model->muted || (model->ptt_pressed))) { + + if(!(!model->muted || (model->ptt_pressed))) { // show muted if(model->mic_pressed) { // canvas_draw_icon(canvas, x_mic + 1, 0, &I_MicrophonePressedCrossed_15x15); @@ -614,19 +676,21 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { const uint8_t x_ptt_margin = 4; const uint8_t x_ptt_width = 17; const uint8_t x_ptt = x_1 + 19; - canvas_draw_icon(canvas, x_ptt , y_2 , &I_BtnFrameLeft_3x18); - canvas_draw_icon(canvas, x_ptt + x_ptt_width + 3 + x_ptt_margin, y_2 , &I_BtnFrameRight_2x18); - canvas_draw_line(canvas, x_ptt + 3 , y_2 , x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2); - canvas_draw_line(canvas, x_ptt + 3 , y_2 + 16, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 16); - canvas_draw_line(canvas, x_ptt + 3 , y_2 + 17, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 17); - + canvas_draw_icon(canvas, x_ptt, y_2, &I_BtnFrameLeft_3x18); + canvas_draw_icon(canvas, x_ptt + x_ptt_width + 3 + x_ptt_margin, y_2, &I_BtnFrameRight_2x18); + canvas_draw_line(canvas, x_ptt + 3, y_2, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2); + canvas_draw_line( + canvas, x_ptt + 3, y_2 + 16, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 16); + canvas_draw_line( + canvas, x_ptt + 3, y_2 + 17, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 17); - if (model->ptt_pressed) { + if(model->ptt_pressed) { elements_slightly_rounded_box(canvas, x_ptt + 3, y_2 + 2, x_ptt_width + x_ptt_margin, 13); canvas_set_color(canvas, ColorWhite); } canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, x_ptt + 2 + x_ptt_margin / 2, y_2 + 13, AlignLeft, AlignBottom, "PTT"); + elements_multiline_text_aligned( + canvas, x_ptt + 2 + x_ptt_margin / 2, y_2 + 13, AlignLeft, AlignBottom, "PTT"); canvas_set_font(canvas, FontSecondary); canvas_set_color(canvas, ColorBlack); } @@ -649,8 +713,8 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { model->right_pressed = true; } else if(event->key == InputKeyOk) { model->ptt_pressed = true; - if (!model->mic_pressed && model->muted){ - model->callback_start_ptt ? model->callback_start_ptt(hid_ptt):0; + if(!model->mic_pressed && model->muted) { + model->callback_start_ptt ? model->callback_start_ptt(hid_ptt) : 0; } } else if(event->key == InputKeyBack) { model->mic_pressed = true; @@ -658,12 +722,12 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } else if(event->type == InputTypeRelease) { if(event->key == InputKeyUp) { model->up_pressed = false; - if (!model->ptt_pressed){ + if(!model->ptt_pressed) { hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT); } } else if(event->key == InputKeyDown) { model->down_pressed = false; - if (!model->ptt_pressed){ + if(!model->ptt_pressed) { hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT); } } else if(event->key == InputKeyLeft) { @@ -674,10 +738,11 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } else if(event->key == InputKeyOk) { model->ptt_pressed = false; if(!model->mic_pressed) { - if (model->muted) { - model->callback_stop_ptt ? model->callback_stop_ptt(hid_ptt):0; + if(model->muted) { + model->callback_stop_ptt ? model->callback_stop_ptt(hid_ptt) : 0; } else { - model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0; + model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt) : + 0; model->muted = true; } } @@ -685,13 +750,13 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { model->mic_pressed = false; } } else if(event->type == InputTypeShort && !model->ptt_pressed) { - if(event->key == InputKeyBack ) { // no changes if PTT is pressed + if(event->key == InputKeyBack) { // no changes if PTT is pressed model->muted = !model->muted; - model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0; + model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt) : 0; } else if(event->key == InputKeyRight) { - model->callback_trigger_camera ? model->callback_trigger_camera(hid_ptt):0; + model->callback_trigger_camera ? model->callback_trigger_camera(hid_ptt) : 0; } else if(event->key == InputKeyLeft) { - model->callback_trigger_hand ? model->callback_trigger_hand(hid_ptt):0; + model->callback_trigger_hand ? model->callback_trigger_hand(hid_ptt) : 0; } } else if(event->type == InputTypeLong && event->key == InputKeyRight) { model->muted = !model->muted; @@ -699,10 +764,11 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } else if(event->type == InputTypeLong && event->key == InputKeyLeft) { notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); model->left_pressed = false; - view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp); + view_dispatcher_switch_to_view( + hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp); } //LED - if (!model->muted || (model->ptt_pressed)) { + if(!model->muted || (model->ptt_pressed)) { notification_message(hid_ptt->hid->notifications, &sequence_set_red_255); } else { notification_message(hid_ptt->hid->notifications, &sequence_reset_red); @@ -747,45 +813,199 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { view_set_orientation(hid_ptt->view, ViewOrientationVerticalFlip); with_view_model( - hid_ptt->view, HidPushToTalkModel * model, { + hid_ptt->view, + HidPushToTalkModel * model, + { model->transport = hid->transport; model->muted = true; // assume we're muted model->os = furi_string_alloc(); model->app = furi_string_alloc(); - }, true); + }, + true); FURI_LOG_I(TAG, "Calling adding list"); ptt_menu_add_list(hid->hid_ptt_menu, "macOS", HidPushToTalkMacOS); ptt_menu_add_list(hid->hid_ptt_menu, "Win/Linux", HidPushToTalkLinux); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Google Meet", HidPushToTalkAppIndexGoogleMeet, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Google Meet", HidPushToTalkAppIndexGoogleMeet, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Google Hangouts", HidPushToTalkAppIndexGoogleHangouts, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Google Hangouts", HidPushToTalkAppIndexGoogleHangouts, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Discord", HidPushToTalkAppIndexDiscord, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Discord", HidPushToTalkAppIndexDiscord, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "FaceTime", HidPushToTalkAppIndexFaceTime, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Jamulus", HidPushToTalkAppIndexJamulus, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Jamulus", HidPushToTalkAppIndexJamulus, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Signal", HidPushToTalkAppIndexSignal, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Signal", HidPushToTalkAppIndexSignal, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Skype", HidPushToTalkAppIndexSkype, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Skype", HidPushToTalkAppIndexSkype, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Slack Call", HidPushToTalkAppIndexSlackCall, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Slack Call", HidPushToTalkAppIndexSlackCall, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Slack Hubble", HidPushToTalkAppIndexSlackHubble, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Slack Hubble", HidPushToTalkAppIndexSlackHubble, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "TeamSpeak", HidPushToTalkAppIndexTeamSpeak, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "TeamSpeak", HidPushToTalkAppIndexTeamSpeak, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Teams", HidPushToTalkAppIndexTeams, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Teams", HidPushToTalkAppIndexTeams, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Zoom", HidPushToTalkAppIndexZoom, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Zoom", HidPushToTalkAppIndexZoom, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Webex", HidPushToTalkAppIndexWebex, hid_ptt_menu_callback, hid_ptt); - ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Webex", HidPushToTalkAppIndexWebex, hid_ptt_menu_callback, hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Google Meet", + HidPushToTalkAppIndexGoogleMeet, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Google Meet", + HidPushToTalkAppIndexGoogleMeet, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Google Hangouts", + HidPushToTalkAppIndexGoogleHangouts, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Google Hangouts", + HidPushToTalkAppIndexGoogleHangouts, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Discord", + HidPushToTalkAppIndexDiscord, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Discord", + HidPushToTalkAppIndexDiscord, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "FaceTime", + HidPushToTalkAppIndexFaceTime, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Jamulus", + HidPushToTalkAppIndexJamulus, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Jamulus", + HidPushToTalkAppIndexJamulus, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Signal", + HidPushToTalkAppIndexSignal, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Signal", + HidPushToTalkAppIndexSignal, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Skype", + HidPushToTalkAppIndexSkype, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Skype", + HidPushToTalkAppIndexSkype, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Slack Call", + HidPushToTalkAppIndexSlackCall, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Slack Call", + HidPushToTalkAppIndexSlackCall, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Slack Hubble", + HidPushToTalkAppIndexSlackHubble, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Slack Hubble", + HidPushToTalkAppIndexSlackHubble, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "TeamSpeak", + HidPushToTalkAppIndexTeamSpeak, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "TeamSpeak", + HidPushToTalkAppIndexTeamSpeak, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Teams", + HidPushToTalkAppIndexTeams, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Teams", + HidPushToTalkAppIndexTeams, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Zoom", + HidPushToTalkAppIndexZoom, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Zoom", + HidPushToTalkAppIndexZoom, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkMacOS, + "Webex", + HidPushToTalkAppIndexWebex, + hid_ptt_menu_callback, + hid_ptt); + ptt_menu_add_item_to_list( + hid->hid_ptt_menu, + HidPushToTalkLinux, + "Webex", + HidPushToTalkAppIndexWebex, + hid_ptt_menu_callback, + hid_ptt); hid_ptt->help = widget_alloc(); view_set_previous_callback(widget_get_view(hid_ptt->help), hid_ptt_view); - view_dispatcher_add_view(hid->view_dispatcher, HidViewPushToTalkHelp, widget_get_view(hid_ptt->help)); + view_dispatcher_add_view( + hid->view_dispatcher, HidViewPushToTalkHelp, widget_get_view(hid_ptt->help)); return hid_ptt; } @@ -793,10 +1013,13 @@ void hid_ptt_free(HidPushToTalk* hid_ptt) { furi_assert(hid_ptt); notification_message(hid_ptt->hid->notifications, &sequence_reset_red); with_view_model( - hid_ptt->view, HidPushToTalkModel * model, { - furi_string_free(model->os); - furi_string_free(model->app); - }, true); + hid_ptt->view, + HidPushToTalkModel * model, + { + furi_string_free(model->os); + furi_string_free(model->app); + }, + true); view_dispatcher_remove_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp); widget_free(hid_ptt->help); view_free(hid_ptt->view); @@ -806,10 +1029,13 @@ void hid_ptt_free(HidPushToTalk* hid_ptt) { void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected) { furi_assert(hid_ptt); with_view_model( - hid_ptt->view, HidPushToTalkModel * model, { - if (!connected && model->connected) { + hid_ptt->view, + HidPushToTalkModel * model, + { + if(!connected && model->connected) { notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); } model->connected = connected; - }, true); + }, + true); } diff --git a/applications/system/hid_app/views/hid_ptt.h b/applications/system/hid_app/views/hid_ptt.h index 44883edd2b..219e1c537c 100644 --- a/applications/system/hid_app/views/hid_ptt.h +++ b/applications/system/hid_app/views/hid_ptt.h @@ -15,5 +15,5 @@ void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected); enum HidPushToTalkOSes { HidPushToTalkMacOS, - HidPushToTalkLinux, + HidPushToTalkLinux, }; diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index d84a394f4e..074c85ba72 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -58,11 +58,12 @@ typedef struct { size_t list_position; size_t position; size_t window_position; - PushToTalkMenuList *lists; + PushToTalkMenuList* lists; int lists_count; } HidPushToTalkMenuModel; -static void hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTalkMenuItemArray_t items) { +static void + hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTalkMenuItemArray_t items) { furi_assert(context); HidPushToTalkMenuModel* model = context; const uint8_t item_height = 16; @@ -71,7 +72,8 @@ static void hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTa canvas_set_font(canvas, FontSecondary); size_t position = 0; PushToTalkMenuItemArray_it_t it; - for(PushToTalkMenuItemArray_it(it, items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) { + for(PushToTalkMenuItemArray_it(it, items); !PushToTalkMenuItemArray_end_p(it); + PushToTalkMenuItemArray_next(it)) { const size_t item_position = position - model->window_position; const size_t items_on_screen = 3; uint8_t y_offset = 16; @@ -105,15 +107,14 @@ static void hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTa position++; } - elements_scrollbar_pos(canvas, 128 , 17, 46, model->position, PushToTalkMenuItemArray_size(items)); + elements_scrollbar_pos( + canvas, 128, 17, 46, model->position, PushToTalkMenuItemArray_size(items)); } -PushToTalkMenuList * hid_ptt_menu_get_list_at_index( - void* context, - uint32_t index) { +PushToTalkMenuList* hid_ptt_menu_get_list_at_index(void* context, uint32_t index) { furi_assert(context); HidPushToTalkMenuModel* model = context; - for (int i = 0; i < model->lists_count; i++) { + for(int i = 0; i < model->lists_count; i++) { PushToTalkMenuList* list = &model->lists[i]; if(index == list->index) { return list; @@ -125,7 +126,7 @@ PushToTalkMenuList * hid_ptt_menu_get_list_at_index( static void hid_ptt_menu_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidPushToTalkMenuModel* model = context; - if (model->lists_count == 0){ + if(model->lists_count == 0) { return; } uint8_t item_width = canvas_width(canvas) - 5; @@ -139,32 +140,28 @@ static void hid_ptt_menu_draw_callback(Canvas* canvas, void* context) { FuriString* disp_str; disp_str = furi_string_alloc_set(list->label); elements_string_fit_width(canvas, disp_str, item_width - (6 * 2)); - uint8_t x_pos = (canvas_width(canvas) - canvas_string_width(canvas,furi_string_get_cstr(disp_str))) / 2; - canvas_draw_str(canvas,x_pos,11,furi_string_get_cstr(disp_str)); + uint8_t x_pos = + (canvas_width(canvas) - canvas_string_width(canvas, furi_string_get_cstr(disp_str))) / 2; + canvas_draw_str(canvas, x_pos, 11, furi_string_get_cstr(disp_str)); furi_string_free(disp_str); canvas_set_font(canvas, FontSecondary); - hid_ptt_menu_draw_list( - canvas, - context, - list->items - ); + hid_ptt_menu_draw_list(canvas, context, list->items); } -void ptt_menu_add_list( - HidPushToTalkMenu* hid_ptt_menu, - const char* label, - uint32_t index) { +void ptt_menu_add_list(HidPushToTalkMenu* hid_ptt_menu, const char* label, uint32_t index) { furi_assert(label); furi_assert(hid_ptt_menu); with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, { - if (model->lists_count == 0) { - model->lists = (PushToTalkMenuList *)malloc(sizeof(PushToTalkMenuList)); + if(model->lists_count == 0) { + model->lists = (PushToTalkMenuList*)malloc(sizeof(PushToTalkMenuList)); } else { - model->lists = (PushToTalkMenuList *)realloc(model->lists, (model->lists_count + 1) * sizeof(PushToTalkMenuList)); + model->lists = (PushToTalkMenuList*)realloc( + model->lists, (model->lists_count + 1) * sizeof(PushToTalkMenuList)); } - if (model->lists == NULL) { + if(model->lists == NULL) { FURI_LOG_E(TAG, "Memory reallocation failed (%i)", model->lists_count); return; } @@ -177,7 +174,6 @@ void ptt_menu_add_list( true); } - void ptt_menu_add_item_to_list( HidPushToTalkMenu* hid_ptt_menu, uint32_t list_index, @@ -190,10 +186,11 @@ void ptt_menu_add_item_to_list( furi_assert(hid_ptt_menu); UNUSED(list_index); with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, { PushToTalkMenuList* list = hid_ptt_menu_get_list_at_index(model, list_index); - if (list == NULL){ + if(list == NULL) { FURI_LOG_E(TAG, "Adding item %s to unknown index %li", label, list_index); return; } @@ -206,16 +203,17 @@ void ptt_menu_add_item_to_list( true); } -void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift){ +void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift) { size_t new_position = 0; uint32_t index = 0; with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, { - int new_list_position = (short) model->list_position + shift; - if (new_list_position >= model->lists_count) { + int new_list_position = (short)model->list_position + shift; + if(new_list_position >= model->lists_count) { new_list_position = 0; - } else if (new_list_position < 0) { + } else if(new_list_position < 0) { new_list_position = model->lists_count - 1; } PushToTalkMenuList* list = &model->lists[model->list_position]; @@ -225,8 +223,9 @@ void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift){ size_t position = 0; // Find item index from current list PushToTalkMenuItemArray_it_t it; - for(PushToTalkMenuItemArray_it(it, list->items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) { - if (position == model->position){ + for(PushToTalkMenuItemArray_it(it, list->items); !PushToTalkMenuItemArray_end_p(it); + PushToTalkMenuItemArray_next(it)) { + if(position == model->position) { index = PushToTalkMenuItemArray_cref(it)->index; break; } @@ -235,8 +234,10 @@ void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift){ // Try to find item with the same index in a new list position = 0; bool item_exists_in_new_list = false; - for(PushToTalkMenuItemArray_it(it, new_list->items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) { - if (PushToTalkMenuItemArray_cref(it)->index == index) { + for(PushToTalkMenuItemArray_it(it, new_list->items); + !PushToTalkMenuItemArray_end_p(it); + PushToTalkMenuItemArray_next(it)) { + if(PushToTalkMenuItemArray_cref(it)->index == index) { item_exists_in_new_list = true; new_position = position; break; @@ -246,20 +247,20 @@ void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift){ // This list item is not presented in a new list, let's try to keep position as is. // If it's out of range for the new list set it to the end - if (!item_exists_in_new_list) { + if(!item_exists_in_new_list) { new_position = items_size - 1 < model->position ? items_size - 1 : model->position; } // Tune window position. As we have 3 items on screen, keep focus centered const size_t items_on_screen = 3; - if (new_position >= items_size - 1) { - if (items_size < items_on_screen + 1) { + if(new_position >= items_size - 1) { + if(items_size < items_on_screen + 1) { new_window_position = 0; } else { new_window_position = items_size - items_on_screen; } - } else if (new_position < items_on_screen - 1) { + } else if(new_position < items_on_screen - 1) { new_window_position = 0; } else { new_window_position = new_position - 1; @@ -273,7 +274,8 @@ void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift){ void ptt_menu_process_up(HidPushToTalkMenu* hid_ptt_menu) { with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, { PushToTalkMenuList* list = &model->lists[model->list_position]; const size_t items_on_screen = 3; @@ -296,7 +298,8 @@ void ptt_menu_process_up(HidPushToTalkMenu* hid_ptt_menu) { void ptt_menu_process_down(HidPushToTalkMenu* hid_ptt_menu) { with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, { PushToTalkMenuList* list = &model->lists[model->list_position]; const size_t items_on_screen = 3; @@ -320,7 +323,8 @@ void ptt_menu_process_ok(HidPushToTalkMenu* hid_ptt_menu) { PushToTalkMenuList* list = NULL; PushToTalkMenuItem* item = NULL; with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, { list = &model->lists[model->list_position]; const size_t items_size = PushToTalkMenuItemArray_size(list->items); @@ -390,24 +394,30 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) { view_set_input_callback(hid_ptt_menu->view, hid_ptt_menu_input_callback); with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, { + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, + { model->lists_count = 0; model->position = 0; model->window_position = 0; - }, true); + }, + true); return hid_ptt_menu; } void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu) { furi_assert(hid_ptt_menu); with_view_model( - hid_ptt_menu->view, HidPushToTalkMenuModel * model, { - for (int i = 0; i < model->lists_count; i++) { + hid_ptt_menu->view, + HidPushToTalkMenuModel * model, + { + for(int i = 0; i < model->lists_count; i++) { PushToTalkMenuItemArray_clear(model->lists[i].items); furi_string_free(model->lists[i].label); } free(model->lists); - }, true); + }, + true); view_free(hid_ptt_menu->view); free(hid_ptt_menu); } diff --git a/applications/system/hid_app/views/hid_ptt_menu.h b/applications/system/hid_app/views/hid_ptt_menu.h index b273ab74d3..c6dc53d550 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.h +++ b/applications/system/hid_app/views/hid_ptt_menu.h @@ -5,7 +5,12 @@ typedef struct Hid Hid; typedef struct HidPushToTalkMenu HidPushToTalkMenu; -typedef void (*PushToTalkMenuItemCallback)(void* context, uint32_t listIndex, FuriString* listLabel, uint32_t itemIndex, FuriString* itemLabel ); +typedef void (*PushToTalkMenuItemCallback)( + void* context, + uint32_t listIndex, + FuriString* listLabel, + uint32_t itemIndex, + FuriString* itemLabel); HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* bt_hid); @@ -21,7 +26,4 @@ void ptt_menu_add_item_to_list( PushToTalkMenuItemCallback callback, void* callback_context); -void ptt_menu_add_list( - HidPushToTalkMenu* hid_ptt_menu, - const char* label, - uint32_t index); \ No newline at end of file +void ptt_menu_add_list(HidPushToTalkMenu* hid_ptt_menu, const char* label, uint32_t index); \ No newline at end of file diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index 40f58f462b..0c7881e63d 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -348,6 +348,8 @@ int32_t update_task_worker_flash_writer(void* context) { // Production furi_hal_rtc_set_log_level(FuriLogLevelDefault); furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); + furi_hal_rtc_reset_flag(FuriHalRtcFlagLegacySleep); + furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone); #endif update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); success = true; diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index c2583ae5a5..9aaff3c6f2 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -203,10 +203,3 @@ Max butthurt: 10 Min level: 3 Max level: 3 Weight: 2 - -Name: L1_New_year_128x64 -Min butthurt: 0 -Max butthurt: 10 -Min level: 1 -Max level: 3 -Weight: 7 diff --git a/assets/icons/NFC/MFKey_qr_25x25.png b/assets/icons/NFC/MFKey_qr_25x25.png new file mode 100644 index 0000000000..feb07e2807 Binary files /dev/null and b/assets/icons/NFC/MFKey_qr_25x25.png differ diff --git a/assets/icons/NFC/NFC_dolphin_emulation_47x61.png b/assets/icons/NFC/NFC_dolphin_emulation_47x61.png deleted file mode 100644 index 1783531285..0000000000 Binary files a/assets/icons/NFC/NFC_dolphin_emulation_47x61.png and /dev/null differ diff --git a/assets/icons/NFC/NFC_dolphin_emulation_51x64.png b/assets/icons/NFC/NFC_dolphin_emulation_51x64.png new file mode 100644 index 0000000000..ad5646d164 Binary files /dev/null and b/assets/icons/NFC/NFC_dolphin_emulation_51x64.png differ diff --git a/assets/icons/NFC/check_big_20x17.png b/assets/icons/NFC/check_big_20x17.png index c74e5b1c31..0e84cfa071 100644 Binary files a/assets/icons/NFC/check_big_20x17.png and b/assets/icons/NFC/check_big_20x17.png differ diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md new file mode 100644 index 0000000000..c757c0d2b4 --- /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 230a98050f..7880c041f6 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 004def9a99..901a762145 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 b56db65637..233b574b04 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 53467ecdb2..3d270816c5 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 5d11add9b9..a587d8ab27 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 826772a670..b83b4212fc 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 8794e10dc3..f18fb1681d 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 d07f7e60ba..5ddc494163 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 db4feeb4e1..abc85bb90d 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 85d915acdc..b71d78ff0a 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 d8ee05d528..c8c552bece 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/lfrfid_worker.c b/lib/lfrfid/lfrfid_worker.c index ffaa8ee925..7fda36f0b8 100644 --- a/lib/lfrfid/lfrfid_worker.c +++ b/lib/lfrfid/lfrfid_worker.c @@ -8,12 +8,14 @@ typedef enum { LFRFIDEventStopMode = (1 << 1), LFRFIDEventRead = (1 << 2), LFRFIDEventWrite = (1 << 3), - LFRFIDEventEmulate = (1 << 4), - LFRFIDEventReadRaw = (1 << 5), - LFRFIDEventEmulateRaw = (1 << 6), + LFRFIDEventWriteAndSetPass = (1 << 4), + LFRFIDEventEmulate = (1 << 5), + LFRFIDEventReadRaw = (1 << 6), + LFRFIDEventEmulateRaw = (1 << 7), LFRFIDEventAll = (LFRFIDEventStopThread | LFRFIDEventStopMode | LFRFIDEventRead | LFRFIDEventWrite | - LFRFIDEventEmulate | LFRFIDEventReadRaw | LFRFIDEventEmulateRaw), + LFRFIDEventWriteAndSetPass | LFRFIDEventEmulate | LFRFIDEventReadRaw | + LFRFIDEventEmulateRaw), } LFRFIDEventType; static int32_t lfrfid_worker_thread(void* thread_context); @@ -69,6 +71,18 @@ void lfrfid_worker_write_start( furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventWrite); } +void lfrfid_worker_write_and_set_pass_start( + LFRFIDWorker* worker, + LFRFIDProtocol protocol, + LFRFIDWorkerWriteCallback callback, + void* context) { + furi_assert(worker->mode_index == LFRFIDWorkerIdle); + worker->protocol = protocol; + worker->write_cb = callback; + worker->cb_ctx = context; + furi_thread_flags_set(furi_thread_get_id(worker->thread), LFRFIDEventWriteAndSetPass); +} + void lfrfid_worker_emulate_start(LFRFIDWorker* worker, LFRFIDProtocol protocol) { furi_assert(worker->mode_index == LFRFIDWorkerIdle); worker->protocol = protocol; @@ -145,6 +159,8 @@ static int32_t lfrfid_worker_thread(void* thread_context) { // switch mode if(flags & LFRFIDEventRead) worker->mode_index = LFRFIDWorkerRead; if(flags & LFRFIDEventWrite) worker->mode_index = LFRFIDWorkerWrite; + if(flags & LFRFIDEventWriteAndSetPass) + worker->mode_index = LFRFIDWorkerWriteAndSetPass; if(flags & LFRFIDEventEmulate) worker->mode_index = LFRFIDWorkerEmulate; if(flags & LFRFIDEventReadRaw) worker->mode_index = LFRFIDWorkerReadRaw; if(flags & LFRFIDEventEmulateRaw) worker->mode_index = LFRFIDWorkerEmulateRaw; diff --git a/lib/lfrfid/lfrfid_worker.h b/lib/lfrfid/lfrfid_worker.h index 22135097e2..7ec6b60089 100644 --- a/lib/lfrfid/lfrfid_worker.h +++ b/lib/lfrfid/lfrfid_worker.h @@ -106,6 +106,20 @@ void lfrfid_worker_write_start( LFRFIDWorkerWriteCallback callback, void* context); +/** + * @brief Start write and set pass mode + * + * @param worker + * @param protocol + * @param callback + * @param context + */ +void lfrfid_worker_write_and_set_pass_start( + LFRFIDWorker* worker, + LFRFIDProtocol protocol, + LFRFIDWorkerWriteCallback callback, + void* context); + /** * Start emulate mode * @param worker diff --git a/lib/lfrfid/lfrfid_worker_i.h b/lib/lfrfid/lfrfid_worker_i.h index 33c0bff085..7c31151bb1 100644 --- a/lib/lfrfid/lfrfid_worker_i.h +++ b/lib/lfrfid/lfrfid_worker_i.h @@ -22,6 +22,7 @@ typedef enum { LFRFIDWorkerIdle, LFRFIDWorkerRead, LFRFIDWorkerWrite, + LFRFIDWorkerWriteAndSetPass, LFRFIDWorkerEmulate, LFRFIDWorkerReadRaw, LFRFIDWorkerEmulateRaw, diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 32e2532590..17e42ad979 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -1,3 +1,4 @@ +#include "lfrfid/lfrfid_i.h" #include #include #include "lfrfid_worker_i.h" @@ -48,6 +49,15 @@ void lfrfid_worker_delay(LFRFIDWorker* worker, uint32_t milliseconds) { } } +void t5577_trace(LFRFIDT5577 t5577, const char* message) { + if(furi_log_get_level() == FuriLogLevelTrace) { + FURI_LOG_T(TAG, "%s", message); + for(uint8_t i = 0; i < 8; i++) FURI_LOG_T(TAG, "\nBlock %u %08lX", i, t5577.block[i]); + FURI_LOG_T(TAG, "Mask: %u", t5577.mask); + FURI_LOG_T(TAG, "Blocks to write: %lu", t5577.blocks_to_write); + } +} + /**************************************************************************************************/ /********************************************** READ **********************************************/ /**************************************************************************************************/ @@ -574,6 +584,100 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { free(read_data); } +static void lfrfid_worker_mode_write_and_set_pass_process(LFRFIDWorker* worker) { + LFRFIDProtocol protocol = worker->protocol; + LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest)); + request->write_type = LFRFIDWriteTypeT5577; + + bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request); + + uint32_t write_start_time = furi_get_tick(); + bool too_long = false; + size_t unsuccessful_reads = 0; + + size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol); + uint8_t* verify_data = malloc(data_size); + uint8_t* read_data = malloc(data_size); + protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size); + + if(can_be_written) { + while(!lfrfid_worker_check_for_stop(worker)) { + FURI_LOG_D(TAG, "Data write with pass"); + + LfRfid* app = worker->cb_ctx; + uint32_t pass = (app->password[0] << 24) | (app->password[1] << 16) | + (app->password[2] << 8) | (app->password[3]); + + request->t5577.mask = 0b10000001; + for(uint8_t i = 0; i < request->t5577.blocks_to_write; i++) + request->t5577.mask |= (1 << i); + + request->t5577.block[0] |= (1 << 4); + request->t5577.block[7] = pass; + + t5577_trace(request->t5577, "Write with password"); + + t5577_write_with_mask(&request->t5577, 0, true, 0); + + ProtocolId read_result = PROTOCOL_NO; + LFRFIDWorkerReadState state = lfrfid_worker_read_internal( + worker, + protocol_dict_get_features(worker->protocols, protocol), + LFRFID_WORKER_WRITE_VERIFY_TIME_MS, + &read_result); + + if(state == LFRFIDWorkerReadOK) { + bool read_success = false; + + if(read_result == protocol) { + protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); + + if(memcmp(read_data, verify_data, data_size) == 0) { + read_success = true; + } + } + + if(read_success) { + FURI_LOG_D(TAG, "Write with password %08lX success", pass); + + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + } + break; + } else { + unsuccessful_reads++; + + if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); + } + } + } + } else if(state == LFRFIDWorkerReadExit) { + break; + } + + if(!too_long && + (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { + too_long = true; + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); + } + } + + lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); + } + } else { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); + } + } + + free(request); + free(verify_data); + free(read_data); +} + /**************************************************************************************************/ /******************************************* READ RAW *********************************************/ /**************************************************************************************************/ @@ -629,6 +733,7 @@ const LFRFIDWorkerModeType lfrfid_worker_modes[] = { [LFRFIDWorkerIdle] = {.process = NULL}, [LFRFIDWorkerRead] = {.process = lfrfid_worker_mode_read_process}, [LFRFIDWorkerWrite] = {.process = lfrfid_worker_mode_write_process}, + [LFRFIDWorkerWriteAndSetPass] = {.process = lfrfid_worker_mode_write_and_set_pass_process}, [LFRFIDWorkerEmulate] = {.process = lfrfid_worker_mode_emulate_process}, [LFRFIDWorkerReadRaw] = {.process = lfrfid_worker_mode_read_raw_process}, [LFRFIDWorkerEmulateRaw] = {.process = lfrfid_worker_mode_emulate_raw_process}, diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index 04386a6752..a3ab56f25b 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/lfrfid/tools/t5577.c b/lib/lfrfid/tools/t5577.c index 666a5c8fe4..7d6d6d298e 100644 --- a/lib/lfrfid/tools/t5577.c +++ b/lib/lfrfid/tools/t5577.c @@ -1,6 +1,7 @@ #include "t5577.h" #include #include +#include #define T5577_TIMING_WAIT_TIME 400 #define T5577_TIMING_START_GAP 30 @@ -13,6 +14,9 @@ #define T5577_OPCODE_PAGE_1 0b11 #define T5577_OPCODE_RESET 0b00 +#define T5577_BLOCKS_IN_PAGE_0 8 +#define T5577_BLOCKS_IN_PAGE_1 4 + static void t5577_start() { furi_hal_rfid_tim_read_start(125000, 0.5); @@ -52,6 +56,7 @@ static void t5577_write_reset() { } static void t5577_write_block_pass( + uint8_t page, uint8_t block, bool lock_bit, uint32_t data, @@ -62,8 +67,8 @@ static void t5577_write_block_pass( // start gap t5577_write_gap(T5577_TIMING_START_GAP); - // opcode for page 0 - t5577_write_opcode(T5577_OPCODE_PAGE_0); + // opcode for page + t5577_write_opcode((page == 1) ? T5577_OPCODE_PAGE_1 : T5577_OPCODE_PAGE_0); // password if(with_pass) { @@ -92,7 +97,7 @@ static void t5577_write_block_pass( } static void t5577_write_block_simple(uint8_t block, bool lock_bit, uint32_t data) { - t5577_write_block_pass(block, lock_bit, data, false, 0); + t5577_write_block_pass(0, block, lock_bit, data, false, 0); } void t5577_write(LFRFIDT5577* data) { @@ -110,9 +115,28 @@ void t5577_write_with_pass(LFRFIDT5577* data, uint32_t password) { t5577_start(); FURI_CRITICAL_ENTER(); for(size_t i = 0; i < data->blocks_to_write; i++) { - t5577_write_block_pass(i, false, data->block[i], true, password); + t5577_write_block_pass(0, i, false, data->block[i], true, password); } t5577_write_reset(); FURI_CRITICAL_EXIT(); t5577_stop(); } + +void t5577_write_with_mask(LFRFIDT5577* data, uint8_t page, bool with_pass, uint32_t password) { + t5577_start(); + FURI_CRITICAL_ENTER(); + + uint8_t mask = data->mask; + + size_t pages_total = (page == 0) ? T5577_BLOCKS_IN_PAGE_0 : T5577_BLOCKS_IN_PAGE_1; + + for(size_t i = 0; i < pages_total; i++) { + bool need_to_write = mask & 1; + mask >>= 1; + if(!need_to_write) continue; + t5577_write_block_pass(page, i, false, data->block[i], with_pass, password); + } + t5577_write_reset(); + FURI_CRITICAL_EXIT(); + t5577_stop(); +} \ No newline at end of file diff --git a/lib/lfrfid/tools/t5577.h b/lib/lfrfid/tools/t5577.h index c77984476f..f7b5cc4f5a 100644 --- a/lib/lfrfid/tools/t5577.h +++ b/lib/lfrfid/tools/t5577.h @@ -42,6 +42,7 @@ extern "C" { typedef struct { uint32_t block[LFRFID_T5577_BLOCK_COUNT]; uint32_t blocks_to_write; + uint8_t mask; } LFRFIDT5577; /** @@ -53,6 +54,8 @@ void t5577_write(LFRFIDT5577* data); void t5577_write_with_pass(LFRFIDT5577* data, uint32_t password); +void t5577_write_with_mask(LFRFIDT5577* data, uint8_t page, bool with_pass, uint32_t password); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 41332362c8..3ad62f322a 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -22,6 +22,7 @@ env.Append( File("protocols/mf_ultralight/mf_ultralight.h"), File("protocols/mf_classic/mf_classic.h"), File("protocols/mf_desfire/mf_desfire.h"), + File("protocols/emv/emv.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), # Pollers @@ -32,6 +33,7 @@ env.Append( File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), + File("protocols/emv/emv_poller.h"), File("protocols/st25tb/st25tb_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 26f4dc3b7b..b21e22423a 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -7,6 +7,18 @@ #define ISO14443_4_BLOCK_PCB_R (5U << 5) #define ISO14443_4_BLOCK_PCB_S (3U << 6) +#define ISO14443_4_BLOCK_PCB_I_ (0U << 6) +#define ISO14443_4_BLOCK_PCB_R_ (2U << 6) +#define ISO14443_4_BLOCK_PCB_TYPE_MASK (3U << 6) + +#define ISO14443_4_BLOCK_PCB_S_DESELECT (0U << 4) +#define ISO14443_4_BLOCK_PCB_S_WTX (3U << 4) +#define ISO14443_4_BLOCK_PCB_BLOCK_NUMBER (1U << 0) + +#define ISO14443_4_BLOCK_PCB_NAD (1U << 2) +#define ISO14443_4_BLOCK_PCB_CID (1U << 3) +#define ISO14443_4_BLOCK_PCB_CHAINING (1U << 4) + struct Iso14443_4Layer { uint8_t pcb; uint8_t pcb_prev; @@ -62,3 +74,56 @@ bool iso14443_4_layer_decode_block( return ret; } + +Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( + Iso14443_4Layer* instance, + BitBuffer* output_data, + const BitBuffer* block_data) { + furi_assert(instance); + + Iso14443_4aError ret = Iso14443_4aErrorProtocol; + bit_buffer_reset(output_data); + + do { + const uint8_t pcb_field = bit_buffer_get_byte(block_data, 0); + const uint8_t block_type = pcb_field & ISO14443_4_BLOCK_PCB_TYPE_MASK; + switch(block_type) { + case ISO14443_4_BLOCK_PCB_I_: + if(pcb_field == instance->pcb_prev) { + bit_buffer_copy_right(output_data, block_data, 1); + ret = Iso14443_4aErrorNone; + } else { + // send original request again + ret = Iso14443_4aErrorSendExtra; + } + break; + case ISO14443_4_BLOCK_PCB_R_: + // TODO + break; + case ISO14443_4_BLOCK_PCB_S: + if((pcb_field & ISO14443_4_BLOCK_PCB_S_WTX) == ISO14443_4_BLOCK_PCB_S_WTX) { + const uint8_t inf_field = bit_buffer_get_byte(block_data, 1); + //const uint8_t power_level = inf_field >> 6; + const uint8_t wtxm = inf_field & 0b111111; + //uint32_t fwt_temp = MIN((fwt * wtxm), fwt_max); + + bit_buffer_append_byte( + output_data, + ISO14443_4_BLOCK_PCB_S | ISO14443_4_BLOCK_PCB_S_WTX | ISO14443_4_BLOCK_PCB); + bit_buffer_append_byte(output_data, wtxm); + ret = Iso14443_4aErrorSendExtra; + } + break; + } + } while(false); + + if(ret != Iso14443_4aErrorNone) { + FURI_LOG_RAW_T("RAW RX:"); + for(size_t x = 0; x < bit_buffer_get_size_bytes(block_data); x++) { + FURI_LOG_RAW_T("%02X ", bit_buffer_get_byte(block_data, x)); + } + FURI_LOG_RAW_T("\r\n"); + } + + return ret; +} diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 712173ce1b..14e435c2fd 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -1,5 +1,6 @@ #pragma once +#include "protocols/iso14443_4a/iso14443_4a.h" #include #ifdef __cplusplus @@ -24,6 +25,11 @@ bool iso14443_4_layer_decode_block( BitBuffer* output_data, const BitBuffer* block_data); +Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( + Iso14443_4Layer* instance, + BitBuffer* output_data, + const BitBuffer* block_data); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/emv/emv.c b/lib/nfc/protocols/emv/emv.c new file mode 100644 index 0000000000..4cdacaefe3 --- /dev/null +++ b/lib/nfc/protocols/emv/emv.c @@ -0,0 +1,188 @@ +//#include "emv_i.h" + +#include "flipper_format.h" +#include +#include "protocols/emv/emv.h" +#include +#include +#include + +#define EMV_PROTOCOL_NAME "EMV" + +const NfcDeviceBase nfc_device_emv = { + .protocol_name = EMV_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)emv_alloc, + .free = (NfcDeviceFree)emv_free, + .reset = (NfcDeviceReset)emv_reset, + .copy = (NfcDeviceCopy)emv_copy, + .verify = (NfcDeviceVerify)emv_verify, + .load = (NfcDeviceLoad)emv_load, + .save = (NfcDeviceSave)emv_save, + .is_equal = (NfcDeviceEqual)emv_is_equal, + .get_name = (NfcDeviceGetName)emv_get_device_name, + .get_uid = (NfcDeviceGetUid)emv_get_uid, + .set_uid = (NfcDeviceSetUid)emv_set_uid, + .get_base_data = (NfcDeviceGetBaseData)emv_get_base_data, +}; + +EmvData* emv_alloc() { + EmvData* data = malloc(sizeof(EmvData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->emv_application.pin_try_counter = 0xff; + + return data; +} + +void emv_free(EmvData* data) { + furi_assert(data); + + emv_reset(data); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void emv_reset(EmvData* data) { + furi_assert(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->emv_application, 0, sizeof(EmvApplication)); +} + +void emv_copy(EmvData* destination, const EmvData* source) { + furi_assert(destination); + furi_assert(source); + + emv_reset(destination); + + iso14443_4a_copy(destination->iso14443_4a_data, source->iso14443_4a_data); + destination->emv_application = source->emv_application; +} + +bool emv_verify(EmvData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, EMV_PROTOCOL_NAME); +} + +bool emv_load(EmvData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool parsed = false; + + do { + // Read ISO14443_4A data + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + EmvApplication* app = &data->emv_application; + + //Read name + if(!flipper_format_read_string(ff, "Name", temp_str)) break; + strcpy(app->name, furi_string_get_cstr(temp_str)); + if(app->name[0] != '\0') app->name_found = true; + + uint32_t pan_len; + if(!flipper_format_read_uint32(ff, "PAN length", &pan_len, 1)) break; + app->pan_len = pan_len; + + if(!flipper_format_read_hex(ff, "PAN", app->pan, pan_len)) break; + + uint32_t aid_len; + if(!flipper_format_read_uint32(ff, "AID length", &aid_len, 1)) break; + app->aid_len = aid_len; + + if(!flipper_format_read_hex(ff, "AID", app->aid, aid_len)) break; + + if(!flipper_format_read_hex(ff, "Country code", (uint8_t*)&app->country_code, 2)) break; + + if(!flipper_format_read_hex(ff, "Currency code", (uint8_t*)&app->currency_code, 2)) break; + + if(!flipper_format_read_hex(ff, "Expiration year", &app->exp_year, 1)) break; + if(!flipper_format_read_hex(ff, "Expiration month", &app->exp_month, 1)) break; + + uint32_t pin_try_counter; + if(!flipper_format_read_uint32(ff, "PIN counter", &pin_try_counter, 1)) break; + app->pin_try_counter = pin_try_counter; + + parsed = true; + } while(false); + + furi_string_free(temp_str); + + return parsed; +} + +bool emv_save(const EmvData* data, FlipperFormat* ff) { + furi_assert(data); + + FuriString* temp_str = furi_string_alloc(); + bool saved = false; + + do { + EmvApplication app = data->emv_application; + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, "EMV specific data:\n")) break; + + if(!flipper_format_write_string_cstr(ff, "Name", app.name)) break; + + uint32_t pan_len = app.pan_len; + if(!flipper_format_write_uint32(ff, "PAN length", &pan_len, 1)) break; + + if(!flipper_format_write_hex(ff, "PAN", app.pan, pan_len)) break; + + uint32_t aid_len = app.aid_len; + if(!flipper_format_write_uint32(ff, "AID length", &aid_len, 1)) break; + + if(!flipper_format_write_hex(ff, "AID", app.aid, aid_len)) break; + + if(!flipper_format_write_hex(ff, "Country code", (uint8_t*)&app.country_code, 2)) break; + + if(!flipper_format_write_hex(ff, "Currency code", (uint8_t*)&app.currency_code, 2)) break; + + if(!flipper_format_write_hex(ff, "Expiration year", (uint8_t*)&app.exp_year, 1)) break; + + if(!flipper_format_write_hex(ff, "Expiration month", (uint8_t*)&app.exp_month, 1)) break; + + if(!flipper_format_write_uint32(ff, "PIN counter", (uint32_t*)&app.pin_try_counter, 1)) + break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + + return saved; +} + +bool emv_is_equal(const EmvData* data, const EmvData* other) { + furi_assert(data); + furi_assert(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + memcmp(&data->emv_application, &other->emv_application, sizeof(EmvApplication)) == 0; +} + +const char* emv_get_device_name(const EmvData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return EMV_PROTOCOL_NAME; +} + +const uint8_t* emv_get_uid(const EmvData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool emv_set_uid(EmvData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* emv_get_base_data(const EmvData* data) { + furi_assert(data); + + return data->iso14443_4a_data; +} \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv.h b/lib/nfc/protocols/emv/emv.h new file mode 100644 index 0000000000..42aa1a703b --- /dev/null +++ b/lib/nfc/protocols/emv/emv.h @@ -0,0 +1,136 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_APDU_LEN 255 + +#define EMV_REQ_GET_DATA 0x80CA + +#define EMV_TAG_APP_TEMPLATE 0x61 +#define EMV_TAG_AID 0x4F +#define EMV_TAG_PRIORITY 0x87 +#define EMV_TAG_PDOL 0x9F38 +#define EMV_TAG_CARD_NAME 0x50 +#define EMV_TAG_FCI 0xBF0C +#define EMV_TAG_PIN_TRY_COUNTER 0x9F17 +#define EMV_TAG_LOG_ENTRY 0x9F4D +#define EMV_TAG_LOG_FMT 0x9F4F + +#define EMV_TAG_LAST_ONLINE_ATC 0x9F13 +#define EMV_TAG_ATC 0x9F36 +#define EMV_TAG_LOG_AMOUNT 0x9F02 +#define EMV_TAG_LOG_COUNTRY 0x9F1A +#define EMV_TAG_LOG_CURRENCY 0x5F2A +#define EMV_TAG_LOG_DATE 0x9A +#define EMV_TAG_LOG_TIME 0x9F21 + +#define EMV_TAG_TRACK_1_EQUIV 0x56 +#define EMV_TAG_TRACK_2_EQUIV 0x57 +#define EMV_TAG_PAN 0x5A +#define EMV_TAG_AFL 0x94 +#define EMV_TAG_EXP_DATE 0x5F24 +#define EMV_TAG_COUNTRY_CODE 0x5F28 +#define EMV_TAG_CURRENCY_CODE 0x9F42 +#define EMV_TAG_CARDHOLDER_NAME 0x5F20 +#define EMV_TAG_TRACK_2_DATA 0x9F6B +#define EMV_TAG_GPO_FMT1 0x80 + +#define EMV_TAG_RESP_BUF_SIZE 0x6C +#define EMV_TAG_RESP_BYTES_AVAILABLE 0x61 + +typedef struct { + uint16_t tag; + uint8_t data[]; +} PDOLValue; + +typedef struct { + uint8_t size; + uint8_t data[MAX_APDU_LEN]; +} APDU; + +typedef struct { + uint16_t atc; + uint64_t amount; + uint16_t country; + uint16_t currency; + uint32_t date; + uint32_t time; +} Transaction; + +typedef struct { + uint8_t log_sfi; + uint8_t log_records; + uint8_t log_fmt[50]; + uint8_t log_fmt_len; + uint8_t active_tr; + bool saving_trans_list; + Transaction trans[16]; + uint8_t priority; + uint8_t aid[16]; + uint8_t aid_len; + char name[32]; + bool name_found; + uint8_t pan[10]; // card_number + uint8_t pan_len; + uint8_t exp_month; + uint8_t exp_year; + uint16_t country_code; + uint16_t currency_code; + uint8_t pin_try_counter; + uint16_t transaction_counter; + uint16_t last_online_atc; + APDU pdol; + APDU afl; +} EmvApplication; + +typedef enum { + EmvErrorNone = 0, + EmvErrorNotPresent, + EmvErrorProtocol, + EmvErrorTimeout, +} EmvError; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + EmvApplication emv_application; +} EmvData; + +extern const NfcDeviceBase nfc_device_emv; + +// Virtual methods + +EmvData* emv_alloc(); + +void emv_free(EmvData* data); + +void emv_reset(EmvData* data); + +void emv_copy(EmvData* data, const EmvData* other); + +bool emv_verify(EmvData* data, const FuriString* device_type); + +bool emv_load(EmvData* data, FlipperFormat* ff, uint32_t version); + +bool emv_save(const EmvData* data, FlipperFormat* ff); + +bool emv_is_equal(const EmvData* data, const EmvData* other); + +const char* emv_get_device_name(const EmvData* data, NfcDeviceNameType name_type); + +const uint8_t* emv_get_uid(const EmvData* data, size_t* uid_len); + +bool emv_set_uid(EmvData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* emv_get_base_data(const EmvData* data); + +// Getters and tests + +//const EmvApplication* emv_get_application(const EmvData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/emv/emv_poller.c b/lib/nfc/protocols/emv/emv_poller.c new file mode 100644 index 0000000000..6ca21df1c5 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller.c @@ -0,0 +1,202 @@ +#include "emv_poller_i.h" + +#include + +#include + +#define TAG "EMVPoller" + +// MAX Le is 255 bytes + 2 for CRC +#define EMV_BUF_SIZE (512U) + +typedef NfcCommand (*EmvPollerReadHandler)(EmvPoller* instance); + +const EmvData* emv_poller_get_data(EmvPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static EmvPoller* emv_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + EmvPoller* instance = malloc(sizeof(EmvPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = emv_alloc(); + instance->tx_buffer = bit_buffer_alloc(EMV_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(EMV_BUF_SIZE); + + instance->state = EmvPollerStateIdle; + + instance->emv_event.data = &instance->emv_event_data; + + instance->general_event.protocol = NfcProtocolEmv; + instance->general_event.event_data = &instance->emv_event; + instance->general_event.instance = instance; + + return instance; +} + +static void emv_poller_free(EmvPoller* instance) { + furi_assert(instance); + + emv_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand emv_poller_handler_idle(EmvPoller* instance) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = EmvPollerStateSelectPPSE; + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_select_ppse(EmvPoller* instance) { + instance->error = emv_poller_select_ppse(instance); + + if(instance->error == EmvErrorNone) { + FURI_LOG_D(TAG, "Select PPSE success"); + instance->state = EmvPollerStateSelectApplication; + } else { + FURI_LOG_E(TAG, "Failed to select PPSE"); + instance->state = EmvPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_select_application(EmvPoller* instance) { + instance->error = emv_poller_select_application(instance); + + if(instance->error == EmvErrorNone) { + FURI_LOG_D(TAG, "Select application success"); + } else { + FURI_LOG_E(TAG, "Failed to select application"); + // We have to try GPO request with empty tag + } + instance->state = EmvPollerStateGetProcessingOptions; + + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance) { + instance->error = emv_poller_get_processing_options(instance); + + if(instance->error == EmvErrorNone) { + FURI_LOG_D(TAG, "Get processing options success"); + } else { + FURI_LOG_E(TAG, "Failed to get processing options"); + } + + // Read another informations + instance->state = EmvPollerStateReadFiles; + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) { + emv_poller_read_afl(instance); + emv_poller_read_log_entry(instance); + + instance->state = EmvPollerStateReadExtra; + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_read_extra_data(EmvPoller* instance) { + emv_poller_get_last_online_atc(instance); + emv_poller_get_pin_try_counter(instance); + + instance->state = EmvPollerStateReadSuccess; + return NfcCommandContinue; +} + +static NfcCommand emv_poller_handler_read_fail(EmvPoller* instance) { + FURI_LOG_D(TAG, "Read failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->emv_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = EmvPollerStateIdle; + return command; +} + +static NfcCommand emv_poller_handler_read_success(EmvPoller* instance) { + FURI_LOG_D(TAG, "Read success"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->emv_event.type = EmvPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const EmvPollerReadHandler emv_poller_read_handler[EmvPollerStateNum] = { + [EmvPollerStateIdle] = emv_poller_handler_idle, + [EmvPollerStateSelectPPSE] = emv_poller_handler_select_ppse, + [EmvPollerStateSelectApplication] = emv_poller_handler_select_application, + [EmvPollerStateGetProcessingOptions] = emv_poller_handler_get_processing_options, + [EmvPollerStateReadFiles] = emv_poller_handler_read_files, + [EmvPollerStateReadExtra] = emv_poller_handler_read_extra_data, + [EmvPollerStateReadFailed] = emv_poller_handler_read_fail, + [EmvPollerStateReadSuccess] = emv_poller_handler_read_success, +}; + +static void + emv_poller_set_callback(EmvPoller* instance, NfcGenericCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand emv_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + EmvPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = emv_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->emv_event.type = EmvPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool emv_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + EmvPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + const EmvError error = emv_poller_select_ppse(instance); + protocol_detected = (error == EmvErrorNone); + } + + return protocol_detected; +} + +const NfcPollerBase emv_poller = { + .alloc = (NfcPollerAlloc)emv_poller_alloc, + .free = (NfcPollerFree)emv_poller_free, + .set_callback = (NfcPollerSetCallback)emv_poller_set_callback, + .run = (NfcPollerRun)emv_poller_run, + .detect = (NfcPollerDetect)emv_poller_detect, + .get_data = (NfcPollerGetData)emv_poller_get_data, +}; \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller.h b/lib/nfc/protocols/emv/emv_poller.h new file mode 100644 index 0000000000..64bd0be9d2 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller.h @@ -0,0 +1,59 @@ +#pragma once + +#include "emv.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief EmvPoller opaque type definition. + */ +typedef struct EmvPoller EmvPoller; + +/** + * @brief Enumeration of possible Emv poller event types. + */ +typedef enum { + EmvPollerEventTypeReadSuccess, /**< Card was read successfully. */ + EmvPollerEventTypeReadFailed, /**< Poller failed to read card. */ +} EmvPollerEventType; + +/** + * @brief Emv poller event data. + */ +typedef union { + EmvError error; /**< Error code indicating card reading fail reason. */ +} EmvPollerEventData; + +/** + * @brief Emv poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + EmvPollerEventType type; /**< Type of emmitted event. */ + EmvPollerEventData* data; /**< Pointer to event specific data. */ +} EmvPollerEvent; + +EmvError emv_poller_select_ppse(EmvPoller* instance); + +EmvError emv_poller_select_application(EmvPoller* instance); + +EmvError emv_poller_get_processing_options(EmvPoller* instance); + +EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t record_num); + +EmvError emv_poller_read_afl(EmvPoller* instance); + +EmvError emv_poller_read_log_entry(EmvPoller* instance); + +EmvError emv_poller_get_pin_try_counter(EmvPoller* instance); + +EmvError emv_poller_get_last_online_atc(EmvPoller* instance); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller_defs.h b/lib/nfc/protocols/emv/emv_poller_defs.h new file mode 100644 index 0000000000..001910112f --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase emv_poller; \ No newline at end of file diff --git a/lib/nfc/protocols/emv/emv_poller_i.c b/lib/nfc/protocols/emv/emv_poller_i.c new file mode 100644 index 0000000000..c237125b24 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller_i.c @@ -0,0 +1,727 @@ +#include "emv_poller_i.h" +#include "protocols/emv/emv.h" + +#define TAG "EMVPoller" + +const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information +const PDOLValue pdol_term_type = {0x9F5A, {0x00}}; // Terminal transaction type +const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator +const PDOLValue pdol_term_trans_qualifies = { + 0x9F66, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_addtnl_term_qualifies = { + 0x9F40, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_amount_authorise = { + 0x9F02, + {0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised +const PDOLValue pdol_amount = {0x9F03, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Amount +const PDOLValue pdol_country_code = {0x9F1A, {0x01, 0x24}}; // Terminal country code +const PDOLValue pdol_currency_code = {0x5F2A, {0x01, 0x24}}; // Transaction currency code +const PDOLValue pdol_term_verification = { + 0x95, + {0x00, 0x00, 0x00, 0x00, 0x00}}; // Terminal verification results +const PDOLValue pdol_transaction_date = {0x9A, {0x19, 0x01, 0x01}}; // Transaction date +const PDOLValue pdol_transaction_type = {0x9C, {0x00}}; // Transaction type +const PDOLValue pdol_transaction_cert = {0x98, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; // Transaction cert +const PDOLValue pdol_unpredict_number = {0x9F37, {0x82, 0x3D, 0xDE, 0x7A}}; // Unpredictable number + +const PDOLValue* const pdol_values[] = { + &pdol_term_info, + &pdol_term_type, + &pdol_merchant_type, + &pdol_term_trans_qualifies, + &pdol_addtnl_term_qualifies, + &pdol_amount_authorise, + &pdol_amount, + &pdol_country_code, + &pdol_currency_code, + &pdol_term_verification, + &pdol_transaction_date, + &pdol_transaction_type, + &pdol_transaction_cert, + &pdol_unpredict_number, +}; + +EmvError emv_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return EmvErrorNone; + case Iso14443_4aErrorNotPresent: + return EmvErrorNotPresent; + case Iso14443_4aErrorTimeout: + return EmvErrorTimeout; + default: + return EmvErrorProtocol; + } +} + +static void emv_trace(EmvPoller* instance, const char* message) { + if(furi_log_get_level() == FuriLogLevelTrace) { + FURI_LOG_T(TAG, "%s", message); + + printf("TX: "); + size_t size = bit_buffer_get_size_bytes(instance->tx_buffer); + for(size_t i = 0; i < size; i++) { + printf("%02X ", bit_buffer_get_byte(instance->tx_buffer, i)); + } + + printf("\r\nRX: "); + size = bit_buffer_get_size_bytes(instance->rx_buffer); + for(size_t i = 0; i < size; i++) { + printf("%02X ", bit_buffer_get_byte(instance->rx_buffer, i)); + } + printf("\r\n"); + } +} + +static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { + bool tag_found; + for(uint16_t i = 0; i < src->size; i++) { + tag_found = false; + for(uint8_t j = 0; j < sizeof(pdol_values) / sizeof(PDOLValue*); j++) { + if(src->data[i] == pdol_values[j]->tag) { + // Found tag with 1 byte length + uint8_t len = src->data[++i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } else if(((src->data[i] << 8) | src->data[i + 1]) == pdol_values[j]->tag) { + // Found tag with 2 byte length + i += 2; + uint8_t len = src->data[i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } + } + if(!tag_found) { + // Unknown tag, fill zeros + i += 2; + uint8_t len = src->data[i]; + memset(dest->data + dest->size, 0, len); + dest->size += len; + } + } + return dest->size; +} + +static bool + emv_decode_tlv_tag(const uint8_t* buff, uint16_t tag, uint8_t tlen, EmvApplication* app) { + uint8_t i = 0; + bool success = false; + + switch(tag) { + case EMV_TAG_LOG_FMT: + furi_check(tlen < sizeof(app->log_fmt)); + memcpy(app->log_fmt, &buff[i], tlen); + app->log_fmt_len = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_LOG_FMT %X: len %d", tag, tlen); + break; + case EMV_TAG_GPO_FMT1: + // skip AIP + i += 2; + tlen -= 2; + furi_check(tlen < sizeof(app->afl.data)); + memcpy(app->afl.data, &buff[i], tlen); + app->afl.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_GPO_FMT1 %X: ", tag); + break; + case EMV_TAG_AID: + app->aid_len = tlen; + memcpy(app->aid, &buff[i], tlen); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AID %X: ", tag); + for(size_t x = 0; x < tlen; x++) { + FURI_LOG_RAW_T("%02X ", app->aid[x]); + } + FURI_LOG_RAW_T("\r\n"); + break; + case EMV_TAG_PRIORITY: + memcpy(&app->priority, &buff[i], tlen); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority); + break; + case EMV_TAG_CARD_NAME: + memcpy(app->name, &buff[i], tlen); + app->name[tlen] = '\0'; + app->name_found = true; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name); + break; + case EMV_TAG_PDOL: + memcpy(app->pdol.data, &buff[i], tlen); + app->pdol.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_AFL: + memcpy(app->afl.data, &buff[i], tlen); + app->afl.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen); + break; + // Tracks data https://murdoch.is/papers/defcon20emvdecode.pdf + case EMV_TAG_TRACK_1_EQUIV: { + // Contain PAN and expire date + char track_1_equiv[80]; + memcpy(track_1_equiv, &buff[i], tlen); + track_1_equiv[tlen] = '\0'; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv); + break; + } + case EMV_TAG_TRACK_2_DATA: + case EMV_TAG_TRACK_2_EQUIV: { + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X", tag); + // 0xD0 delimits PAN from expiry (YYMM) + for(int x = 1; x < tlen; x++) { + if(buff[i + x + 1] > 0xD0) { + memcpy(app->pan, &buff[i], x + 1); + app->pan_len = x + 1; + app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4); + app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4); + break; + } + } + + // Convert 4-bit to ASCII representation + char track_2_equiv[41]; + uint8_t track_2_equiv_len = 0; + for(int x = 0; x < tlen; x++) { + char top = (buff[i + x] >> 4) + '0'; + char bottom = (buff[i + x] & 0x0F) + '0'; + track_2_equiv[x * 2] = top; + track_2_equiv_len++; + if(top == '?') break; + track_2_equiv[x * 2 + 1] = bottom; + track_2_equiv_len++; + if(bottom == '?') break; + } + track_2_equiv[track_2_equiv_len] = '\0'; + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X : %s", tag, track_2_equiv); + success = true; + break; + } + case EMV_TAG_CARDHOLDER_NAME: { + char name[27]; + memcpy(name, &buff[i], tlen); + name[tlen] = '\0'; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_CARDHOLDER_NAME %x: %s", tag, name); + break; + } + case EMV_TAG_PAN: + memcpy(app->pan, &buff[i], tlen); + app->pan_len = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_PAN %x", tag); + break; + case EMV_TAG_EXP_DATE: + app->exp_year = buff[i]; + app->exp_month = buff[i + 1]; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag); + break; + case EMV_TAG_CURRENCY_CODE: + app->currency_code = (buff[i] << 8 | buff[i + 1]); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_CURRENCY_CODE %x", tag); + break; + case EMV_TAG_COUNTRY_CODE: + app->country_code = (buff[i] << 8 | buff[i + 1]); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_COUNTRY_CODE %x", tag); + break; + case EMV_TAG_LOG_ENTRY: + app->log_sfi = buff[i]; + app->log_records = buff[i + 1]; + success = true; + FURI_LOG_T( + TAG, + "found EMV_TAG_LOG_ENTRY %x: sfi 0x%x, records %d", + tag, + app->log_sfi, + app->log_records); + break; + case EMV_TAG_LAST_ONLINE_ATC: + app->last_online_atc = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_ATC: + if(app->saving_trans_list) + app->trans[app->active_tr].atc = (buff[i] << 8 | buff[i + 1]); + else + app->transaction_counter = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_LOG_AMOUNT: + memcpy(&app->trans[app->active_tr].amount, &buff[i], tlen); + success = true; + break; + case EMV_TAG_LOG_COUNTRY: + app->trans[app->active_tr].country = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_LOG_CURRENCY: + app->trans[app->active_tr].currency = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_LOG_DATE: + memcpy(&app->trans[app->active_tr].date, &buff[i], tlen); + success = true; + break; + case EMV_TAG_LOG_TIME: + memcpy(&app->trans[app->active_tr].time, &buff[i], tlen); + success = true; + break; + case EMV_TAG_PIN_TRY_COUNTER: + app->pin_try_counter = buff[i]; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_PIN_TRY_COUNTER %x: %d", tag, app->pin_try_counter); + break; + } + return success; +} + +static bool emv_response_error(const uint8_t* buff, uint16_t len) { + uint8_t i = 0; + uint8_t first_byte = 0; + bool error = true; + + first_byte = buff[i]; + + if((len == 2) && ((first_byte >> 4) == 6)) { + switch(buff[i]) { + case EMV_TAG_RESP_BUF_SIZE: + FURI_LOG_T(TAG, " Wrong length. Read %02X bytes", buff[i + 1]); + // Need to request SFI again with this length value + return error; + case EMV_TAG_RESP_BYTES_AVAILABLE: + FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]); + // Need to request one more time + return error; + + default: + FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]); + return error; + } + } + return false; +} + +static bool + emv_parse_tag(const uint8_t* buff, uint16_t len, uint16_t* t, uint8_t* tl, uint8_t* off) { + uint8_t i = *off; + uint16_t tag = 0; + uint8_t first_byte = 0; + uint8_t tlen = 0; + bool success = false; + + first_byte = buff[i]; + + if(emv_response_error(buff, len)) return success; + + if((first_byte & 31) == 31) { // 2-byte tag + tag = buff[i] << 8 | buff[i + 1]; + i++; + FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag); + } else { + tag = buff[i]; + FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag); + } + i++; + tlen = buff[i]; + if((tlen & 128) == 128) { // long length value + i++; + tlen = buff[i]; + FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen); + } else { + FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen); + } + i++; + + *off = i; + *t = tag; + *tl = tlen; + success = true; + return success; +} + +static bool emv_decode_tl( + const uint8_t* buff, + uint16_t len, + const uint8_t* fmt, + uint8_t fmt_len, + EmvApplication* app) { + uint8_t i = 0; + uint8_t f = 0; + uint16_t tag = 0; + uint8_t tlen = 0; + bool success = false; + + if(emv_response_error(buff, len)) return success; + + while(f < fmt_len && i < len) { + success = emv_parse_tag(fmt, fmt_len, &tag, &tlen, &f); + if(!success) return success; + emv_decode_tlv_tag(&buff[i], tag, tlen, app); + i += tlen; + } + success = true; + return success; +} + +static bool emv_decode_response_tlv(const uint8_t* buff, uint8_t len, EmvApplication* app) { + uint8_t i = 0; + uint16_t tag = 0; + uint8_t first_byte = 0; + uint8_t tlen = 0; + bool success = false; + + while(i < len) { + first_byte = buff[i]; + + success = emv_parse_tag(buff, len, &tag, &tlen, &i); + if(!success) return success; + + if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse + FURI_LOG_T(TAG, "Constructed TLV %x", tag); + if(!emv_decode_response_tlv(&buff[i], tlen, app)) { + FURI_LOG_T(TAG, "Failed to decode response for %x", tag); + // return false; + } else { + success = true; + } + } else { + emv_decode_tlv_tag(&buff[i], tag, tlen, app); + } + i += tlen; + } + return success; +} + +EmvError emv_poller_select_ppse(EmvPoller* instance) { + EmvError error = EmvErrorNone; + + const uint8_t emv_select_ppse_cmd[] = { + 0x00, 0xA4, // SELECT ppse + 0x04, 0x00, // P1:By name, P2: empty + 0x0e, // Lc: Data length + 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string: + 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE) + 0x00 // Le + }; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_copy_bytes(instance->tx_buffer, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd)); + do { + FURI_LOG_D(TAG, "Send select PPSE"); + + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed select PPSE"); + error = emv_process_error(iso14443_4a_error); + break; + } + + emv_trace(instance, "Select PPSE answer:"); + + const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); + + if(!emv_decode_response_tlv( + buff, + bit_buffer_get_size_bytes(instance->rx_buffer), + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_E(TAG, "Failed to parse application"); + } + } while(false); + + return error; +} + +EmvError emv_poller_select_application(EmvPoller* instance) { + EmvError error = EmvErrorNone; + + const uint8_t emv_select_header[] = { + 0x00, + 0xA4, // SELECT application + 0x04, + 0x00 // P1:By name, P2:First or only occurence + }; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Copy header + bit_buffer_copy_bytes(instance->tx_buffer, emv_select_header, sizeof(emv_select_header)); + + // Copy AID + bit_buffer_append_byte(instance->tx_buffer, instance->data->emv_application.aid_len); + bit_buffer_append_bytes( + instance->tx_buffer, + instance->data->emv_application.aid, + instance->data->emv_application.aid_len); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + do { + FURI_LOG_D(TAG, "Start application"); + + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + emv_trace(instance, "Start application answer:"); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); + error = emv_process_error(iso14443_4a_error); + break; + } + + const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); + + if(!emv_decode_response_tlv( + buff, + bit_buffer_get_size_bytes(instance->rx_buffer), + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_E(TAG, "Failed to parse application"); + break; + } + + } while(false); + + return error; +} + +EmvError emv_poller_get_processing_options(EmvPoller* instance) { + EmvError error = EmvErrorNone; + + const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00}; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + // Copy header + bit_buffer_copy_bytes(instance->tx_buffer, emv_gpo_header, sizeof(emv_gpo_header)); + + // Prepare and copy pdol parameters + APDU pdol_data = {0, {0}}; + emv_prepare_pdol(&pdol_data, &instance->data->emv_application.pdol); + + bit_buffer_append_byte(instance->tx_buffer, 0x02 + pdol_data.size); + bit_buffer_append_byte(instance->tx_buffer, 0x83); + bit_buffer_append_byte(instance->tx_buffer, pdol_data.size); + + bit_buffer_append_bytes(instance->tx_buffer, pdol_data.data, pdol_data.size); + bit_buffer_append_byte(instance->tx_buffer, 0x00); + + do { + FURI_LOG_D(TAG, "Get proccessing options"); + + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + emv_trace(instance, "Get processing options answer:"); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed to get processing options, error %u", iso14443_4a_error); + error = emv_process_error(iso14443_4a_error); + break; + } + + const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); + + if(!emv_decode_response_tlv( + buff, + bit_buffer_get_size_bytes(instance->rx_buffer), + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_E(TAG, "Failed to parse processing options"); + } + } while(false); + + return error; +} + +EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t record_num) { + EmvError error = EmvErrorNone; + FuriString* text = furi_string_alloc(); + + uint8_t sfi_param = (sfi << 3) | (1 << 2); + uint8_t emv_sfi_header[] = { + 0x00, + 0xB2, // READ RECORD + record_num, // P1:record_number + sfi_param, // P2:SFI + 0x00 // Le + }; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_copy_bytes(instance->tx_buffer, emv_sfi_header, sizeof(emv_sfi_header)); + + do { + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + furi_string_printf(text, "SFI 0x%X record %d:", sfi, record_num); + emv_trace(instance, furi_string_get_cstr(text)); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed to read SFI 0x%X record %d", sfi, record_num); + error = emv_process_error(iso14443_4a_error); + break; + } + } while(false); + + furi_string_free(text); + + return error; +} + +EmvError emv_poller_read_afl(EmvPoller* instance) { + EmvError error = EmvErrorNone; + + APDU* afl = &instance->data->emv_application.afl; + + if(afl->size == 0) { + return false; + } + + FURI_LOG_D(TAG, "Search PAN in SFI"); + + // Iterate through all files + for(size_t i = 0; i < instance->data->emv_application.afl.size; i += 4) { + uint8_t sfi = afl->data[i] >> 3; + uint8_t record_start = afl->data[i + 1]; + uint8_t record_end = afl->data[i + 2]; + // Iterate through all records in file + for(uint8_t record = record_start; record <= record_end; ++record) { + error = emv_poller_read_sfi_record(instance, sfi, record); + if(error != EmvErrorNone) break; + + if(!emv_decode_response_tlv( + bit_buffer_get_data(instance->rx_buffer), + bit_buffer_get_size_bytes(instance->rx_buffer), + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record); + } + + // Some READ RECORD returns 1 byte response 0x12/0x13 (IDK WTF), + // then poller return Timeout to all subsequent requests. + // TODO: remove below lines when it was fixed + if(instance->data->emv_application.pan_len != 0) + return EmvErrorNone; // Card number fetched + } + } + + return error; +} + +static EmvError emv_poller_req_get_data(EmvPoller* instance, uint16_t tag) { + EmvError error = EmvErrorNone; + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte(instance->tx_buffer, EMV_REQ_GET_DATA >> 8); + bit_buffer_append_byte(instance->tx_buffer, EMV_REQ_GET_DATA & 0xFF); + bit_buffer_append_byte(instance->tx_buffer, tag >> 8); + bit_buffer_append_byte(instance->tx_buffer, tag & 0xFF); + bit_buffer_append_byte(instance->tx_buffer, 0x00); //Length + + do { + FURI_LOG_D(TAG, "Get data for tag 0x%x", tag); + + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext( + instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); + + emv_trace(instance, "Get log data answer:"); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + FURI_LOG_E(TAG, "Failed to get data, error %u", iso14443_4a_error); + error = emv_process_error(iso14443_4a_error); + break; + } + + const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer); + + if(!emv_decode_response_tlv( + buff, + bit_buffer_get_size_bytes(instance->rx_buffer), + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_E(TAG, "Failed to parse get data"); + } + } while(false); + + return error; +} + +EmvError emv_poller_get_pin_try_counter(EmvPoller* instance) { + return emv_poller_req_get_data(instance, EMV_TAG_PIN_TRY_COUNTER); +} + +EmvError emv_poller_get_last_online_atc(EmvPoller* instance) { + return emv_poller_req_get_data(instance, EMV_TAG_LAST_ONLINE_ATC); +} + +static EmvError emv_poller_get_log_format(EmvPoller* instance) { + return emv_poller_req_get_data(instance, EMV_TAG_LOG_FMT); +} + +EmvError emv_poller_read_log_entry(EmvPoller* instance) { + EmvError error = EmvErrorProtocol; + + if(!instance->data->emv_application.log_sfi) return error; + uint8_t records = instance->data->emv_application.log_records; + if(records == 0) { + return error; + } + + instance->data->emv_application.saving_trans_list = true; + error = emv_poller_get_log_format(instance); + if(error != EmvErrorNone) return error; + + FURI_LOG_D(TAG, "Read Transaction logs"); + + uint8_t sfi = instance->data->emv_application.log_sfi; + uint8_t record_start = 1; + uint8_t record_end = records; + // Iterate through all records in file + for(uint8_t record = record_start; record <= record_end; ++record) { + error = emv_poller_read_sfi_record(instance, sfi, record); + if(error != EmvErrorNone) break; + if(!emv_decode_tl( + bit_buffer_get_data(instance->rx_buffer), + bit_buffer_get_size_bytes(instance->rx_buffer), + instance->data->emv_application.log_fmt, + instance->data->emv_application.log_fmt_len, + &instance->data->emv_application)) { + error = EmvErrorProtocol; + FURI_LOG_T(TAG, "Failed to parse log SFI 0x%X record %d", sfi, record); + break; + } + + instance->data->emv_application.active_tr++; + furi_check( + instance->data->emv_application.active_tr < + COUNT_OF(instance->data->emv_application.trans)); + } + + instance->data->emv_application.saving_trans_list = false; + return error; +} diff --git a/lib/nfc/protocols/emv/emv_poller_i.h b/lib/nfc/protocols/emv/emv_poller_i.h new file mode 100644 index 0000000000..7043657475 --- /dev/null +++ b/lib/nfc/protocols/emv/emv_poller_i.h @@ -0,0 +1,51 @@ +#pragma once + +#include "emv_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + EmvPollerStateIdle, + EmvPollerStateSelectPPSE, + EmvPollerStateSelectApplication, + EmvPollerStateGetProcessingOptions, + EmvPollerStateReadFiles, + EmvPollerStateReadExtra, + EmvPollerStateReadFailed, + EmvPollerStateReadSuccess, + + EmvPollerStateNum, +} EmvPollerState; + +typedef enum { + EmvPollerSessionStateIdle, + EmvPollerSessionStateActive, + EmvPollerSessionStateStopRequest, +} EmvPollerSessionState; + +struct EmvPoller { + Iso14443_4aPoller* iso14443_4a_poller; + EmvPollerSessionState session_state; + EmvPollerState state; + EmvError error; + EmvData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + EmvPollerEventData emv_event_data; + EmvPollerEvent emv_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +EmvError emv_process_error(Iso14443_4aError error); + +const EmvData* emv_poller_get_data(EmvPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a.c index 9c2a530d53..bfa2e71c64 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.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h index df212152de..05a7bd577c 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a.h @@ -13,6 +13,7 @@ typedef enum { Iso14443_4aErrorNotPresent, Iso14443_4aErrorProtocol, Iso14443_4aErrorTimeout, + Iso14443_4aErrorSendExtra, } Iso14443_4aError; typedef enum { diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index fef565e514..019beb2be9 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -56,6 +56,11 @@ Iso14443_4aError iso14443_4a_poller_send_block( const BitBuffer* tx_buffer, BitBuffer* rx_buffer); +Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + /** * @brief Send HALT command to the card. * diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index 938e4e715f..2065b81a28 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -81,3 +81,50 @@ Iso14443_4aError iso14443_4a_poller_send_block( return error; } + +Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_assert(instance); + + uint8_t retry = 5; + bit_buffer_reset(instance->tx_buffer); + iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + + Iso14443_4aError error = Iso14443_4aErrorNone; + + do { + bit_buffer_reset(instance->rx_buffer); + Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + iso14443_4a_get_fwt_fc_max(instance->data)); + + if(iso14443_3a_error != Iso14443_3aErrorNone) { + FURI_LOG_RAW_T("RAW RX(%d):", bit_buffer_get_size_bytes(instance->rx_buffer)); + for(size_t x = 0; x < bit_buffer_get_size_bytes(instance->rx_buffer); x++) { + FURI_LOG_RAW_T("%02X ", bit_buffer_get_byte(instance->rx_buffer, x)); + } + FURI_LOG_RAW_T("\r\n"); + + error = iso14443_4a_process_error(iso14443_3a_error); + break; + + } else { + error = iso14443_4_layer_decode_block_pwt_ext( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); + if(error == Iso14443_4aErrorSendExtra) { + if(--retry == 0) break; + // Send response for Control message + if(bit_buffer_get_size_bytes(rx_buffer)) + bit_buffer_copy(instance->tx_buffer, rx_buffer); + continue; + } + break; + } + } while(true); + + return error; +} \ No newline at end of file diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.c b/lib/nfc/protocols/iso15693_3/iso15693_3.c index 3203cbad00..472edfa959 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.c @@ -328,7 +328,12 @@ bool iso15693_3_is_block_locked(const Iso15693_3Data* data, uint8_t block_index) furi_assert(data); furi_assert(block_index < data->system_info.block_count); - return *(const uint8_t*)simple_array_cget(data->block_security, block_index); + // TODO: make proper fix for this, old format had no Block Security Status in file + if(simple_array_get_count(data->block_security) != 0) { + return *(const uint8_t*)simple_array_cget(data->block_security, block_index); + } else { + return false; + } } uint8_t iso15693_3_get_manufacturer_id(const Iso15693_3Data* data) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index dbc32a1b51..d846bba69b 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 8e65eca5a5..646803e75b 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/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 870bcafd9e..e09523f23c 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -42,5 +43,6 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, + [NfcProtocolEmv] = &nfc_device_emv, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 2a6167e9cb..ecfe98c10a 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -20,4 +20,5 @@ const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, [NfcProtocolFelica] = &nfc_listener_felica, + [NfcProtocolEmv] = NULL, }; diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index 7553c74de1..e79c96d98a 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolMfClassic] = &mf_classic_poller, [NfcProtocolMfDesfire] = &mf_desfire_poller, [NfcProtocolSlix] = &nfc_poller_slix, - /* Add new pollers here */ [NfcProtocolSt25tb] = &nfc_poller_st25tb, + [NfcProtocolEmv] = &emv_poller, + /* Add new pollers here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 2ea9b39820..252f86de28 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -12,17 +12,19 @@ * ``` * **************************** Protocol tree structure *************************** * - * (Start) - * | - * +------------------------+-----------+---------+------------+ - * | | | | | - * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB - * | | | - * +---------------+-------------+ ISO14443-4B SLIX - * | | | - * ISO14443-4A Mf Ultralight Mf Classic - * | - * Mf Desfire + * (Start) + * | + * +------------------------+-----------+---------+------------+ + * | | | | | + * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB + * | | | + * +---------------+-------------+ ISO14443-4B SLIX + * | | | + * ISO14443-4A Mf Ultralight Mf Classic + * | + * +-----+-----+ + * | | + * Mf Desfire EMV * ``` * * When implementing a new protocol, its place in the tree must be determined first. @@ -61,6 +63,7 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { /** List of ISO14443-4A child protocols. */ static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { NfcProtocolMfDesfire, + NfcProtocolEmv, }; /** List of ISO115693-3 child protocols. */ @@ -146,6 +149,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, + [NfcProtocolEmv] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index ee63453335..39e8045fee 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -72,19 +72,19 @@ * * ### 2.2 File structure explanation * - * | Filename | Explanation | - * |:------------------------------|:------------| - * | protocol_name.h | Main protocol data structure and associated functions declarations. It is recommended to keep the former as opaque pointer. | - * | protocol_name.c | Implementations of functions declared in `protocol_name.h`. | - * | protocol_name_device_defs.h | Declarations for use by the NfcDevice library. See nfc_device_base_i.h for more info. | - * | protocol_name_poller.h | Protocol-specific poller and associated functions declarations. | - * | protocol_name_poller.c | Implementation of functions declared in `protocol_name_poller.h`. | - * | protocol_name_poller_defs.h | Declarations for use by the NfcPoller library. See nfc_poller_base.h for more info. | - * | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. | - * | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. | + * | Filename | Explanation | + * |:------------------------------|:--------------------------------------------------------------------------------------------------------------------------------| + * | protocol_name.h | Main protocol data structure and associated functions declarations. It is recommended to keep the former as opaque pointer. | + * | protocol_name.c | Implementations of functions declared in `protocol_name.h`. | + * | protocol_name_device_defs.h | Declarations for use by the NfcDevice library. See nfc_device_base_i.h for more info. | + * | protocol_name_poller.h | Protocol-specific poller and associated functions declarations. | + * | protocol_name_poller.c | Implementation of functions declared in `protocol_name_poller.h`. | + * | protocol_name_poller_defs.h | Declarations for use by the NfcPoller library. See nfc_poller_base.h for more info. | + * | protocol_name_listener.h | Protocol-specific listener and associated functions declarations. Optional, needed for emulation support. | + * | protocol_name_listener.c | Implementation of functions declared in `protocol_name_listener.h`. Optional, needed for emulation support. | * | protocol_name_listener_defs.h | Declarations for use by the NfcListener library. See nfc_listener_base.h for more info. Optional, needed for emulation support. | - * | protocol_name_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional.| - * | protocol_name_sync.c | Synchronous API implementation. Optional. | + * | protocol_name_sync.h | Synchronous API declarations. (See below for sync API explanation). Optional. | + * | protocol_name_sync.c | Synchronous API implementation. Optional. | * * ## 3 Implement the code * @@ -187,6 +187,7 @@ typedef enum { NfcProtocolMfDesfire, NfcProtocolSlix, NfcProtocolSt25tb, + NfcProtocolEmv, /* Add new protocols here */ NfcProtocolNum, /**< Special value representing the number of available protocols. */ diff --git a/lib/signal_reader/signal_reader.c b/lib/signal_reader/signal_reader.c index 7c4d0bae7e..1c08d29f45 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/blocks/custom_btn_i.h b/lib/subghz/blocks/custom_btn_i.h index 33ea6be9f1..aafcc82da8 100644 --- a/lib/subghz/blocks/custom_btn_i.h +++ b/lib/subghz/blocks/custom_btn_i.h @@ -5,6 +5,7 @@ #define PROG_MODE_OFF (0U) #define PROG_MODE_KEELOQ_BFT (1U) #define PROG_MODE_KEELOQ_APRIMATIC (2U) +#define PROG_MODE_KEELOQ_DEA_MIO (3U) typedef uint8_t ProgMode; diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index 5fdc7f45c8..e76bb2822b 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -62,6 +62,8 @@ void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; instance->decoder.decode_count_bit++; + if(instance->decoder.decode_count_bit < 62) return; + uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFFFF; //can be multiple, since flipper can't read it well.. if(preamble == 0b0011111111111110 || preamble == 0b0111111111111110 || @@ -76,8 +78,12 @@ void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { if(channel == 0x2 || channel == 0x4 || channel == 0xA) { // 2GIG brand crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8050, 0); - } else { // channel == 0x8 + } else if(channel == 0x8) { crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8005, 0); + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + return; } uint16_t crc = instance->decoder.decode_data & 0xFFFF; if(crc == crc_calc) { @@ -86,16 +92,19 @@ void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { instance->generic.data_count_bit = instance->decoder .decode_count_bit; //maybe set it to 64, and hack the first 2 bits to 1! will see if replay needs it - instance->generic.serial = (instance->decoder.decode_data >> 24) & 0xFFFFF; - instance->generic.btn = (instance->decoder.decode_data >> 16) & - 0xFF; //not exactly button, but can contain btn data too. if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context); instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; return; } + } else if(instance->decoder.decode_count_bit >= 64) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + return; } } @@ -166,6 +175,11 @@ void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* out furi_assert(context); SubGhzProtocolDecoderHoneywell* instance = context; + // Parse here and not in decode to avoid visual glitches when loading from file + instance->generic.serial = (instance->generic.data >> 24) & 0xFFFFF; + instance->generic.btn = (instance->generic.data >> 16) & + 0xFF; //not exactly button, but can contain btn data too. + uint8_t channel = (instance->generic.data >> 44) & 0xF; uint8_t contact = (instance->generic.btn & 0x80) >> 7; uint8_t tamper = (instance->generic.btn & 0x40) >> 6; diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 2a92e9db5c..0f20cc5298 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -159,6 +159,13 @@ static bool subghz_protocol_keeloq_gen_data( } else if(prog_mode == PROG_MODE_KEELOQ_APRIMATIC) { prog_mode = PROG_MODE_OFF; } + } else if(strcmp(instance->manufacture_name, "Dea_Mio") == 0) { + // Dea_Mio programming mode on / off conditions + if(btn == 0xF) { + prog_mode = PROG_MODE_KEELOQ_DEA_MIO; + } else if(prog_mode == PROG_MODE_KEELOQ_DEA_MIO) { + prog_mode = PROG_MODE_OFF; + } } subghz_custom_btn_set_prog_mode(prog_mode); @@ -168,6 +175,9 @@ static bool subghz_protocol_keeloq_gen_data( } else if(prog_mode == PROG_MODE_KEELOQ_APRIMATIC) { // If we using Aprimatic programming mode we will trasmit some strange looking hop value, why? cuz manufacturer did it this way :) hop = 0x1A2B3C4D; + } else if(prog_mode == PROG_MODE_KEELOQ_DEA_MIO) { + // If we using DEA Mio programming mode we will trasmit only FIX value with button code 0xF, hop is zero + hop = 0x00000000; } if(counter_up && prog_mode == PROG_MODE_OFF) { // Counter increment conditions @@ -223,17 +233,18 @@ static bool subghz_protocol_keeloq_gen_data( (strcmp(instance->manufacture_name, "DTM_Neo") == 0) || (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) || - (strcmp(instance->manufacture_name, "Came_Space") == 0)) { + (strcmp(instance->manufacture_name, "Came_Space") == 0) || + (strcmp(instance->manufacture_name, "Genius_Bravo") == 0) || + (strcmp(instance->manufacture_name, "GSN") == 0)) { // DTM Neo, Came_Space uses 12bit serial -> simple learning - // FAAC_RC,XT , Mutanco_Mutancode 12bit serial -> normal learning + // FAAC_RC,XT , Mutanco_Mutancode, Genius_Bravo, GSN 12bit serial -> normal learning decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; } else if( (strcmp(instance->manufacture_name, "NICE_Smilo") == 0) || (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0) || - (strcmp(instance->manufacture_name, "JCM_Tech") == 0) || - (strcmp(instance->manufacture_name, "Normstahl") == 0)) { - // Nice Smilo, MHouse, JCM, Normstahl -> 8bit serial - simple learning + (strcmp(instance->manufacture_name, "JCM_Tech") == 0)) { + // Nice Smilo, MHouse, JCM -> 8bit serial - simple learning decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; } else if(strcmp(instance->manufacture_name, "Beninca") == 0) { @@ -242,6 +253,14 @@ static bool subghz_protocol_keeloq_gen_data( } else if(strcmp(instance->manufacture_name, "Centurion") == 0) { decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt; // Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning + } else if(strcmp(instance->manufacture_name, "Dea_Mio") == 0) { + uint8_t first_disc_num = (instance->generic.serial >> 8) & 0xF; + uint8_t result_disc = (0xC + (first_disc_num % 4)); + uint32_t dea_serial = (instance->generic.serial & 0xFF) | + (((uint32_t)result_disc) << 8); + decrypt = btn << 28 | (dea_serial & 0xFFF) << 16 | instance->generic.cnt; + // Dea_Mio -> modified serial in hop, uses last 3 digits modifying first one (example - 419 -> C19) + // (see formula in result_disc var) - simple learning } // Old type selector fixage for compatibilitiy with old signal files uint8_t kl_type_en = instance->keystore->kl_type; @@ -310,7 +329,7 @@ static bool subghz_protocol_keeloq_gen_data( } } } - if(hop) { + if(hop || (prog_mode == PROG_MODE_KEELOQ_DEA_MIO) || (prog_mode == PROG_MODE_KEELOQ_BFT)) { // If we have hop - we will save it to generic data var that will be used later in transmission uint64_t yek = (uint64_t)fix << 32 | hop; instance->generic.data = @@ -399,11 +418,14 @@ static bool instance->manufacture_name = "BFT"; } else if(prog_mode == PROG_MODE_KEELOQ_APRIMATIC) { instance->manufacture_name = "Aprimatic"; + } else if(prog_mode == PROG_MODE_KEELOQ_DEA_MIO) { + instance->manufacture_name = "Dea_Mio"; } - // Custom button (programming mode button) for BFT and Aprimatic + // Custom button (programming mode button) for BFT, Aprimatic, Dea_Mio uint8_t klq_last_custom_btn = 0xA; if((strcmp(instance->manufacture_name, "BFT") == 0) || - (strcmp(instance->manufacture_name, "Aprimatic") == 0)) { + (strcmp(instance->manufacture_name, "Aprimatic") == 0) || + (strcmp(instance->manufacture_name, "Dea_Mio") == 0)) { klq_last_custom_btn = 0xF; } @@ -976,7 +998,7 @@ static void subghz_protocol_keeloq_check_remote_controller( uint32_t key_hop = key & 0x00000000ffffffff; static uint16_t temp_counter = 0; // Be careful with prog_mode - // If we are in BFT / Aprimatic programming mode we will set previous remembered counter and skip mf keys check + // If we are in BFT / Aprimatic / Dea_Mio programming mode we will set previous remembered counter and skip mf keys check ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); if(prog_mode == PROG_MODE_OFF) { if(keystore->mfname == 0x0) { @@ -1033,6 +1055,11 @@ static void subghz_protocol_keeloq_check_remote_controller( *manufacture_name = "Aprimatic"; keystore->mfname = *manufacture_name; instance->cnt = temp_counter; + } else if(prog_mode == PROG_MODE_KEELOQ_DEA_MIO) { + // When we are in prog mode we should fix mfname and apply temp counter + *manufacture_name = "Dea_Mio"; + keystore->mfname = *manufacture_name; + instance->cnt = temp_counter; } else { // Counter protection furi_crash("Unsupported Prog Mode"); diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 5d7118092a..ade840a365 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_stopping; - 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) { @@ -214,7 +202,6 @@ SubGhzFileEncoderWorker* subghz_file_encoder_worker_alloc() { instance->str_data = furi_string_alloc(); instance->file_path = furi_string_alloc(); - instance->level = false; instance->worker_stopping = true; return instance; diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index 732fdfedfe..541622b88d 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -44,15 +44,23 @@ const char* const name_generator_right[] = { "stuff", }; -void name_generator_make_auto(char* name, size_t max_name_size, const char* prefix) { +void name_generator_make_auto_datetime( + char* name, + size_t max_name_size, + const char* prefix, + FuriHalRtcDateTime* custom_time) { if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDetailedFilename)) { - name_generator_make_detailed(name, max_name_size, prefix); + name_generator_make_detailed_datetime(name, max_name_size, prefix, custom_time); } else { - name_generator_make_random(name, max_name_size); + name_generator_make_random_prefixed(name, max_name_size, prefix); } } -void name_generator_make_random(char* name, size_t max_name_size) { +void name_generator_make_auto(char* name, size_t max_name_size, const char* prefix) { + name_generator_make_auto_datetime(name, max_name_size, prefix, NULL); +} + +void name_generator_make_random_prefixed(char* name, size_t max_name_size, const char* prefix) { furi_assert(name); furi_assert(max_name_size); @@ -62,30 +70,52 @@ void name_generator_make_random(char* name, size_t max_name_size) { snprintf( name, max_name_size, - "%s_%s", + "%s%s%s_%s", + prefix ? prefix : "", + prefix ? "_" : "", name_generator_left[name_generator_left_i], name_generator_right[name_generator_right_i]); // Set first symbol to upper case - name[0] = name[0] - 0x20; + if(islower((int)name[0])) name[0] = name[0] - 0x20; } -void name_generator_make_detailed(char* name, size_t max_name_size, const char* prefix) { +void name_generator_make_random(char* name, size_t max_name_size) { + name_generator_make_random_prefixed(name, max_name_size, NULL); +} + +void name_generator_make_detailed_datetime( + char* name, + size_t max_name_size, + const char* prefix, + FuriHalRtcDateTime* custom_time) { furi_assert(name); furi_assert(max_name_size); furi_assert(prefix); FuriHalRtcDateTime dateTime; - furi_hal_rtc_get_datetime(&dateTime); + if(custom_time) { + dateTime = *custom_time; + } else { + furi_hal_rtc_get_datetime(&dateTime); + } snprintf( name, max_name_size, - "%s-%.4d_%.2d_%.2d-%.2d_%.2d", - prefix, + "%s-%.4d_%.2d_%.2d-%.2d_%.2d_%.2d", + prefix ? prefix : "S", dateTime.year, dateTime.month, dateTime.day, dateTime.hour, - dateTime.minute); + dateTime.minute, + dateTime.second); + + // Set first symbol to upper case + if(islower((int)name[0])) name[0] = name[0] - 0x20; +} + +void name_generator_make_detailed(char* name, size_t max_name_size, const char* prefix) { + name_generator_make_detailed_datetime(name, max_name_size, prefix, NULL); } diff --git a/lib/toolbox/name_generator.h b/lib/toolbox/name_generator.h index bc17d54cd5..ac9cdf6f51 100644 --- a/lib/toolbox/name_generator.h +++ b/lib/toolbox/name_generator.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -14,13 +15,20 @@ extern "C" { * @param[in] prefix The prefix of the name */ void name_generator_make_auto(char* name, size_t max_name_size, const char* prefix); +void name_generator_make_auto_datetime( + char* name, + size_t max_name_size, + const char* prefix, + FuriHalRtcDateTime* custom_time); /** Generates random name * * @param name buffer to write random name * @param max_name_size length of given buffer + * @param[in] prefix The prefix of the name */ void name_generator_make_random(char* name, size_t max_name_size); +void name_generator_make_random_prefixed(char* name, size_t max_name_size, const char* prefix); /** Generates detailed name * @@ -29,6 +37,11 @@ void name_generator_make_random(char* name, size_t max_name_size); * @param[in] prefix The prefix of the name */ void name_generator_make_detailed(char* name, size_t max_name_size, const char* prefix); +void name_generator_make_detailed_datetime( + char* name, + size_t max_name_size, + const char* prefix, + FuriHalRtcDateTime* custom_time); #ifdef __cplusplus } diff --git a/lib/toolbox/value_index.c b/lib/toolbox/value_index.c index 5ec0fb9628..c17b0ae794 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 5aa768e3d1..bcd3024acd 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 1099f0c330..711c1b440b 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/ufbt/SConstruct b/scripts/ufbt/SConstruct index 2fc170ad95..8df1ae110a 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 922a9091b6..f957ee98bb 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 ead935b08b..9daefb4308 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 3269bab575..a5639743f9 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 33cd3f035e..ce5210f5fe 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 4b3f4bda56..65c749e07b 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 ab20e2f7de..4873b385c6 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 52aabcbea5..ffb664a3e0 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,10 +1,11 @@ entry,status,name,type,params -Version,+,50.1,, +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*, @@ -1345,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" @@ -2085,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" @@ -2417,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*" diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 5f4e6165dc..957d9d6733 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/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0ce105b056..677137937a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,11 +1,12 @@ entry,status,name,type,params -Version,+,50.1,, +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,, @@ -120,6 +121,8 @@ Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/nfc_listener.h,, Header,+,lib/nfc/nfc_poller.h,, Header,+,lib/nfc/nfc_scanner.h,, +Header,+,lib/nfc/protocols/emv/emv.h,, +Header,+,lib/nfc/protocols/emv/emv_poller.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, @@ -223,7 +226,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,, @@ -236,11 +238,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,, @@ -261,7 +266,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,, @@ -877,6 +881,26 @@ Function,+,elf_symbolname_hash,uint32_t,const char* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* +Function,+,emv_alloc,EmvData*, +Function,+,emv_copy,void,"EmvData*, const EmvData*" +Function,+,emv_free,void,EmvData* +Function,+,emv_get_base_data,Iso14443_4aData*,const EmvData* +Function,+,emv_get_device_name,const char*,"const EmvData*, NfcDeviceNameType" +Function,+,emv_get_uid,const uint8_t*,"const EmvData*, size_t*" +Function,+,emv_is_equal,_Bool,"const EmvData*, const EmvData*" +Function,+,emv_load,_Bool,"EmvData*, FlipperFormat*, uint32_t" +Function,+,emv_poller_get_last_online_atc,EmvError,EmvPoller* +Function,+,emv_poller_get_pin_try_counter,EmvError,EmvPoller* +Function,+,emv_poller_get_processing_options,EmvError,EmvPoller* +Function,+,emv_poller_read_afl,EmvError,EmvPoller* +Function,+,emv_poller_read_log_entry,EmvError,EmvPoller* +Function,+,emv_poller_read_sfi_record,EmvError,"EmvPoller*, uint8_t, uint8_t" +Function,+,emv_poller_select_application,EmvError,EmvPoller* +Function,+,emv_poller_select_ppse,EmvError,EmvPoller* +Function,+,emv_reset,void,EmvData* +Function,+,emv_save,_Bool,"const EmvData*, FlipperFormat*" +Function,+,emv_set_uid,_Bool,"EmvData*, const uint8_t*, size_t" +Function,+,emv_verify,_Bool,"EmvData*, const FuriString*" Function,-,erand48,double,unsigned short[3] Function,-,erf,double,double Function,-,erfc,double,double @@ -891,6 +915,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" @@ -1174,14 +1201,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 @@ -1293,7 +1312,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, @@ -1434,6 +1453,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 @@ -1452,10 +1473,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* @@ -1465,6 +1489,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, @@ -1523,13 +1573,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*, @@ -1579,15 +1622,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" @@ -1950,6 +1995,7 @@ Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_block_pwt_ext,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" Function,+,iso14443_4a_reset,void,Iso14443_4aData* Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" @@ -2048,6 +2094,7 @@ Function,+,lfrfid_worker_read_start,void,"LFRFIDWorker*, LFRFIDWorkerReadType, L Function,+,lfrfid_worker_start_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_stop,void,LFRFIDWorker* Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* +Function,+,lfrfid_worker_write_and_set_pass_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" @@ -2456,8 +2503,11 @@ Function,-,music_worker_set_volume,void,"MusicWorker*, float" Function,-,music_worker_start,void,MusicWorker* Function,-,music_worker_stop,void,MusicWorker* Function,+,name_generator_make_auto,void,"char*, size_t, const char*" +Function,+,name_generator_make_auto_datetime,void,"char*, size_t, const char*, FuriHalRtcDateTime*" Function,+,name_generator_make_detailed,void,"char*, size_t, const char*" +Function,+,name_generator_make_detailed_datetime,void,"char*, size_t, const char*, FuriHalRtcDateTime*" Function,+,name_generator_make_random,void,"char*, size_t" +Function,+,name_generator_make_random_prefixed,void,"char*, size_t, const char*" Function,-,nan,double,const char* Function,-,nanf,float,const char* Function,-,nanl,long double,const char* @@ -2709,7 +2759,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" @@ -3206,6 +3256,7 @@ Function,+,submenu_set_orientation,void,"Submenu*, ViewOrientation" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* Function,+,t5577_write,void,LFRFIDT5577* +Function,+,t5577_write_with_mask,void,"LFRFIDT5577*, uint8_t, _Bool, uint32_t" Function,+,t5577_write_with_pass,void,"LFRFIDT5577*, uint32_t" Function,-,tan,double,double Function,-,tanf,float,float @@ -3270,10 +3321,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*" @@ -3619,6 +3670,7 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, +Variable,-,nfc_device_emv,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, diff --git a/targets/f7/ble_glue/tl_dbg_conf.h b/targets/f7/ble_glue/tl_dbg_conf.h index daaa9d82ba..240cd5f2fa 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 691729ccf9..9054f4df57 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 0b113d2dac..0000000000 --- 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 ce31a66b33..0000000000 --- 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 9c0d84c550..03a16b70f7 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -411,7 +411,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) { @@ -441,7 +444,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 c508dac72b..a9cd4e7aa6 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 8a280ff8d6..03d7850f94 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 ea835b95fb..85f2d2e45d 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 9e3a70da73..f03aea75f7 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 cb8065bedb..88aad6858e 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 98b23466c2..0a5023131f 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 0000000000..1296ee6202 --- /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 0000000000..975406670f --- /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 0000000000..37454823bc --- /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 0000000000..463f431815 --- /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 0000000000..9f10102e10 --- /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 0000000000..9528e35eb0 --- /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 20771a29a1..7c4af4411f 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; @@ -106,6 +104,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); @@ -185,8 +187,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]) { @@ -214,8 +216,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]); @@ -294,6 +296,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); } @@ -301,12 +304,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); } @@ -314,6 +321,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; } @@ -401,10 +410,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; @@ -435,6 +441,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, @@ -576,73 +583,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; } } } @@ -654,38 +708,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); @@ -702,11 +737,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}; @@ -718,9 +753,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); @@ -730,14 +766,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}; @@ -745,29 +779,18 @@ 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; @@ -776,8 +799,8 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* // furi_hal_subghz_debug_gpio_buff[0] = 0; // furi_hal_subghz_debug_gpio_buff[1] = 0; - 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); @@ -789,33 +812,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); @@ -824,16 +855,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 136a2af410..b901e85ea4 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.h +++ b/targets/f7/furi_hal/furi_hal_subghz.h @@ -18,10 +18,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 209c6be6a2..0000000000 --- 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 3ba4dc4837..0000000000 --- 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, configures 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/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h index e6fd9eb1cc..4f8aad6bd6 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 5edda6ba19..ebe0fe6149 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 */