diff --git a/.ci_files/anims_ofw.txt b/.ci_files/anims_ofw.txt new file mode 100644 index 000000000..a6c7ca694 --- /dev/null +++ b/.ci_files/anims_ofw.txt @@ -0,0 +1,128 @@ +Filetype: Flipper Animation Manifest +Version: 1 + +Name: L1_Waves_128x50 +Min butthurt: 0 +Max butthurt: 5 +Min level: 1 +Max level: 3 +Weight: 3 + +Name: L1_Laptop_128x51 +Min butthurt: 0 +Max butthurt: 7 +Min level: 1 +Max level: 1 +Weight: 3 + +Name: L1_Sleep_128x64 +Min butthurt: 0 +Max butthurt: 10 +Min level: 1 +Max level: 3 +Weight: 3 + +Name: L1_Recording_128x51 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 1 +Weight: 3 + +Name: L1_Furippa1_128x64 +Min butthurt: 0 +Max butthurt: 6 +Min level: 1 +Max level: 1 +Weight: 3 + +Name: L2_Furippa2_128x64 +Min butthurt: 0 +Max butthurt: 6 +Min level: 2 +Max level: 2 +Weight: 3 + +Name: L3_Furippa3_128x64 +Min butthurt: 0 +Max butthurt: 6 +Min level: 3 +Max level: 3 +Weight: 3 + +Name: L1_Read_books_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 1 +Weight: 3 + +Name: L2_Hacking_pc_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 2 +Max level: 2 +Weight: 3 + +Name: L1_Cry_128x64 +Min butthurt: 8 +Max butthurt: 13 +Min level: 1 +Max level: 3 +Weight: 3 + +Name: L1_Boxing_128x64 +Min butthurt: 10 +Max butthurt: 13 +Min level: 1 +Max level: 3 +Weight: 3 + +Name: L1_Mad_fist_128x64 +Min butthurt: 9 +Max butthurt: 13 +Min level: 1 +Max level: 3 +Weight: 3 + +Name: L1_Mods_128x64 +Min butthurt: 0 +Max butthurt: 9 +Min level: 1 +Max level: 3 +Weight: 5 + +Name: L1_Painting_128x64 +Min butthurt: 0 +Max butthurt: 7 +Min level: 1 +Max level: 3 +Weight: 4 + +Name: L3_Hijack_radio_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 3 +Max level: 3 +Weight: 3 + +Name: L3_Lab_research_128x54 +Min butthurt: 0 +Max butthurt: 10 +Min level: 3 +Max level: 3 +Weight: 3 + +Name: L2_Soldering_128x64 +Min butthurt: 0 +Max butthurt: 10 +Min level: 2 +Max level: 2 +Weight: 3 + +Name: L1_Leaving_sad_128x64 +Min butthurt: 14 +Max butthurt: 14 +Min level: 1 +Max level: 3 +Weight: 3 diff --git a/.drone.yml b/.drone.yml index 76e742b74..02d7e4662 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ steps: - git submodule foreach git config --local gc.auto 0 - git log -1 --format='%H' - - name: "Build default fw" + - name: "Build default FW" image: hfdj/fztools pull: never commands: @@ -28,15 +28,39 @@ steps: FBT_TOOLS_CUSTOM_LINK: from_secret: fbt_link + - name: "Build no anims FW" + image: hfdj/fztools + pull: never + commands: + - rm -f assets/dolphin/external/manifest.txt + - cp .ci_files/anims_ofw.txt assets/dolphin/external/manifest.txt + - export DIST_SUFFIX=${DRONE_TAG} + - export WORKFLOW_BRANCH_OR_TAG=dev-cfw + - ./fbt COMPACT=1 DEBUG=0 updater_package + - mkdir artifacts-ofw-anims + - mv dist/f7-C/* artifacts-ofw-anims/ + - ls -laS artifacts-ofw-anims + - ls -laS artifacts-ofw-anims/f7-update-${DRONE_TAG} + - echo '' >> CHANGELOG.md + - echo '### [Version without custom animations - Install via Web Updater](https://lab.flipper.net/?url=https://unleashedflip.com/builds/flipper-z-f7-update-noanims-'${DRONE_TAG}'.tgz&channel=dev-cfw&version='${DRONE_TAG}')' >> CHANGELOG.md + environment: + FBT_TOOLS_CUSTOM_LINK: + from_secret: fbt_link + - name: "Bundle self-update packages" image: kramos/alpine-zip commands: + - mv artifacts-ofw-anims/flipper-z-f7-update-${DRONE_TAG}.tgz artifacts-ofw-anims/flipper-z-f7-update-noanims-${DRONE_TAG}.tgz + - cp artifacts-ofw-anims/flipper-z-f7-update-noanims-${DRONE_TAG}.tgz . - cp artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz . + - zip -r artifacts-ofw-anims/flipper-z-f7-update-noanims-${DRONE_TAG}.zip artifacts-ofw-anims/f7-update-${DRONE_TAG} - zip -r artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip artifacts-default/f7-update-${DRONE_TAG} + - rm -rf artifacts-ofw-anims/f7-update-${DRONE_TAG} - rm -rf artifacts-default/f7-update-${DRONE_TAG} + - ls -laS artifacts-ofw-anims - ls -laS artifacts-default - - name: "Upload to updates server" + - name: "Upload default to updates srv" image: appleboy/drone-scp settings: host: @@ -51,6 +75,21 @@ steps: from_secret: dep_target source: flipper-z-f7-update-${DRONE_TAG}.tgz + - name: "Upload no-anims to updates srv" + image: appleboy/drone-scp + settings: + host: + from_secret: dep_host + username: + from_secret: dep_user + password: + from_secret: dep_passwd + port: + from_secret: dep_port + target: + from_secret: dep_target + source: flipper-z-f7-update-noanims-${DRONE_TAG}.tgz + - name: "Do Github release" image: ddplugins/github-release pull: never @@ -63,6 +102,7 @@ steps: files: - artifacts-default/*.tgz - artifacts-default/*.zip + - artifacts-ofw-anims/*.tgz title: ${DRONE_TAG} note: CHANGELOG.md checksum: @@ -90,6 +130,8 @@ steps: [-How to install firmware-](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md) + [-Version without custom animations - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/builds/flipper-z-f7-update-noanims-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG}) + [-Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/builds/flipper-z-f7-update-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG})" document: @@ -111,6 +153,12 @@ steps: [[Github]](https://github.com/DarkFlippers/unleashed-firmware/releases/tag/${DRONE_TAG}) + [-How to install firmware-](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md) + + + [-Version without custom animations - Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/builds/flipper-z-f7-update-noanims-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG}) + + [-Install via Web Updater-](https://lab.flipper.net/?url=https://unleashedflip.com/builds/flipper-z-f7-update-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG})" trigger: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8705629bc..6f1f24b90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,49 @@ ### New changes -* OFW: **NFC magic cards support (gen1a) (ability to write UID)** -* Plugins -> PR: FlappyBird - draw bird via icon animation (by @an4tur0r | PR #149) -* Plugins: DHT Temp montior - Fix DHT22 timeout bug and other fixes by @quen0n -* Infrared: Universal remote assets update (by @Amec0e) -* OFW: SubGhz: fix incorrect response in rpc mode. Code cleanup -* OFW: Storage: tree timestamps -* OFW: Dolphin: add L1_Mods_128x64 animation -* OFW: Run Bad USB immediately after connection +* API now 99% compatible with official firmware, that means all apps built on OFW can be used on unleashed! +* Also extra apps pack was updated, download latest by using link below +* Archive: Show loading popup on delete +* Docs -> PR: Fix link to "TOTP (Authenticator) config description" (by @pbek | PR #157) +* Reorder main menu - Applications now first item, clock moved 2 items up +* API: Add `value_index` to API symbols +* API: Furi Region Mocks, fix protocol dict funcs was disabled in API +* New animation L3_FlipperMustache_128x64 by @Svaarich +* Fix FlipperCity animation by @Svaarich +* CI/CD: Builds without custom animations now included in releases +* SubGHz: Fix magellan display issue +* SubGHz: Fix wrong error message in history +* SubGHz: Add frequencies 434.075, 434.390 +* SubGHz: Frequency analyzer: Add counter, GUI fixes, allow Ok button - When signal is present (when frequency window shows black background) +* SubGHz: Frequency analyzer: move -+ in freq analyzer, swap up & down button +* SubGHz Remote: Cleanup code in unirf, fix issue #153 +* Plugins: Remove `srand` calls +* Plugins: Fix DHT Monitor icon +* Plugins: RFID Fuzzer - Fix random crashes and improve stability +* Plugins: RFID Fuzzer - allow holding left right to change delay faster (hold TD button to add +10 or -10 to time delay) +* Plugins: Morse code cleanup text by pressing back +* Plugins: TOTP Update - "BadUSB" type key into pc mode [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator) +* Plugins: Update i2c Tools [(by NaejEL)](https://github.com/NaejEL/flipperzero-i2ctools) +* Plugins -> PR: Barcode generator: refactoring, ux improvements, implement EAN-8. (by @msvsergey | PR #154) +* Plugins -> PR: Fix HC-SR04 plugin naming (by @krolchonok | PR #161) +* Plugins: Added BH1750 - Lightmeter [(by oleksiikutuzov)](https://github.com/oleksiikutuzov/flipperzero-lightmeter) +* Plugins: Added iButton Fuzzer [(by xMasterX)](https://github.com/xMasterX/ibutton-fuzzer) +* OFW: BadUSB and Archive fixes +* OFW: iButton: Fix header "Saved!" message stays on other screens + proper popups reset +* OFW: Bug fixes and improvements: Furi, Input, CLI +* OFW: SubGhz: properly handle storage loss +* OFW: NFC - Force card types in extra actions +* OFW: (docs): bad path for furi core +* OFW: RPC: increase stack size, fix stack overflow +* OFW: fbt: 'target' field for apps; lib debugging support +* OFW: NFC: fix crash on MFC read +* OFW: Furi: show thread allocation balance for child threads +* OFW: Add Acurite 609TXC protocol to weather station +* OFW: DAP-Link: show error if usb is locked +* OFW: fbt: compile_db fixes +* OFW: Infrared: add Kaseikyo IR protocol +* OFW: WS: fix show negative temperature +* OFW: fbt: fix for launch_app +* OFW: Code cleanup: srand, PVS warnings +* OFW: fbt: fixes for ufbt pt3 #### [🎲 Download latest extra apps pack](https://download-directory.github.io/?url=https://github.com/xMasterX/unleashed-extra-pack/tree/main/apps) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 18c0e2a60..ea7548750 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -179,7 +179,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { if(favorites) { browser->callback(ArchiveBrowserEventEnterFavMove, browser->context); //} else if((archive_is_known_app(selected->type)) && (selected->is_app == false)) { - } else if(selected->is_app == false) { + } else { // Added ability to rename files and folders archive_show_file_menu(browser, false); scene_manager_set_scene_state( diff --git a/applications/main/archive/scenes/archive_scene_delete.c b/applications/main/archive/scenes/archive_scene_delete.c index 6c7a90cb4..c2f2ddad5 100644 --- a/applications/main/archive/scenes/archive_scene_delete.c +++ b/applications/main/archive/scenes/archive_scene_delete.c @@ -48,11 +48,16 @@ bool archive_scene_delete_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeRight) { + // Show loading popup on delete + view_dispatcher_switch_to_view(app->view_dispatcher, ArchiveViewStack); + archive_show_loading_popup(app, true); + if(selected->is_app) { archive_app_delete_file(browser, name); } else { archive_delete_file(browser, "%s", name); } + archive_show_loading_popup(app, false); archive_show_file_menu(browser, false); return scene_manager_previous_scene(app->scene_manager); } else if(event.event == GuiButtonTypeLeft) { diff --git a/applications/main/archive/scenes/archive_scene_rename.c b/applications/main/archive/scenes/archive_scene_rename.c index 85eeeadf2..346383162 100644 --- a/applications/main/archive/scenes/archive_scene_rename.c +++ b/applications/main/archive/scenes/archive_scene_rename.c @@ -66,9 +66,6 @@ bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { FuriString* path_dst; path_dst = furi_string_alloc(); - path_extract_dirname(path_src, path_dst); - furi_string_cat_printf( - path_dst, "/%s%s", archive->text_store, archive->file_extension); if(file->type == ArchiveFileTypeFolder) { // Rename folder/dir diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index ddd00a7e4..e8d3acfd7 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -123,7 +123,7 @@ void subghz_history_clear_tmp_dir(SubGhzHistory* instance) { } // Stage 2 - create dir if necessary - res = !storage_simply_mkdir(instance->storage, SUBGHZ_HISTORY_TMP_DIR); + res = storage_simply_mkdir(instance->storage, SUBGHZ_HISTORY_TMP_DIR); if(!res) { FURI_LOG_E(TAG, "Cannot process temp dir!"); } diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index f43e88dcf..4cef24e1d 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -23,9 +23,9 @@ static const uint32_t subghz_frequency_list[] = { 300000000, 302757000, 303875000, 304250000, 307000000, 307500000, 307800000, 309000000, 310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000, 315000000, 318000000, 330000000, 345000000, 348000000, 387000000, 390000000, 418000000, 433075000, - 433220000, 433420000, 433657070, 433889000, 433920000, 434176948, 434420000, 434775000, - 438900000, 464000000, 779000000, 868350000, 868400000, 868800000, 868950000, 906400000, - 915000000, 925000000, 928000000}; + 433220000, 433420000, 433657070, 433889000, 433920000, 434075000, 434176948, 434390000, + 434420000, 434775000, 438900000, 464000000, 779000000, 868350000, 868400000, 868800000, + 868950000, 906400000, 915000000, 925000000, 928000000}; typedef enum { SubGhzFrequencyAnalyzerStatusIDLE, diff --git a/applications/plugins/dht_temp_sensor/icon.png b/applications/plugins/dht_temp_sensor/icon.png index 1730432e7..0e87c26c2 100644 Binary files a/applications/plugins/dht_temp_sensor/icon.png and b/applications/plugins/dht_temp_sensor/icon.png differ diff --git a/applications/plugins/flipfrid/flipfrid.h b/applications/plugins/flipfrid/flipfrid.h index 4e3e7a37b..8ce2cca79 100644 --- a/applications/plugins/flipfrid/flipfrid.h +++ b/applications/plugins/flipfrid/flipfrid.h @@ -81,6 +81,7 @@ typedef struct { LFRFIDWorker* worker; ProtocolDict* dict; ProtocolId protocol; + bool workr_rund; uint8_t time_between_cards; diff --git a/applications/plugins/flipfrid/scene/flipfrid_scene_run_attack.c b/applications/plugins/flipfrid/scene/flipfrid_scene_run_attack.c index 6aac68cc3..623a11407 100644 --- a/applications/plugins/flipfrid/scene/flipfrid_scene_run_attack.c +++ b/applications/plugins/flipfrid/scene/flipfrid_scene_run_attack.c @@ -94,8 +94,11 @@ void flipfrid_scene_run_attack_on_enter(FlipFridState* context) { } void flipfrid_scene_run_attack_on_exit(FlipFridState* context) { - lfrfid_worker_stop(context->worker); - lfrfid_worker_stop_thread(context->worker); + if(context->workr_rund) { + lfrfid_worker_stop(context->worker); + lfrfid_worker_stop_thread(context->worker); + context->workr_rund = false; + } lfrfid_worker_free(context->worker); protocol_dict_free(context->dict); notification_message(context->notify, &sequence_blink_stop); @@ -109,9 +112,13 @@ void flipfrid_scene_run_attack_on_tick(FlipFridState* context) { context->worker = lfrfid_worker_alloc(context->dict); lfrfid_worker_start_thread(context->worker); lfrfid_worker_emulate_start(context->worker, context->protocol); + context->workr_rund = true; } else if(0 == counter) { - lfrfid_worker_stop(context->worker); - lfrfid_worker_stop_thread(context->worker); + if(context->workr_rund) { + lfrfid_worker_stop(context->worker); + lfrfid_worker_stop_thread(context->worker); + context->workr_rund = false; + } switch(context->attack) { case FlipFridAttackDefaultValues: if(context->proto == EM4100) { @@ -517,7 +524,7 @@ void flipfrid_scene_run_attack_on_event(FlipFridEvent event, FlipFridState* cont break; case InputKeyRight: if(!context->is_attacking) { - if(context->time_between_cards < 60) { + if(context->time_between_cards < 70) { context->time_between_cards++; } } @@ -552,6 +559,26 @@ void flipfrid_scene_run_attack_on_event(FlipFridEvent event, FlipFridState* cont break; } } + if(event.input_type == InputTypeLong) { + switch(event.key) { + case InputKeyLeft: + if(!context->is_attacking) { + if(context->time_between_cards > 0) { + context->time_between_cards -= 10; + } + } + break; + case InputKeyRight: + if(!context->is_attacking) { + if(context->time_between_cards < 70) { + context->time_between_cards += 10; + } + } + break; + default: + break; + } + } } } diff --git a/applications/plugins/flipper_i2ctools/views/main_view.h b/applications/plugins/flipper_i2ctools/views/main_view.h index 57b2be2e8..3a9211529 100644 --- a/applications/plugins/flipper_i2ctools/views/main_view.h +++ b/applications/plugins/flipper_i2ctools/views/main_view.h @@ -2,7 +2,7 @@ #include #include #include -#define APP_NAME "I2C_Tools" +#define APP_NAME "I2C Tools" #define SCAN_MENU_TEXT "Scan" #define SCAN_MENU_X 75 diff --git a/applications/plugins/ibtn_fuzzer/LICENSE.md b/applications/plugins/ibtn_fuzzer/LICENSE.md new file mode 100644 index 000000000..ba3b84456 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/LICENSE.md @@ -0,0 +1,8 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * @xMasterX and @G4N4P4T1(made original version) wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/application.fam b/applications/plugins/ibtn_fuzzer/application.fam new file mode 100644 index 000000000..b27f47ba9 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/application.fam @@ -0,0 +1,13 @@ +App( + appid="iBtn_Fuzzer", + name="iButton Fuzzer", + apptype=FlipperAppType.EXTERNAL, + entry_point="ibtnfuzzer_start", + cdefines=["APP_IBTN_FUZZ"], + requires=["gui", "storage", "dialogs", "input", "notification"], + stack_size=1 * 1024, + order=15, + fap_icon="ibutt_10px.png", + fap_category="Tools", + fap_icon_assets="images", +) diff --git a/applications/plugins/ibtn_fuzzer/ibtnfuzzer.c b/applications/plugins/ibtn_fuzzer/ibtnfuzzer.c new file mode 100644 index 000000000..f02da3b82 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/ibtnfuzzer.c @@ -0,0 +1,268 @@ +#include "ibtnfuzzer.h" + +#include "scene/ibtnfuzzer_scene_entrypoint.h" +#include "scene/ibtnfuzzer_scene_load_file.h" +#include "scene/ibtnfuzzer_scene_select_field.h" +#include "scene/ibtnfuzzer_scene_run_attack.h" +#include "scene/ibtnfuzzer_scene_load_custom_uids.h" + +#define IBTNFUZZER_APP_FOLDER "/ext/ibtnfuzzer" + +static void ibtnfuzzer_draw_callback(Canvas* const canvas, void* ctx) { + iBtnFuzzerState* ibtnfuzzer_state = (iBtnFuzzerState*)acquire_mutex((ValueMutex*)ctx, 100); + + if(ibtnfuzzer_state == NULL) { + return; + } + + // Draw correct Canvas + switch(ibtnfuzzer_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + ibtnfuzzer_scene_entrypoint_on_draw(canvas, ibtnfuzzer_state); + break; + case SceneSelectFile: + ibtnfuzzer_scene_load_file_on_draw(canvas, ibtnfuzzer_state); + break; + case SceneSelectField: + ibtnfuzzer_scene_select_field_on_draw(canvas, ibtnfuzzer_state); + break; + case SceneAttack: + ibtnfuzzer_scene_run_attack_on_draw(canvas, ibtnfuzzer_state); + break; + case SceneLoadCustomUids: + ibtnfuzzer_scene_load_custom_uids_on_draw(canvas, ibtnfuzzer_state); + break; + } + + release_mutex((ValueMutex*)ctx, ibtnfuzzer_state); +} + +void ibtnfuzzer_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + iBtnFuzzerEvent event = { + .evt_type = EventTypeKey, .key = input_event->key, .input_type = input_event->type}; + furi_message_queue_put(event_queue, &event, 25); +} + +static void ibtnfuzzer_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + iBtnFuzzerEvent event = { + .evt_type = EventTypeTick, .key = InputKeyUp, .input_type = InputTypeRelease}; + furi_message_queue_put(event_queue, &event, 25); +} + +iBtnFuzzerState* ibtnfuzzer_alloc() { + iBtnFuzzerState* ibtnfuzzer = malloc(sizeof(iBtnFuzzerState)); + ibtnfuzzer->notification_msg = furi_string_alloc(); + ibtnfuzzer->attack_name = furi_string_alloc(); + ibtnfuzzer->proto_name = furi_string_alloc(); + ibtnfuzzer->data_str = furi_string_alloc(); + + ibtnfuzzer->previous_scene = NoneScene; + ibtnfuzzer->current_scene = SceneEntryPoint; + ibtnfuzzer->is_running = true; + ibtnfuzzer->is_attacking = false; + ibtnfuzzer->key_index = 0; + ibtnfuzzer->menu_index = 0; + ibtnfuzzer->menu_proto_index = 0; + + ibtnfuzzer->attack = iBtnFuzzerAttackDefaultValues; + ibtnfuzzer->notify = furi_record_open(RECORD_NOTIFICATION); + + ibtnfuzzer->data[0] = 0x00; + ibtnfuzzer->data[1] = 0x00; + ibtnfuzzer->data[2] = 0x00; + ibtnfuzzer->data[3] = 0x00; + ibtnfuzzer->data[4] = 0x00; + ibtnfuzzer->data[5] = 0x00; + ibtnfuzzer->data[6] = 0x00; + ibtnfuzzer->data[7] = 0x00; + + ibtnfuzzer->payload[0] = 0x00; + ibtnfuzzer->payload[1] = 0x00; + ibtnfuzzer->payload[2] = 0x00; + ibtnfuzzer->payload[3] = 0x00; + ibtnfuzzer->payload[4] = 0x00; + ibtnfuzzer->payload[5] = 0x00; + ibtnfuzzer->payload[6] = 0x00; + ibtnfuzzer->payload[7] = 0x00; + + //Dialog + ibtnfuzzer->dialogs = furi_record_open(RECORD_DIALOGS); + + return ibtnfuzzer; +} + +void ibtnfuzzer_free(iBtnFuzzerState* ibtnfuzzer) { + //Dialog + furi_record_close(RECORD_DIALOGS); + notification_message(ibtnfuzzer->notify, &sequence_blink_stop); + + // Strings + furi_string_free(ibtnfuzzer->notification_msg); + furi_string_free(ibtnfuzzer->attack_name); + furi_string_free(ibtnfuzzer->proto_name); + furi_string_free(ibtnfuzzer->data_str); + + free(ibtnfuzzer->data); + free(ibtnfuzzer->payload); + + // The rest + free(ibtnfuzzer); +} + +// ENTRYPOINT +int32_t ibtnfuzzer_start(void* p) { + UNUSED(p); + // Input + FURI_LOG_I(TAG, "Initializing input"); + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(iBtnFuzzerEvent)); + iBtnFuzzerState* ibtnfuzzer_state = ibtnfuzzer_alloc(); + ValueMutex ibtnfuzzer_state_mutex; + + // Mutex + FURI_LOG_I(TAG, "Initializing ibtnfuzzer mutex"); + if(!init_mutex(&ibtnfuzzer_state_mutex, ibtnfuzzer_state, sizeof(iBtnFuzzerState))) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_NOTIFICATION); + ibtnfuzzer_free(ibtnfuzzer_state); + return 255; + } + + Storage* storage = furi_record_open(RECORD_STORAGE); + if(!storage_simply_mkdir(storage, IBTNFUZZER_APP_FOLDER)) { + FURI_LOG_E(TAG, "Could not create folder %s", IBTNFUZZER_APP_FOLDER); + } + furi_record_close(RECORD_STORAGE); + + // Configure view port + FURI_LOG_I(TAG, "Initializing viewport"); + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, ibtnfuzzer_draw_callback, &ibtnfuzzer_state_mutex); + view_port_input_callback_set(view_port, ibtnfuzzer_input_callback, event_queue); + + // Configure timer + FURI_LOG_I(TAG, "Initializing timer"); + FuriTimer* timer = + furi_timer_alloc(ibtnfuzzer_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 10); // 10 times per second + + // Register view port in GUI + FURI_LOG_I(TAG, "Initializing gui"); + Gui* gui = (Gui*)furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Init values + iBtnFuzzerEvent event; + while(ibtnfuzzer_state->is_running) { + // Get next event + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25); + if(event_status == FuriStatusOk) { + if(event.evt_type == EventTypeKey) { + //Handle event key + switch(ibtnfuzzer_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + ibtnfuzzer_scene_entrypoint_on_event(event, ibtnfuzzer_state); + break; + case SceneSelectFile: + ibtnfuzzer_scene_load_file_on_event(event, ibtnfuzzer_state); + break; + case SceneSelectField: + ibtnfuzzer_scene_select_field_on_event(event, ibtnfuzzer_state); + break; + case SceneAttack: + ibtnfuzzer_scene_run_attack_on_event(event, ibtnfuzzer_state); + break; + case SceneLoadCustomUids: + ibtnfuzzer_scene_load_custom_uids_on_event(event, ibtnfuzzer_state); + break; + } + + } else if(event.evt_type == EventTypeTick) { + //Handle event tick + if(ibtnfuzzer_state->current_scene != ibtnfuzzer_state->previous_scene) { + // Trigger Exit Scene + switch(ibtnfuzzer_state->previous_scene) { + case SceneEntryPoint: + ibtnfuzzer_scene_entrypoint_on_exit(ibtnfuzzer_state); + break; + case SceneSelectFile: + ibtnfuzzer_scene_load_file_on_exit(ibtnfuzzer_state); + break; + case SceneSelectField: + ibtnfuzzer_scene_select_field_on_exit(ibtnfuzzer_state); + break; + case SceneAttack: + ibtnfuzzer_scene_run_attack_on_exit(ibtnfuzzer_state); + break; + case SceneLoadCustomUids: + ibtnfuzzer_scene_load_custom_uids_on_exit(ibtnfuzzer_state); + break; + case NoneScene: + break; + } + + // Trigger Entry Scene + switch(ibtnfuzzer_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + ibtnfuzzer_scene_entrypoint_on_enter(ibtnfuzzer_state); + break; + case SceneSelectFile: + ibtnfuzzer_scene_load_file_on_enter(ibtnfuzzer_state); + break; + case SceneSelectField: + ibtnfuzzer_scene_select_field_on_enter(ibtnfuzzer_state); + break; + case SceneAttack: + ibtnfuzzer_scene_run_attack_on_enter(ibtnfuzzer_state); + break; + case SceneLoadCustomUids: + ibtnfuzzer_scene_load_custom_uids_on_enter(ibtnfuzzer_state); + break; + } + ibtnfuzzer_state->previous_scene = ibtnfuzzer_state->current_scene; + } + + // Trigger Tick Scene + switch(ibtnfuzzer_state->current_scene) { + case NoneScene: + case SceneEntryPoint: + ibtnfuzzer_scene_entrypoint_on_tick(ibtnfuzzer_state); + break; + case SceneSelectFile: + ibtnfuzzer_scene_load_file_on_tick(ibtnfuzzer_state); + break; + case SceneSelectField: + ibtnfuzzer_scene_select_field_on_tick(ibtnfuzzer_state); + break; + case SceneAttack: + ibtnfuzzer_scene_run_attack_on_tick(ibtnfuzzer_state); + break; + case SceneLoadCustomUids: + ibtnfuzzer_scene_load_custom_uids_on_tick(ibtnfuzzer_state); + break; + } + view_port_update(view_port); + } + } + } + + // Cleanup + furi_timer_stop(timer); + furi_timer_free(timer); + + FURI_LOG_I(TAG, "Cleaning up"); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + ibtnfuzzer_free(ibtnfuzzer_state); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/ibtnfuzzer.h b/applications/plugins/ibtn_fuzzer/ibtnfuzzer.h new file mode 100644 index 000000000..1af5e9ff1 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/ibtnfuzzer.h @@ -0,0 +1,89 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#define TAG "iBtnFuzzer" + +typedef enum { + iBtnFuzzerAttackDefaultValues, + iBtnFuzzerAttackLoadFile, + iBtnFuzzerAttackLoadFileCustomUids, +} iBtnFuzzerAttacks; + +typedef enum { + DS1990, + Metakom, + Cyfral, +} iBtnFuzzerProtos; + +typedef enum { + NoneScene, + SceneEntryPoint, + SceneSelectFile, + SceneSelectField, + SceneAttack, + SceneLoadCustomUids, +} iBtnFuzzerScene; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType evt_type; + InputKey key; + InputType input_type; +} iBtnFuzzerEvent; + +// STRUCTS +typedef struct { + bool is_running; + bool is_attacking; + iBtnFuzzerScene current_scene; + iBtnFuzzerScene previous_scene; + NotificationApp* notify; + u_int8_t menu_index; + u_int8_t menu_proto_index; + + FuriString* data_str; + uint8_t data[8]; + uint8_t payload[8]; + uint8_t attack_step; + iBtnFuzzerAttacks attack; + iBtnFuzzerProtos proto; + FuriString* attack_name; + FuriString* proto_name; + + DialogsApp* dialogs; + FuriString* notification_msg; + uint8_t key_index; + iButtonWorker* worker; + iButtonKey* key; + iButtonKeyType keytype; + bool workr_rund; + bool enter_rerun; + + uint8_t time_between_cards; + + // Used for custom dictionnary + Stream* uids_stream; +} iBtnFuzzerState; \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/ibutt_10px.png b/applications/plugins/ibtn_fuzzer/ibutt_10px.png new file mode 100644 index 000000000..2fdaf123a Binary files /dev/null and b/applications/plugins/ibtn_fuzzer/ibutt_10px.png differ diff --git a/applications/plugins/ibtn_fuzzer/images/ibutt_10px.png b/applications/plugins/ibtn_fuzzer/images/ibutt_10px.png new file mode 100644 index 000000000..2fdaf123a Binary files /dev/null and b/applications/plugins/ibtn_fuzzer/images/ibutt_10px.png differ diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.c b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.c new file mode 100644 index 000000000..a951e0c1f --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.c @@ -0,0 +1,215 @@ +#include "ibtnfuzzer_scene_entrypoint.h" + +FuriString* main_menu_items[3]; +FuriString* main_menu_proto_items[3]; + +void ibtnfuzzer_scene_entrypoint_menu_callback( + iBtnFuzzerState* context, + uint32_t index, + uint32_t proto_index) { + switch(index) { + case iBtnFuzzerAttackDefaultValues: + context->attack = iBtnFuzzerAttackDefaultValues; + context->current_scene = SceneAttack; + furi_string_set(context->attack_name, "Default Values"); + break; + case iBtnFuzzerAttackLoadFile: + context->attack = iBtnFuzzerAttackLoadFile; + context->current_scene = SceneSelectFile; + furi_string_set(context->attack_name, "Load File"); + break; + case iBtnFuzzerAttackLoadFileCustomUids: + context->attack = iBtnFuzzerAttackLoadFileCustomUids; + context->current_scene = SceneLoadCustomUids; + furi_string_set(context->attack_name, "Load Custom UIDs"); + break; + default: + break; + } + + switch(proto_index) { + case DS1990: + context->proto = DS1990; + furi_string_set(context->proto_name, "DS1990"); + break; + case Metakom: + context->proto = Metakom; + furi_string_set(context->proto_name, "Metakom"); + break; + case Cyfral: + context->proto = Cyfral; + furi_string_set(context->proto_name, "Cyfral"); + break; + default: + break; + } +} + +void ibtnfuzzer_scene_entrypoint_on_enter(iBtnFuzzerState* context) { + // Clear the previous payload + context->payload[0] = 0x00; + context->payload[1] = 0x00; + context->payload[2] = 0x00; + context->payload[3] = 0x00; + context->payload[4] = 0x00; + context->payload[5] = 0x00; + context->payload[6] = 0x00; + context->payload[7] = 0x00; + + context->menu_index = 0; + /*for(uint32_t i = 0; i < 4; i++) { + menu_items[i] = furi_string_alloc(); + }*/ + + main_menu_items[0] = furi_string_alloc_set("Default Values"); + main_menu_items[1] = furi_string_alloc_set("Load File"); + main_menu_items[2] = furi_string_alloc_set("Load uids from file"); + + context->menu_proto_index = 0; + /*for(uint32_t i = 0; i < 4; i++) { + menu_proto_items[i] = furi_string_alloc(); + }*/ + + main_menu_proto_items[0] = furi_string_alloc_set("DS1990"); + main_menu_proto_items[1] = furi_string_alloc_set("Metakom"); + main_menu_proto_items[2] = furi_string_alloc_set("Cyfral"); +} + +void ibtnfuzzer_scene_entrypoint_on_exit(iBtnFuzzerState* context) { + context->enter_rerun = false; + + for(uint32_t i = 0; i < 3; i++) { + furi_string_free(main_menu_items[i]); + } + + for(uint32_t i = 0; i < 3; i++) { + furi_string_free(main_menu_proto_items[i]); + } +} + +void ibtnfuzzer_scene_entrypoint_on_tick(iBtnFuzzerState* context) { + UNUSED(context); +} + +void ibtnfuzzer_scene_entrypoint_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + if(context->menu_index < iBtnFuzzerAttackLoadFileCustomUids) { + context->menu_index++; + } + break; + case InputKeyUp: + if(context->menu_index > iBtnFuzzerAttackDefaultValues) { + context->menu_index--; + } + break; + case InputKeyLeft: + if(context->menu_proto_index > DS1990) { + context->menu_proto_index--; + } else if(context->menu_proto_index == DS1990) { + context->menu_proto_index = Cyfral; + } + break; + case InputKeyRight: + if(context->menu_proto_index < Cyfral) { + context->menu_proto_index++; + } else if(context->menu_proto_index == Cyfral) { + context->menu_proto_index = DS1990; + } + break; + case InputKeyOk: + ibtnfuzzer_scene_entrypoint_menu_callback( + context, context->menu_index, context->menu_proto_index); + break; + case InputKeyBack: + context->is_running = false; + break; + default: + break; + } + } + } +} + +void ibtnfuzzer_scene_entrypoint_on_draw(Canvas* canvas, iBtnFuzzerState* context) { + if(!context->enter_rerun) { + ibtnfuzzer_scene_entrypoint_on_enter(context); + context->enter_rerun = true; + } + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + if(main_menu_items[context->menu_index] != NULL) { + if(context->menu_index > iBtnFuzzerAttackDefaultValues) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + 24, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_items[context->menu_index - 1])); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, + 64, + 36, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_items[context->menu_index])); + + if(context->menu_index < iBtnFuzzerAttackLoadFileCustomUids) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + 48, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_items[context->menu_index + 1])); + } + + if(context->menu_proto_index > DS1990) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + -12, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index - 1])); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 27, 4, AlignCenter, AlignTop, "<"); + + canvas_set_font(canvas, FontPrimary); + if(main_menu_proto_items[context->menu_proto_index] != NULL) { + canvas_draw_str_aligned( + canvas, + 64, + 4, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index])); + } + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 101, 4, AlignCenter, AlignTop, ">"); + + if(context->menu_proto_index < Cyfral) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, + 64, + -12, + AlignCenter, + AlignTop, + furi_string_get_cstr(main_menu_proto_items[context->menu_proto_index + 1])); + } + } +} \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.h b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.h new file mode 100644 index 000000000..b77aec369 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_entrypoint.h @@ -0,0 +1,8 @@ +#pragma once +#include "../ibtnfuzzer.h" + +void ibtnfuzzer_scene_entrypoint_on_enter(iBtnFuzzerState* context); +void ibtnfuzzer_scene_entrypoint_on_exit(iBtnFuzzerState* context); +void ibtnfuzzer_scene_entrypoint_on_tick(iBtnFuzzerState* context); +void ibtnfuzzer_scene_entrypoint_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context); +void ibtnfuzzer_scene_entrypoint_on_draw(Canvas* canvas, iBtnFuzzerState* context); \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_custom_uids.c b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_custom_uids.c new file mode 100644 index 000000000..8f1a422b3 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_custom_uids.c @@ -0,0 +1,85 @@ +#include "ibtnfuzzer_scene_load_custom_uids.h" +#include "ibtnfuzzer_scene_run_attack.h" +#include "ibtnfuzzer_scene_entrypoint.h" + +#define IBTNFUZZER_UIDS_EXTENSION ".txt" +#define IBTNFUZZER_APP_PATH_FOLDER "/ext/ibtnfuzzer" + +bool ibtnfuzzer_load_uids(iBtnFuzzerState* context, const char* file_path) { + bool result = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + context->uids_stream = buffered_file_stream_alloc(storage); + result = + buffered_file_stream_open(context->uids_stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING); + // Close if loading fails + if(!result) { + buffered_file_stream_close(context->uids_stream); + return false; + } + return result; +} + +bool ibtnfuzzer_load_custom_uids_from_file(iBtnFuzzerState* context) { + // Input events and views are managed by file_select + FuriString* uid_path; + uid_path = furi_string_alloc(); + furi_string_set(uid_path, IBTNFUZZER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, IBTNFUZZER_UIDS_EXTENSION, &I_ibutt_10px); + browser_options.hide_ext = false; + + bool res = dialog_file_browser_show(context->dialogs, uid_path, uid_path, &browser_options); + + if(res) { + res = ibtnfuzzer_load_uids(context, furi_string_get_cstr(uid_path)); + } + + furi_string_free(uid_path); + + return res; +} + +void ibtnfuzzer_scene_load_custom_uids_on_enter(iBtnFuzzerState* context) { + if(ibtnfuzzer_load_custom_uids_from_file(context)) { + // Force context loading + ibtnfuzzer_scene_run_attack_on_enter(context); + context->current_scene = SceneAttack; + } else { + ibtnfuzzer_scene_entrypoint_on_enter(context); + context->current_scene = SceneEntryPoint; + } +} + +void ibtnfuzzer_scene_load_custom_uids_on_exit(iBtnFuzzerState* context) { + UNUSED(context); +} + +void ibtnfuzzer_scene_load_custom_uids_on_tick(iBtnFuzzerState* context) { + UNUSED(context); +} + +void ibtnfuzzer_scene_load_custom_uids_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + case InputKeyUp: + case InputKeyLeft: + case InputKeyRight: + case InputKeyOk: + case InputKeyBack: + context->current_scene = SceneEntryPoint; + break; + default: + break; + } + } + } +} + +void ibtnfuzzer_scene_load_custom_uids_on_draw(Canvas* canvas, iBtnFuzzerState* context) { + UNUSED(context); + UNUSED(canvas); +} diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_custom_uids.h b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_custom_uids.h new file mode 100644 index 000000000..bb51c7079 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_custom_uids.h @@ -0,0 +1,9 @@ +#pragma once +#include "../ibtnfuzzer.h" + +void ibtnfuzzer_scene_load_custom_uids_on_enter(iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_custom_uids_on_exit(iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_custom_uids_on_tick(iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_custom_uids_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_custom_uids_on_draw(Canvas* canvas, iBtnFuzzerState* context); +bool ibtnfuzzer_load_custom_uids_from_file(iBtnFuzzerState* context); \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_file.c b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_file.c new file mode 100644 index 000000000..99e6db733 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_file.c @@ -0,0 +1,179 @@ +#include "ibtnfuzzer_scene_load_file.h" +#include "ibtnfuzzer_scene_entrypoint.h" + +#define IBUTTON_FUZZER_APP_EXTENSION ".ibtn" +#define IBUTTON_FUZZER_APP_PATH_FOLDER "/ext/ibutton" + +bool ibtnfuzzer_load(iBtnFuzzerState* context, const char* file_path) { + bool result = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + if(!flipper_format_file_open_existing(fff_data_file, file_path)) { + FURI_LOG_E(TAG, "Error open file %s", file_path); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Error open file"); + break; + } + + // FileType + if(!flipper_format_read_string(fff_data_file, "Filetype", temp_str)) { + FURI_LOG_E(TAG, "Missing or incorrect Filetype"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Missing or incorrect Filetypes"); + break; + } else { + FURI_LOG_I(TAG, "Filetype: %s", furi_string_get_cstr(temp_str)); + } + + // Key type + if(!flipper_format_read_string(fff_data_file, "Key type", temp_str)) { + FURI_LOG_E(TAG, "Missing or incorrect Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Missing or incorrect Key type"); + break; + } else { + FURI_LOG_I(TAG, "Key type: %s", furi_string_get_cstr(temp_str)); + + if(context->proto == DS1990) { + if(strcmp(furi_string_get_cstr(temp_str), "Dallas") != 0) { + FURI_LOG_E(TAG, "Unsupported Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Unsupported Key type"); + break; + } + } else if(context->proto == Cyfral) { + if(strcmp(furi_string_get_cstr(temp_str), "Cyfral") != 0) { + FURI_LOG_E(TAG, "Unsupported Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Unsupported Key type"); + break; + } + } else { + if(strcmp(furi_string_get_cstr(temp_str), "Metakom") != 0) { + FURI_LOG_E(TAG, "Unsupported Key type"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Unsupported Key type"); + break; + } + } + } + + // Data + if(!flipper_format_read_string(fff_data_file, "Data", context->data_str)) { + FURI_LOG_E(TAG, "Missing or incorrect Data"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Missing or incorrect Key"); + break; + } else { + FURI_LOG_I(TAG, "Key: %s", furi_string_get_cstr(context->data_str)); + + if(context->proto == DS1990) { + if(furi_string_size(context->data_str) != 23) { + FURI_LOG_E(TAG, "Incorrect Key length"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Incorrect Key length"); + break; + } + } else if(context->proto == Cyfral) { + if(furi_string_size(context->data_str) != 5) { + FURI_LOG_E(TAG, "Incorrect Key length"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Incorrect Key length"); + break; + } + } else { + if(furi_string_size(context->data_str) != 11) { + FURI_LOG_E(TAG, "Incorrect Key length"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Incorrect Key length"); + break; + } + } + + // String to uint8_t + for(uint8_t i = 0; i < 8; i++) { + char temp_str2[3]; + temp_str2[0] = furi_string_get_cstr(context->data_str)[i * 3]; + temp_str2[1] = furi_string_get_cstr(context->data_str)[i * 3 + 1]; + temp_str2[2] = '\0'; + context->data[i] = (uint8_t)strtol(temp_str2, NULL, 16); + } + } + + result = true; + } while(0); + furi_string_free(temp_str); + flipper_format_free(fff_data_file); + if(result) { + FURI_LOG_I(TAG, "Loaded successfully"); + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, "Source loaded."); + } + return result; +} + +void ibtnfuzzer_scene_load_file_on_enter(iBtnFuzzerState* context) { + if(ibtnfuzzer_load_protocol_from_file(context)) { + context->current_scene = SceneSelectField; + } else { + ibtnfuzzer_scene_entrypoint_on_enter(context); + context->current_scene = SceneEntryPoint; + } +} + +void ibtnfuzzer_scene_load_file_on_exit(iBtnFuzzerState* context) { + UNUSED(context); +} + +void ibtnfuzzer_scene_load_file_on_tick(iBtnFuzzerState* context) { + UNUSED(context); +} + +void ibtnfuzzer_scene_load_file_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + case InputKeyUp: + case InputKeyLeft: + case InputKeyRight: + case InputKeyOk: + case InputKeyBack: + context->current_scene = SceneEntryPoint; + break; + default: + break; + } + } + } +} + +void ibtnfuzzer_scene_load_file_on_draw(Canvas* canvas, iBtnFuzzerState* context) { + UNUSED(context); + UNUSED(canvas); +} + +bool ibtnfuzzer_load_protocol_from_file(iBtnFuzzerState* context) { + FuriString* user_file_path; + user_file_path = furi_string_alloc(); + furi_string_set(user_file_path, IBUTTON_FUZZER_APP_PATH_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, IBUTTON_FUZZER_APP_EXTENSION, &I_ibutt_10px); + + // Input events and views are managed by file_select + bool res = dialog_file_browser_show( + context->dialogs, user_file_path, user_file_path, &browser_options); + + if(res) { + res = ibtnfuzzer_load(context, furi_string_get_cstr(user_file_path)); + } + + furi_string_free(user_file_path); + + return res; +} \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_file.h b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_file.h new file mode 100644 index 000000000..34031d08c --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_load_file.h @@ -0,0 +1,9 @@ +#pragma once +#include "../ibtnfuzzer.h" + +void ibtnfuzzer_scene_load_file_on_enter(iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_file_on_exit(iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_file_on_tick(iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_file_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context); +void ibtnfuzzer_scene_load_file_on_draw(Canvas* canvas, iBtnFuzzerState* context); +bool ibtnfuzzer_load_protocol_from_file(iBtnFuzzerState* context); \ No newline at end of file diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.c b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.c new file mode 100644 index 000000000..55942f929 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.c @@ -0,0 +1,490 @@ +#include "ibtnfuzzer_scene_run_attack.h" +#include + +uint8_t counter = 0; + +uint8_t id_list_ds1990[25][8] = { + {0x01, 0xBE, 0x40, 0x11, 0x5A, 0x36, 0x00, 0xE1}, //– код универсального ключа, для Vizit + {0x01, 0xBE, 0x40, 0x11, 0x5A, 0x56, 0x00, 0xBB}, //- проверен работает + {0x01, 0xBE, 0x40, 0x11, 0x00, 0x00, 0x00, 0x77}, //- проверен работает + {0x01, 0xBE, 0x40, 0x11, 0x0A, 0x00, 0x00, 0x1D}, //- проверен работает Визит иногда КЕЙМАНЫ + {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2F}, //- проверен(метаком, цифрал, ВИЗИТ). + {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x9B}, //- проверен Визит, Метакомы, КОНДОР + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14}, //???-Открываает 98% Метаком и некоторые Цифрал + {0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x19, 0xFF}, //???-Отлично работает на старых домофонах + {0x01, 0x6F, 0x2E, 0x88, 0x8A, 0x00, 0x00, 0x4D}, //???-Открывать что-то должен + {0x01, 0x53, 0xD4, 0xFE, 0x00, 0x00, 0x7E, 0x88}, //???-Cyfral, Metakom + {0x01, 0x53, 0xD4, 0xFE, 0x00, 0x00, 0x00, 0x6F}, //???-домофоны Визит (Vizit) - до 99% + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D}, //???-домофоны Cyfral CCD-20 - до 70% + {0x01, 0x00, 0xBE, 0x11, 0xAA, 0x00, 0x00, 0xFB}, //???-домофоны Кейман (KEYMAN) + {0x01, 0x76, 0xB8, 0x2E, 0x0F, 0x00, 0x00, 0x5C}, //???-домофоны Форвард + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF + {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11 + {0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22 + {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33 + {0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44 + {0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55 + {0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66 + {0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77 + {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88 + {0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99 +}; + +uint8_t id_list_metakom[17][4] = { + {0x00, 0x00, 0x00, 0x00}, // Null bytes + {0xFF, 0xFF, 0xFF, 0xFF}, // Only FF + {0x11, 0x11, 0x11, 0x11}, // Only 11 + {0x22, 0x22, 0x22, 0x22}, // Only 22 + {0x33, 0x33, 0x33, 0x33}, // Only 33 + {0x44, 0x44, 0x44, 0x44}, // Only 44 + {0x55, 0x55, 0x55, 0x55}, // Only 55 + {0x66, 0x66, 0x66, 0x66}, // Only 66 + {0x77, 0x77, 0x77, 0x77}, // Only 77 + {0x88, 0x88, 0x88, 0x88}, // Only 88 + {0x99, 0x99, 0x99, 0x99}, // Only 99 + {0x12, 0x34, 0x56, 0x78}, // Incremental UID + {0x9A, 0x78, 0x56, 0x34}, // Decremental UID + {0x04, 0xd0, 0x9b, 0x0d}, // ?? + {0x34, 0x00, 0x29, 0x3d}, // ?? + {0x04, 0xdf, 0x00, 0x00}, // ?? + {0xCA, 0xCA, 0xCA, 0xCA}, // ?? +}; + +uint8_t id_list_cyfral[14][2] = { + {0x00, 0x00}, // Null bytes + {0xFF, 0xFF}, // Only FF + {0x11, 0x11}, // Only 11 + {0x22, 0x22}, // Only 22 + {0x33, 0x33}, // Only 33 + {0x44, 0x44}, // Only 44 + {0x55, 0x55}, // Only 55 + {0x66, 0x66}, // Only 66 + {0x77, 0x77}, // Only 77 + {0x88, 0x88}, // Only 88 + {0x99, 0x99}, // Only 99 + {0x12, 0x34}, // Incremental UID + {0x56, 0x34}, // Decremental UID + {0xCA, 0xCA}, // ?? +}; + +void ibtnfuzzer_scene_run_attack_on_enter(iBtnFuzzerState* context) { + context->time_between_cards = 10; + context->attack_step = 0; + context->key = ibutton_key_alloc(); + context->worker = ibutton_worker_alloc(); + if(context->proto == Metakom) { + context->keytype = iButtonKeyMetakom; + } else if(context->proto == Cyfral) { + context->keytype = iButtonKeyCyfral; + } else { + context->keytype = iButtonKeyDS1990; + } + context->workr_rund = false; +} + +void ibtnfuzzer_scene_run_attack_on_exit(iBtnFuzzerState* context) { + if(context->workr_rund) { + ibutton_worker_stop(context->worker); + ibutton_worker_stop_thread(context->worker); + context->workr_rund = false; + } + ibutton_worker_free(context->worker); + ibutton_key_free(context->key); + notification_message(context->notify, &sequence_blink_stop); +} + +void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context) { + if(context->is_attacking) { + if(1 == counter) { + ibutton_worker_start_thread(context->worker); + ibutton_key_set_type(context->key, context->keytype); + ibutton_key_set_data( + context->key, context->payload, ibutton_key_get_size_by_type(context->keytype)); + ibutton_worker_emulate_start(context->worker, context->key); + context->workr_rund = true; + } else if(0 == counter) { + if(context->workr_rund) { + ibutton_worker_stop(context->worker); + ibutton_worker_stop_thread(context->worker); + context->workr_rund = false; + } + switch(context->attack) { + case iBtnFuzzerAttackDefaultValues: + if(context->proto == DS1990) { + context->payload[0] = id_list_ds1990[context->attack_step][0]; + context->payload[1] = id_list_ds1990[context->attack_step][1]; + context->payload[2] = id_list_ds1990[context->attack_step][2]; + context->payload[3] = id_list_ds1990[context->attack_step][3]; + context->payload[4] = id_list_ds1990[context->attack_step][4]; + context->payload[5] = id_list_ds1990[context->attack_step][5]; + context->payload[6] = id_list_ds1990[context->attack_step][6]; + context->payload[7] = id_list_ds1990[context->attack_step][7]; + + if(context->attack_step == 24) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else if(context->proto == Metakom) { + context->payload[0] = id_list_metakom[context->attack_step][0]; + context->payload[1] = id_list_metakom[context->attack_step][1]; + context->payload[2] = id_list_metakom[context->attack_step][2]; + context->payload[3] = id_list_metakom[context->attack_step][3]; + + if(context->attack_step == 16) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } else { + context->payload[0] = id_list_cyfral[context->attack_step][0]; + context->payload[1] = id_list_cyfral[context->attack_step][1]; + + if(context->attack_step == 13) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } else { + context->attack_step++; + } + break; + } + case iBtnFuzzerAttackLoadFile: + if(context->proto == DS1990) { + context->payload[0] = context->data[0]; + context->payload[1] = context->data[1]; + context->payload[2] = context->data[2]; + context->payload[3] = context->data[3]; + context->payload[4] = context->data[4]; + context->payload[5] = context->data[5]; + context->payload[6] = context->data[6]; + context->payload[7] = context->data[7]; + + context->payload[context->key_index] = context->attack_step; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + break; + } else { + context->attack_step++; + } + break; + } else if(context->proto == Cyfral) { + context->payload[0] = context->data[0]; + context->payload[1] = context->data[1]; + + context->payload[context->key_index] = context->attack_step; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + break; + } else { + context->attack_step++; + } + break; + } else { + context->payload[0] = context->data[0]; + context->payload[1] = context->data[1]; + context->payload[2] = context->data[2]; + context->payload[3] = context->data[3]; + + context->payload[context->key_index] = context->attack_step; + + if(context->attack_step == 255) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + break; + } else { + context->attack_step++; + } + break; + } + + case iBtnFuzzerAttackLoadFileCustomUids: + if(context->proto == DS1990) { + bool end_of_list = false; + while(true) { + furi_string_reset(context->data_str); + if(!stream_read_line(context->uids_stream, context->data_str)) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + stream_rewind(context->uids_stream); + end_of_list = true; + break; + }; + if(furi_string_get_char(context->data_str, 0) == '#') continue; + if(furi_string_size(context->data_str) != 17) break; + break; + } + if(end_of_list) break; + FURI_LOG_D(TAG, furi_string_get_cstr(context->data_str)); + if(furi_string_size(context->data_str) != 17) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_error); + break; + }; + + // string is valid, parse it in context->payload + for(uint8_t i = 0; i < 8; i++) { + char temp_str[3]; + temp_str[0] = furi_string_get_cstr(context->data_str)[i * 2]; + temp_str[1] = furi_string_get_cstr(context->data_str)[i * 2 + 1]; + temp_str[2] = '\0'; + context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16); + } + break; + } else if(context->proto == Cyfral) { + bool end_of_list = false; + while(true) { + furi_string_reset(context->data_str); + if(!stream_read_line(context->uids_stream, context->data_str)) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + stream_rewind(context->uids_stream); + end_of_list = true; + break; + }; + if(furi_string_get_char(context->data_str, 0) == '#') continue; + if(furi_string_size(context->data_str) != 5) break; + break; + } + if(end_of_list) break; + FURI_LOG_D(TAG, furi_string_get_cstr(context->data_str)); + if(furi_string_size(context->data_str) != 5) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_error); + break; + }; + + // string is valid, parse it in context->payload + for(uint8_t i = 0; i < 2; i++) { + char temp_str[3]; + temp_str[0] = furi_string_get_cstr(context->data_str)[i * 2]; + temp_str[1] = furi_string_get_cstr(context->data_str)[i * 2 + 1]; + temp_str[2] = '\0'; + context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16); + } + break; + } else { + bool end_of_list = false; + while(true) { + furi_string_reset(context->data_str); + if(!stream_read_line(context->uids_stream, context->data_str)) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + stream_rewind(context->uids_stream); + end_of_list = true; + break; + }; + if(furi_string_get_char(context->data_str, 0) == '#') continue; + if(furi_string_size(context->data_str) != 9) break; + break; + } + FURI_LOG_D(TAG, furi_string_get_cstr(context->data_str)); + if(end_of_list) break; + if(furi_string_size(context->data_str) != 9) { + context->attack_step = 0; + counter = 0; + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_error); + break; + }; + + // string is valid, parse it in context->payload + for(uint8_t i = 0; i < 4; i++) { + char temp_str[3]; + temp_str[0] = furi_string_get_cstr(context->data_str)[i * 2]; + temp_str[1] = furi_string_get_cstr(context->data_str)[i * 2 + 1]; + temp_str[2] = '\0'; + context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16); + } + break; + } + } + } + + if(counter > context->time_between_cards) { + counter = 0; + } else { + counter++; + } + } +} + +void ibtnfuzzer_scene_run_attack_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + switch(event.key) { + case InputKeyDown: + break; + case InputKeyUp: + break; + case InputKeyLeft: + if(!context->is_attacking) { + if(context->time_between_cards > 0) { + context->time_between_cards--; + } + } + break; + case InputKeyRight: + if(!context->is_attacking) { + if(context->time_between_cards < 80) { + context->time_between_cards++; + } + } + break; + case InputKeyOk: + counter = 0; + if(!context->is_attacking) { + notification_message(context->notify, &sequence_blink_start_blue); + context->is_attacking = true; + } else { + context->is_attacking = false; + notification_message(context->notify, &sequence_blink_stop); + notification_message(context->notify, &sequence_single_vibro); + } + break; + case InputKeyBack: + context->is_attacking = false; + context->attack_step = 0; + counter = 0; + + if(context->attack == iBtnFuzzerAttackLoadFileCustomUids) { + furi_string_reset(context->data_str); + stream_rewind(context->uids_stream); + buffered_file_stream_close(context->uids_stream); + } + + furi_string_reset(context->notification_msg); + notification_message(context->notify, &sequence_blink_stop); + context->current_scene = SceneEntryPoint; + break; + default: + break; + } + } + if(event.input_type == InputTypeLong) { + switch(event.key) { + case InputKeyLeft: + if(!context->is_attacking) { + if(context->time_between_cards > 0) { + context->time_between_cards -= 10; + } + } + break; + case InputKeyRight: + if(!context->is_attacking) { + if(context->time_between_cards < 80) { + context->time_between_cards += 10; + } + } + break; + default: + break; + } + } + } +} + +void ibtnfuzzer_scene_run_attack_on_draw(Canvas* canvas, iBtnFuzzerState* context) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // Frame + //canvas_draw_frame(canvas, 0, 0, 128, 64); + + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 64, 2, AlignCenter, AlignTop, furi_string_get_cstr(context->attack_name)); + + char uid[25]; + char speed[16]; + if(context->proto == Metakom) { + snprintf( + uid, + sizeof(uid), + "%02X:%02X:%02X:%02X", + context->payload[0], + context->payload[1], + context->payload[2], + context->payload[3]); + } else if(context->proto == Cyfral) { + snprintf(uid, sizeof(uid), "%02X:%02X", context->payload[0], context->payload[1]); + } else { + snprintf( + uid, + sizeof(uid), + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + context->payload[0], + context->payload[1], + context->payload[2], + context->payload[3], + context->payload[4], + context->payload[5], + context->payload[6], + context->payload[7]); + } + + canvas_draw_str_aligned(canvas, 64, 38, AlignCenter, AlignTop, uid); + + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned( + canvas, 64, 26, AlignCenter, AlignTop, furi_string_get_cstr(context->proto_name)); + + snprintf(speed, sizeof(speed), "Time delay: %d", context->time_between_cards); + + //canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Speed:"); + canvas_draw_str_aligned(canvas, 64, 14, AlignCenter, AlignTop, speed); + //char start_stop_msg[20]; + if(context->is_attacking) { + elements_button_center(canvas, "Stop"); + //snprintf(start_stop_msg, sizeof(start_stop_msg), " Press OK to stop "); + } else { + elements_button_center(canvas, "Start"); + elements_button_left(canvas, "TD -"); + elements_button_right(canvas, "+ TD"); + } + //canvas_draw_str_aligned(canvas, 64, 44, AlignCenter, AlignTop, start_stop_msg); +} diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.h b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.h new file mode 100644 index 000000000..9e44478f7 --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_run_attack.h @@ -0,0 +1,8 @@ +#pragma once +#include "../ibtnfuzzer.h" + +void ibtnfuzzer_scene_run_attack_on_enter(iBtnFuzzerState* context); +void ibtnfuzzer_scene_run_attack_on_exit(iBtnFuzzerState* context); +void ibtnfuzzer_scene_run_attack_on_tick(iBtnFuzzerState* context); +void ibtnfuzzer_scene_run_attack_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context); +void ibtnfuzzer_scene_run_attack_on_draw(Canvas* canvas, iBtnFuzzerState* context); diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_select_field.c b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_select_field.c new file mode 100644 index 000000000..f3217f65e --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_select_field.c @@ -0,0 +1,160 @@ +#include "ibtnfuzzer_scene_select_field.h" + +void ibtnfuzzer_center_displayed_key(iBtnFuzzerState* context, uint8_t index) { + char key_cstr[25]; + uint8_t key_len = 25; + uint8_t str_index = (index * 3); + int data_len = sizeof(context->data) / sizeof(context->data[0]); + int key_index = 0; + + if(context->proto == DS1990) { + key_len = 25; + } + if(context->proto == Metakom) { + key_len = 13; + } + if(context->proto == Cyfral) { + key_len = 7; + } + + for(uint8_t i = 0; i < data_len; i++) { + if(context->data[i] < 9) { + key_index += + snprintf(&key_cstr[key_index], key_len - key_index, "0%X ", context->data[i]); + } else { + key_index += + snprintf(&key_cstr[key_index], key_len - key_index, "%X ", context->data[i]); + } + } + + char display_menu[17] = { + 'X', 'X', ' ', 'X', 'X', ' ', '<', 'X', 'X', '>', ' ', 'X', 'X', ' ', 'X', 'X', '\0'}; + + if(index > 1) { + display_menu[0] = key_cstr[str_index - 6]; + display_menu[1] = key_cstr[str_index - 5]; + } else { + display_menu[0] = ' '; + display_menu[1] = ' '; + } + + if(index > 0) { + display_menu[3] = key_cstr[str_index - 3]; + display_menu[4] = key_cstr[str_index - 2]; + } else { + display_menu[3] = ' '; + display_menu[4] = ' '; + } + + display_menu[7] = key_cstr[str_index]; + display_menu[8] = key_cstr[str_index + 1]; + + if((str_index + 4) <= (uint8_t)strlen(key_cstr)) { + display_menu[11] = key_cstr[str_index + 3]; + display_menu[12] = key_cstr[str_index + 4]; + } else { + display_menu[11] = ' '; + display_menu[12] = ' '; + } + + if((str_index + 8) <= (uint8_t)strlen(key_cstr)) { + display_menu[14] = key_cstr[str_index + 6]; + display_menu[15] = key_cstr[str_index + 7]; + } else { + display_menu[14] = ' '; + display_menu[15] = ' '; + } + + furi_string_reset(context->notification_msg); + furi_string_set(context->notification_msg, display_menu); +} + +void ibtnfuzzer_scene_select_field_on_enter(iBtnFuzzerState* context) { + furi_string_reset(context->notification_msg); +} + +void ibtnfuzzer_scene_select_field_on_exit(iBtnFuzzerState* context) { + UNUSED(context); +} + +void ibtnfuzzer_scene_select_field_on_tick(iBtnFuzzerState* context) { + UNUSED(context); +} + +void ibtnfuzzer_scene_select_field_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context) { + if(event.evt_type == EventTypeKey) { + if(event.input_type == InputTypeShort) { + const char* key_cstr = furi_string_get_cstr(context->data_str); + int data_len = sizeof(context->data) / sizeof(context->data[0]); + + // don't look, it's ugly but I'm a python dev so... + uint8_t nb_bytes = 0; + for(uint8_t i = 0; i < strlen(key_cstr); i++) { + if(' ' == key_cstr[i]) { + nb_bytes++; + } + } + + switch(event.key) { + case InputKeyDown: + for(uint8_t i = 0; i < data_len; i++) { + if(context->key_index == i) { + context->data[i] = (context->data[i] - 1); + } + } + break; + case InputKeyUp: + for(uint8_t i = 0; i < data_len; i++) { + if(context->key_index == i) { + context->data[i] = (context->data[i] + 1); + } + } + break; + case InputKeyLeft: + if(context->key_index > 0) { + context->key_index = context->key_index - 1; + } + break; + case InputKeyRight: + if(context->key_index < nb_bytes) { + context->key_index = context->key_index + 1; + } + break; + case InputKeyOk: + furi_string_reset(context->notification_msg); + context->current_scene = SceneAttack; + break; + case InputKeyBack: + context->key_index = 0; + furi_string_reset(context->notification_msg); + context->current_scene = SceneSelectFile; + break; + default: + break; + } + FURI_LOG_D(TAG, "Position: %d/%d", context->key_index, nb_bytes); + } + } +} + +void ibtnfuzzer_scene_select_field_on_draw(Canvas* canvas, iBtnFuzzerState* context) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + // Frame + //canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 12, 5, AlignLeft, AlignTop, "Left and right: select byte"); + canvas_draw_str_aligned(canvas, 12, 15, AlignLeft, AlignTop, "Up and down: adjust byte"); + + char msg_index[18]; + canvas_set_font(canvas, FontPrimary); + snprintf(msg_index, sizeof(msg_index), "Field index : %d", context->key_index); + canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignTop, msg_index); + + ibtnfuzzer_center_displayed_key(context, context->key_index); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignTop, furi_string_get_cstr(context->notification_msg)); +} diff --git a/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_select_field.h b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_select_field.h new file mode 100644 index 000000000..b6c684c3b --- /dev/null +++ b/applications/plugins/ibtn_fuzzer/scene/ibtnfuzzer_scene_select_field.h @@ -0,0 +1,9 @@ +#pragma once +#include "../ibtnfuzzer.h" + +void ibtnfuzzer_scene_select_field_on_enter(iBtnFuzzerState* context); +void ibtnfuzzer_scene_select_field_on_exit(iBtnFuzzerState* context); +void ibtnfuzzer_scene_select_field_on_tick(iBtnFuzzerState* context); +void ibtnfuzzer_scene_select_field_on_event(iBtnFuzzerEvent event, iBtnFuzzerState* context); +void ibtnfuzzer_scene_select_field_on_draw(Canvas* canvas, iBtnFuzzerState* context); +void center_displayed_key(iBtnFuzzerState* context, uint8_t index); \ No newline at end of file diff --git a/applications/plugins/lightmeter/LICENSE b/applications/plugins/lightmeter/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/applications/plugins/lightmeter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Oleksii Kutuzov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/plugins/lightmeter/README.md b/applications/plugins/lightmeter/README.md new file mode 100644 index 000000000..d9c071e67 --- /dev/null +++ b/applications/plugins/lightmeter/README.md @@ -0,0 +1,21 @@ +# flipperzero-lightmeter + +[Original link](https://github.com/oleksiikutuzov/flipperzero-lightmeter) + + + + +## Wiring + +``` +VCC -> 3.3V +GND -> GND +SCL -> C0 +SDA -> C1 +``` + +## TODO +- [ ] Save settings to sd card + +## References +App inspired by [lightmeter](https://github.com/vpominchuk/lightmeter) project for Arduino by [vpominchuk](https://github.com/vpominchuk). diff --git a/applications/plugins/lightmeter/application.fam b/applications/plugins/lightmeter/application.fam new file mode 100644 index 000000000..8cd90ee26 --- /dev/null +++ b/applications/plugins/lightmeter/application.fam @@ -0,0 +1,24 @@ +App( + appid="lightmeter", + name="[BH1750] Lightmeter", + apptype=FlipperAppType.EXTERNAL, + entry_point="lightmeter_app", + cdefines=["APP_LIGHTMETER"], + requires=[ + "gui", + ], + stack_size=1 * 1024, + order=90, + fap_icon="lightmeter.png", + fap_category="GPIO", + fap_private_libs=[ + Lib( + name="BH1750", + cincludes=["."], + sources=[ + "BH1750.c", + ], + ), + ], + fap_icon_assets="icons", +) diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c new file mode 100644 index 000000000..2487d5817 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.c @@ -0,0 +1,30 @@ +#include "lightmeter_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const lightmeter_on_enter_handlers[])(void*) = { +#include "lightmeter_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const lightmeter_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "lightmeter_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const lightmeter_on_exit_handlers[])(void* context) = { +#include "lightmeter_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers lightmeter_scene_handlers = { + .on_enter_handlers = lightmeter_on_enter_handlers, + .on_event_handlers = lightmeter_on_event_handlers, + .on_exit_handlers = lightmeter_on_exit_handlers, + .scene_num = LightMeterAppSceneNum, +}; diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h new file mode 100644 index 000000000..9d5931384 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) LightMeterAppScene##id, +typedef enum { +#include "lightmeter_scene_config.h" + LightMeterAppSceneNum, +} LightMeterAppScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers lightmeter_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "lightmeter_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "lightmeter_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "lightmeter_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h new file mode 100644 index 000000000..c72a7713e --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/config/lightmeter_scene_config.h @@ -0,0 +1,4 @@ +ADD_SCENE(lightmeter, main, Main) +ADD_SCENE(lightmeter, config, Config) +ADD_SCENE(lightmeter, help, Help) +ADD_SCENE(lightmeter, about, About) diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c new file mode 100644 index 000000000..1508b4c00 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_about.c @@ -0,0 +1,71 @@ +#include "../../lightmeter.h" + +void lightmeter_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + LightMeterApp* app = context; + + UNUSED(app); + UNUSED(result); + UNUSED(type); + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void lightmeter_scene_about_on_enter(void* context) { + LightMeterApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", LM_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by: %s\n", LM_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", LM_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf( + temp_str, + "Showing suggested camera\nsettings based on ambient\nlight or flash.\n\nInspired by a lightmeter\nproject by vpominchuk\n"); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! Lightmeter \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewAbout); +} + +bool lightmeter_scene_about_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void lightmeter_scene_about_on_exit(void* context) { + LightMeterApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c new file mode 100644 index 000000000..42952562b --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_config.c @@ -0,0 +1,156 @@ +#include "../../lightmeter.h" + +static const char* iso_numbers[] = { + [ISO_6] = "6", + [ISO_12] = "12", + [ISO_25] = "25", + [ISO_50] = "50", + [ISO_100] = "100", + [ISO_200] = "200", + [ISO_400] = "400", + [ISO_800] = "800", + [ISO_1600] = "1600", + [ISO_3200] = "3200", + [ISO_6400] = "6400", + [ISO_12800] = "12800", + [ISO_25600] = "25600", + [ISO_51200] = "51200", + [ISO_102400] = "102400", +}; + +static const char* nd_numbers[] = { + [ND_0] = "0", + [ND_2] = "2", + [ND_4] = "4", + [ND_8] = "8", + [ND_16] = "16", + [ND_32] = "32", + [ND_64] = "64", + [ND_128] = "128", + [ND_256] = "256", + [ND_512] = "512", + [ND_1024] = "1024", + [ND_2048] = "2048", + [ND_4096] = "4096", +}; + +static const char* diffusion_dome[] = { + [WITHOUT_DOME] = "No", + [WITH_DOME] = "Yes", +}; + +enum LightMeterSubmenuIndex { + LightMeterSubmenuIndexISO, + LightMeterSubmenuIndexND, + LightMeterSubmenuIndexDome, +}; + +static void iso_numbers_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, iso_numbers[index]); + + LightMeterConfig* config = app->config; + config->iso = index; + lightmeter_app_set_config(app, config); +} + +static void nd_numbers_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, nd_numbers[index]); + + LightMeterConfig* config = app->config; + config->nd = index; + lightmeter_app_set_config(app, config); +} + +static void dome_presence_cb(VariableItem* item) { + LightMeterApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, diffusion_dome[index]); + + LightMeterConfig* config = app->config; + config->dome = index; + lightmeter_app_set_config(app, config); +} + +static void ok_cb(void* context, uint32_t index) { + LightMeterApp* app = context; + UNUSED(app); + switch(index) { + case 3: + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventHelp); + break; + case 4: + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventAbout); + break; + default: + break; + } +} + +void lightmeter_scene_config_on_enter(void* context) { + LightMeterApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + LightMeterConfig* config = app->config; + + item = + variable_item_list_add(var_item_list, "ISO", COUNT_OF(iso_numbers), iso_numbers_cb, app); + variable_item_set_current_value_index(item, config->iso); + variable_item_set_current_value_text(item, iso_numbers[config->iso]); + + item = variable_item_list_add( + var_item_list, "ND factor", COUNT_OF(nd_numbers), nd_numbers_cb, app); + variable_item_set_current_value_index(item, config->nd); + variable_item_set_current_value_text(item, nd_numbers[config->nd]); + + item = variable_item_list_add( + var_item_list, "Diffusion dome", COUNT_OF(diffusion_dome), dome_presence_cb, app); + variable_item_set_current_value_index(item, config->dome); + variable_item_set_current_value_text(item, diffusion_dome[config->dome]); + + item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); + item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL); + + variable_item_list_set_selected_item( + var_item_list, + scene_manager_get_scene_state(app->scene_manager, LightMeterAppSceneConfig)); + + variable_item_list_set_enter_callback(var_item_list, ok_cb, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewVarItemList); +} + +bool lightmeter_scene_config_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case LightMeterAppCustomEventHelp: + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneHelp); + consumed = true; + break; + case LightMeterAppCustomEventAbout: + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneAbout); + consumed = true; + break; + } + } + return consumed; +} + +void lightmeter_scene_config_on_exit(void* context) { + LightMeterApp* app = context; + variable_item_list_reset(app->var_item_list); + main_view_set_iso(app->main_view, app->config->iso); + main_view_set_nd(app->main_view, app->config->nd); + main_view_set_dome(app->main_view, app->config->dome); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c new file mode 100644 index 000000000..7b6d45864 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_help.c @@ -0,0 +1,32 @@ +#include "../../lightmeter.h" + +void lightmeter_scene_help_on_enter(void* context) { + LightMeterApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf( + temp_str, "App works with BH1750\nambient light sensor\nconnected via I2C interface\n\n"); + furi_string_cat(temp_str, "\e#Pinout:\r\n"); + furi_string_cat( + temp_str, + " SDA: 15 [C1]\r\n" + " SCL: 16 [C0]\r\n"); + + widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewHelp); +} + +bool lightmeter_scene_help_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void lightmeter_scene_help_on_exit(void* context) { + LightMeterApp* app = context; + + widget_reset(app->widget); +} diff --git a/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c new file mode 100644 index 000000000..8ba474461 --- /dev/null +++ b/applications/plugins/lightmeter/gui/scenes/lightmeter_scene_main.c @@ -0,0 +1,43 @@ +#include "../../lightmeter.h" + +static void lightmeter_scene_main_on_left(void* context) { + LightMeterApp* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, LightMeterAppCustomEventConfig); +} + +void lightmeter_scene_main_on_enter(void* context) { + LightMeterApp* app = context; + + lightmeter_main_view_set_left_callback(app->main_view, lightmeter_scene_main_on_left, app); + view_dispatcher_switch_to_view(app->view_dispatcher, LightMeterAppViewMainView); +} + +bool lightmeter_scene_main_on_event(void* context, SceneManagerEvent event) { + LightMeterApp* app = context; + + bool response = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + if(event.event == LightMeterAppCustomEventConfig) { + scene_manager_next_scene(app->scene_manager, LightMeterAppSceneConfig); + response = true; + } + break; + + case SceneManagerEventTypeTick: + lightmeter_app_i2c_callback(app); + response = true; + break; + + default: + break; + } + + return response; +} + +void lightmeter_scene_main_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/lightmeter/gui/views/main_view.c b/applications/plugins/lightmeter/gui/views/main_view.c new file mode 100644 index 000000000..756346fa4 --- /dev/null +++ b/applications/plugins/lightmeter/gui/views/main_view.c @@ -0,0 +1,434 @@ +#include "main_view.h" +#include +#include +#include +#include "../../lightmeter.h" +#include "../../lightmeter_helper.h" + +#define WORKER_TAG "Main View" + +static const int iso_numbers[] = { + [ISO_6] = 6, + [ISO_12] = 12, + [ISO_25] = 25, + [ISO_50] = 50, + [ISO_100] = 100, + [ISO_200] = 200, + [ISO_400] = 400, + [ISO_800] = 800, + [ISO_1600] = 1600, + [ISO_3200] = 3200, + [ISO_6400] = 6400, + [ISO_12800] = 12800, + [ISO_25600] = 25600, + [ISO_51200] = 51200, + [ISO_102400] = 102400, +}; + +static const int nd_numbers[] = { + [ND_0] = 0, + [ND_2] = 2, + [ND_4] = 4, + [ND_8] = 8, + [ND_16] = 16, + [ND_32] = 32, + [ND_64] = 64, + [ND_128] = 128, + [ND_256] = 256, + [ND_512] = 512, + [ND_1024] = 1024, + [ND_2048] = 2048, + [ND_4096] = 4096, +}; + +static const float aperture_numbers[] = { + [AP_1] = 1.0, + [AP_1_4] = 1.4, + [AP_2] = 2.0, + [AP_2_8] = 2.8, + [AP_4] = 4.0, + [AP_5_6] = 5.6, + [AP_8] = 8, + [AP_11] = 11, + [AP_16] = 16, + [AP_22] = 22, + [AP_32] = 32, + [AP_45] = 45, + [AP_64] = 64, + [AP_90] = 90, + [AP_128] = 128, +}; + +static const float speed_numbers[] = { + [SPEED_8000] = 1.0 / 8000, [SPEED_4000] = 1.0 / 4000, [SPEED_2000] = 1.0 / 2000, + [SPEED_1000] = 1.0 / 1000, [SPEED_500] = 1.0 / 500, [SPEED_250] = 1.0 / 250, + [SPEED_125] = 1.0 / 125, [SPEED_60] = 1.0 / 60, [SPEED_30] = 1.0 / 30, + [SPEED_15] = 1.0 / 15, [SPEED_8] = 1.0 / 8, [SPEED_4] = 1.0 / 4, + [SPEED_2] = 1.0 / 2, [SPEED_1S] = 1.0, [SPEED_2S] = 2.0, + [SPEED_4S] = 4.0, [SPEED_8S] = 8.0, [SPEED_15S] = 15.0, + [SPEED_30S] = 30.0, +}; + +struct MainView { + View* view; + LightMeterMainViewButtonCallback cb_left; + void* cb_context; +}; + +void lightmeter_main_view_set_left_callback( + MainView* lightmeter_main_view, + LightMeterMainViewButtonCallback callback, + void* context) { + with_view_model( + lightmeter_main_view->view, + MainViewModel * model, + { + UNUSED(model); + lightmeter_main_view->cb_left = callback; + lightmeter_main_view->cb_context = context; + }, + true); +} + +static void main_view_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + MainViewModel* model = context; + + // FURI_LOG_D("MAIN VIEW", "Drawing"); + + canvas_clear(canvas); + + // top row + draw_top_row(canvas, model); + + // add f, T values + canvas_set_font(canvas, FontBigNumbers); + + // draw f icon and number + canvas_draw_icon(canvas, 15, 17, &I_f_10x14); + draw_aperture(canvas, model); + + // draw T icon and number + canvas_draw_icon(canvas, 15, 34, &I_T_10x14); + draw_speed(canvas, model); + + // draw button + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Config"); + + // draw ND number + draw_nd_number(canvas, model); + + // draw EV number + canvas_set_font(canvas, FontSecondary); + draw_EV_number(canvas, model); + + // draw mode indicator + draw_mode_indicator(canvas, model); +} + +static void main_view_process(MainView* main_view, InputEvent* event) { + with_view_model( + main_view->view, + MainViewModel * model, + { + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->aperture < AP_NUM - 1) model->aperture++; + break; + + case FIXED_SPEED: + if(model->speed < SPEED_NUM - 1) model->speed++; + break; + + default: + break; + } + } else if(event->key == InputKeyDown) { + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->aperture > 0) model->aperture--; + break; + + case FIXED_SPEED: + if(model->speed > 0) model->speed--; + break; + + default: + break; + } + } else if(event->key == InputKeyOk) { + switch(model->current_mode) { + case FIXED_SPEED: + model->current_mode = FIXED_APERTURE; + break; + + case FIXED_APERTURE: + model->current_mode = FIXED_SPEED; + break; + + default: + break; + } + } + } + }, + true); +} + +static bool main_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + MainView* main_view = context; + bool consumed = false; + + if(event->type == InputTypeShort && event->key == InputKeyLeft) { + if(main_view->cb_left) { + main_view->cb_left(main_view->cb_context); + } + consumed = true; + } else if(event->type == InputTypeShort && event->key == InputKeyBack) { + } else { + main_view_process(main_view, event); + consumed = true; + } + + return consumed; +} + +MainView* main_view_alloc() { + MainView* main_view = malloc(sizeof(MainView)); + main_view->view = view_alloc(); + view_set_context(main_view->view, main_view); + view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(MainViewModel)); + view_set_draw_callback(main_view->view, main_view_draw_callback); + view_set_input_callback(main_view->view, main_view_input_callback); + + return main_view; +} + +void main_view_free(MainView* main_view) { + furi_assert(main_view); + view_free(main_view->view); + free(main_view); +} + +View* main_view_get_view(MainView* main_view) { + furi_assert(main_view); + return main_view->view; +} + +void main_view_set_lux(MainView* main_view, float val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->lux = val; }, true); +} + +void main_view_set_EV(MainView* main_view, float val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->EV = val; }, true); +} + +void main_view_set_response(MainView* main_view, bool val) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->response = val; }, true); +} + +void main_view_set_iso(MainView* main_view, int iso) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->iso = iso; }, true); +} + +void main_view_set_nd(MainView* main_view, int nd) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->nd = nd; }, true); +} + +void main_view_set_aperture(MainView* main_view, int aperture) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->aperture = aperture; }, true); +} + +void main_view_set_speed(MainView* main_view, int speed) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->speed = speed; }, true); +} + +void main_view_set_dome(MainView* main_view, bool dome) { + furi_assert(main_view); + with_view_model( + main_view->view, MainViewModel * model, { model->dome = dome; }, true); +} + +bool main_view_get_dome(MainView* main_view) { + furi_assert(main_view); + bool val = false; + with_view_model( + main_view->view, MainViewModel * model, { val = model->dome; }, true); + return val; +} + +void draw_top_row(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + if(!model->response) { + canvas_draw_box(canvas, 0, 0, 128, 12); + canvas_set_color(canvas, ColorWhite); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 24, 10, "No sensor found"); + canvas_set_color(canvas, ColorBlack); + } else { + model->iso_val = iso_numbers[model->iso]; + if(model->nd > 0) model->iso_val /= nd_numbers[model->nd]; + + if(model->lux > 0) { + if(model->current_mode == FIXED_APERTURE) { + model->speed_val = 100 * pow(aperture_numbers[model->aperture], 2) / + (double)model->iso_val / pow(2, model->EV); + } else { + model->aperture_val = sqrt( + pow(2, model->EV) * (double)model->iso_val * + (double)speed_numbers[model->speed] / 100); + } + } + + // TODO when T:30, f/0 instead of f/128 + + canvas_draw_line(canvas, 0, 10, 128, 10); + + canvas_set_font(canvas, FontPrimary); + // metering mode A – ambient, F – flash + canvas_draw_str_aligned(canvas, 1, 1, AlignLeft, AlignTop, "A"); + + snprintf(str, sizeof(str), "ISO: %d", iso_numbers[model->iso]); + canvas_draw_str_aligned(canvas, 19, 1, AlignLeft, AlignTop, str); + + canvas_set_font(canvas, FontSecondary); + snprintf(str, sizeof(str), "lx: %.0f", (double)model->lux); + canvas_draw_str_aligned(canvas, 87, 2, AlignLeft, AlignTop, str); + } +} + +void draw_aperture(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->response) { + if(model->aperture < AP_8) { + snprintf(str, sizeof(str), "/%.1f", (double)aperture_numbers[model->aperture]); + } else { + snprintf(str, sizeof(str), "/%.0f", (double)aperture_numbers[model->aperture]); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 15, AlignLeft, AlignTop, str); + break; + case FIXED_SPEED: + if(model->aperture_val < aperture_numbers[0] || !model->response) { + snprintf(str, sizeof(str), " ---"); + } else if(model->aperture_val < aperture_numbers[AP_8]) { + snprintf(str, sizeof(str), "/%.1f", (double)normalizeAperture(model->aperture_val)); + } else { + snprintf(str, sizeof(str), "/%.0f", (double)normalizeAperture(model->aperture_val)); + } + canvas_draw_str_aligned(canvas, 27, 15, AlignLeft, AlignTop, str); + break; + default: + break; + } +} + +void draw_speed(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[12]; + + switch(model->current_mode) { + case FIXED_APERTURE: + if(model->lux > 0 && model->response) { + if(model->speed_val < 1 && model->speed_val > 0) { + snprintf(str, sizeof(str), ":1/%.0f", 1 / (double)normalizeTime(model->speed_val)); + } else { + snprintf(str, sizeof(str), ":%.0f", (double)normalizeTime(model->speed_val)); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 34, AlignLeft, AlignTop, str); + break; + + case FIXED_SPEED: + if(model->response) { + if(model->speed < SPEED_1S) { + snprintf(str, sizeof(str), ":1/%.0f", 1 / (double)speed_numbers[model->speed]); + } else { + snprintf(str, sizeof(str), ":%.0f", (double)speed_numbers[model->speed]); + } + } else { + snprintf(str, sizeof(str), " ---"); + } + canvas_draw_str_aligned(canvas, 27, 34, AlignLeft, AlignTop, str); + break; + + default: + break; + } +} + +void draw_mode_indicator(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + switch(model->current_mode) { + case FIXED_SPEED: + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 3, 36, AlignLeft, AlignTop, "*"); + break; + + case FIXED_APERTURE: + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 3, 17, AlignLeft, AlignTop, "*"); + break; + + default: + break; + } +} + +void draw_nd_number(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[9]; + + if(model->response) { + snprintf(str, sizeof(str), "ND: %d", nd_numbers[model->nd]); + } else { + snprintf(str, sizeof(str), "ND: ---"); + } + canvas_draw_str_aligned(canvas, 87, 20, AlignLeft, AlignBottom, str); +} + +void draw_EV_number(Canvas* canvas, MainViewModel* context) { + MainViewModel* model = context; + + char str[7]; + + if(model->lux > 0 && model->response) { + snprintf(str, sizeof(str), "EV: %1.0f", (double)model->EV); + canvas_draw_str_aligned(canvas, 87, 29, AlignLeft, AlignBottom, str); + } else { + canvas_draw_str_aligned(canvas, 87, 29, AlignLeft, AlignBottom, "EV: --"); + } +} diff --git a/applications/plugins/lightmeter/gui/views/main_view.h b/applications/plugins/lightmeter/gui/views/main_view.h new file mode 100644 index 000000000..4586e6a54 --- /dev/null +++ b/applications/plugins/lightmeter/gui/views/main_view.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include "lightmeter_icons.h" +#include "../../lightmeter_config.h" + +typedef struct MainView MainView; + +typedef enum { + FIXED_APERTURE, + FIXED_SPEED, + + MODES_SIZE +} MainViewMode; + +typedef struct { + uint8_t recv[2]; + MainViewMode current_mode; + float lux; + float EV; + float aperture_val; + float speed_val; + int iso_val; + bool response; + int iso; + int nd; + int aperture; + int speed; + bool dome; +} MainViewModel; + +typedef void (*LightMeterMainViewButtonCallback)(void* context); + +void lightmeter_main_view_set_left_callback( + MainView* lightmeter_main_view, + LightMeterMainViewButtonCallback callback, + void* context); + +MainView* main_view_alloc(); + +void main_view_free(MainView* main_view); + +View* main_view_get_view(MainView* main_view); + +void main_view_set_lux(MainView* main_view, float val); + +void main_view_set_EV(MainView* main_view_, float val); + +void main_view_set_response(MainView* main_view_, bool val); + +void main_view_set_iso(MainView* main_view, int val); + +void main_view_set_nd(MainView* main_view, int val); + +void main_view_set_aperture(MainView* main_view, int val); + +void main_view_set_speed(MainView* main_view, int val); + +void main_view_set_dome(MainView* main_view, bool val); + +bool main_view_get_dome(MainView* main_view); + +void draw_top_row(Canvas* canvas, MainViewModel* context); + +void draw_aperture(Canvas* canvas, MainViewModel* context); + +void draw_speed(Canvas* canvas, MainViewModel* context); + +void draw_mode_indicator(Canvas* canvas, MainViewModel* context); + +void draw_nd_number(Canvas* canvas, MainViewModel* context); + +void draw_EV_number(Canvas* canvas, MainViewModel* context); diff --git a/applications/plugins/lightmeter/icons/T_10x14.png b/applications/plugins/lightmeter/icons/T_10x14.png new file mode 100644 index 000000000..d81c2c424 Binary files /dev/null and b/applications/plugins/lightmeter/icons/T_10x14.png differ diff --git a/applications/plugins/lightmeter/icons/f_10x14.png b/applications/plugins/lightmeter/icons/f_10x14.png new file mode 100644 index 000000000..c3e85c0ec Binary files /dev/null and b/applications/plugins/lightmeter/icons/f_10x14.png differ diff --git a/applications/plugins/lightmeter/images/framed_gui.gif b/applications/plugins/lightmeter/images/framed_gui.gif new file mode 100644 index 000000000..86c4d79a5 Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui.gif differ diff --git a/applications/plugins/lightmeter/images/framed_gui_config.png b/applications/plugins/lightmeter/images/framed_gui_config.png new file mode 100644 index 000000000..3d0f0c88a Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui_config.png differ diff --git a/applications/plugins/lightmeter/images/framed_gui_main.png b/applications/plugins/lightmeter/images/framed_gui_main.png new file mode 100644 index 000000000..89aa1a11f Binary files /dev/null and b/applications/plugins/lightmeter/images/framed_gui_main.png differ diff --git a/applications/plugins/lightmeter/images/gui_config.png b/applications/plugins/lightmeter/images/gui_config.png new file mode 100644 index 000000000..ac7de4517 Binary files /dev/null and b/applications/plugins/lightmeter/images/gui_config.png differ diff --git a/applications/plugins/lightmeter/images/gui_main.png b/applications/plugins/lightmeter/images/gui_main.png new file mode 100644 index 000000000..ae523aa2f Binary files /dev/null and b/applications/plugins/lightmeter/images/gui_main.png differ diff --git a/applications/plugins/lightmeter/lib/BH1750/BH1750.c b/applications/plugins/lightmeter/lib/BH1750/BH1750.c new file mode 100644 index 000000000..28616e040 --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/BH1750.c @@ -0,0 +1,144 @@ +/** + * @file BH1750.h + * @author Oleksii Kutuzov (oleksii.kutuzov@icloud.com) + * @brief + * @version 0.1 + * @date 2022-11-06 + * + * @copyright Copyright (c) 2022 + * + * Ported from: + * https://github.com/lamik/Light_Sensors_STM32 + */ + +#include "BH1750.h" + +BH1750_mode bh1750_mode = BH1750_DEFAULT_MODE; // Current sensor mode +uint8_t bh1750_mt_reg = BH1750_DEFAULT_MTREG; // Current MT register value + +BH1750_STATUS bh1750_init() { + if(BH1750_OK == bh1750_reset()) { + if(BH1750_OK == bh1750_set_mt_reg(BH1750_DEFAULT_MTREG)) { + return BH1750_OK; + } + } + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_reset() { + uint8_t command = 0x07; + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &command, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_power_state(uint8_t PowerOn) { + PowerOn = (PowerOn ? 1 : 0); + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &PowerOn, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_mode(BH1750_mode mode) { + if(!((mode >> 4) || (mode >> 5))) { + return BH1750_ERROR; + } + + if((mode & 0x0F) > 3) { + return BH1750_ERROR; + } + + bool status; + + bh1750_mode = mode; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &mode, 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_set_mt_reg(uint8_t mt_reg) { + if(mt_reg < 31 || mt_reg > 254) { + return BH1750_ERROR; + } + + bh1750_mt_reg = mt_reg; + + uint8_t tmp[2]; + bool status; + + tmp[0] = (0x40 | (mt_reg >> 5)); + tmp[1] = (0x60 | (mt_reg & 0x1F)); + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &tmp[0], 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + if(!status) { + return BH1750_ERROR; + } + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_tx(I2C_BUS, BH1750_ADDRESS, &tmp[1], 1, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + if(status) { + return BH1750_OK; + } + + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_trigger_manual_conversion() { + if(BH1750_OK == bh1750_set_mode(bh1750_mode)) { + return BH1750_OK; + } + return BH1750_ERROR; +} + +BH1750_STATUS bh1750_read_light(float* result) { + float result_tmp; + uint8_t rcv[2]; + bool status; + + furi_hal_i2c_acquire(I2C_BUS); + status = furi_hal_i2c_rx(I2C_BUS, BH1750_ADDRESS, rcv, 2, I2C_TIMEOUT); + furi_hal_i2c_release(I2C_BUS); + + if(status) { + result_tmp = (rcv[0] << 8) | (rcv[1]); + + if(bh1750_mt_reg != BH1750_DEFAULT_MTREG) { + result_tmp *= (float)((uint8_t)BH1750_DEFAULT_MTREG / (float)bh1750_mt_reg); + } + + if(bh1750_mode == ONETIME_HIGH_RES_MODE_2 || bh1750_mode == CONTINUOUS_HIGH_RES_MODE_2) { + result_tmp /= 2.0; + } + + *result = result_tmp / BH1750_CONVERSION_FACTOR; + + return BH1750_OK; + } + return BH1750_ERROR; +} diff --git a/applications/plugins/lightmeter/lib/BH1750/BH1750.h b/applications/plugins/lightmeter/lib/BH1750/BH1750.h new file mode 100644 index 000000000..991350f7f --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/BH1750.h @@ -0,0 +1,103 @@ +/** + * @file BH1750.h + * @author Oleksii Kutuzov (oleksii.kutuzov@icloud.com) + * @brief + * @version 0.1 + * @date 2022-11-06 + * + * @copyright Copyright (c) 2022 + * + * Ported from: + * https://github.com/lamik/Light_Sensors_STM32 + */ + +#include +#include + +#ifndef BH1750_H_ +#define BH1750_H_ + +// I2C BUS +#define I2C_BUS &furi_hal_i2c_handle_external +#define I2C_TIMEOUT 10 + +#define BH1750_ADDRESS (0x23 << 1) + +#define BH1750_POWER_DOWN 0x00 +#define BH1750_POWER_ON 0x01 +#define BH1750_RESET 0x07 +#define BH1750_DEFAULT_MTREG 69 +#define BH1750_DEFAULT_MODE ONETIME_HIGH_RES_MODE + +#define BH1750_CONVERSION_FACTOR 1.2 + +typedef enum { BH1750_OK = 0, BH1750_ERROR = 1 } BH1750_STATUS; + +typedef enum { + CONTINUOUS_HIGH_RES_MODE = 0x10, + CONTINUOUS_HIGH_RES_MODE_2 = 0x11, + CONTINUOUS_LOW_RES_MODE = 0x13, + ONETIME_HIGH_RES_MODE = 0x20, + ONETIME_HIGH_RES_MODE_2 = 0x21, + ONETIME_LOW_RES_MODE = 0x23 +} BH1750_mode; + +/** + * @brief Initialize the sensor. Sends the reset command and sets the measurement register to the default value. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_init(); + +/** + * @brief Reset all registers to the default value. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_reset(); + +/** + * @brief Sets the power state. 1 - running; 0 - sleep, low power. + * + * @param PowerOn sensor state. + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_power_state(uint8_t PowerOn); + +/** + * @brief Set the Measurement Time register. It allows to increase or decrease the sensitivity. + * + * @param MTreg value from 31 to 254, defaults to 69. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_mt_reg(uint8_t MTreg); + +/** + * @brief Set the mode of converting. Look into the bh1750_mode enum. + * + * @param Mode mode enumerator + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_set_mode(BH1750_mode Mode); + +/** + * @brief Trigger the conversion in manual modes. + * + * @details a low-resolution mode, the conversion time is typically 16 ms, and for a high-resolution + * mode is 120 ms. You need to wait until reading the measurement value. There is no need + * to exit low-power mode for manual conversion. It makes automatically. + * + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_trigger_manual_conversion(); + +/** + * @brief Read the converted value and calculate the result. + * + * @param Result stores received value to this variable. + * @return BH1750_STATUS + */ +BH1750_STATUS bh1750_read_light(float* Result); + +#endif /* BH1750_H_ */ diff --git a/applications/plugins/lightmeter/lib/BH1750/LICENSE b/applications/plugins/lightmeter/lib/BH1750/LICENSE new file mode 100644 index 000000000..cb2f65db5 --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Oleksii Kutuzov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/plugins/lightmeter/lib/BH1750/README.md b/applications/plugins/lightmeter/lib/BH1750/README.md new file mode 100644 index 000000000..b1338d4ab --- /dev/null +++ b/applications/plugins/lightmeter/lib/BH1750/README.md @@ -0,0 +1,2 @@ +# flipperzero-BH1750 +BH1750 light sensor library for Flipper Zero diff --git a/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf b/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf new file mode 100644 index 000000000..267efddc6 Binary files /dev/null and b/applications/plugins/lightmeter/lib/BH1750/docs/BH1750.pdf differ diff --git a/applications/plugins/lightmeter/lightmeter.c b/applications/plugins/lightmeter/lightmeter.c new file mode 100644 index 000000000..6034a9ee9 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter.c @@ -0,0 +1,161 @@ +#include "lightmeter.h" +#include "lightmeter_helper.h" + +#define WORKER_TAG "MAIN APP" + +static bool lightmeter_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + LightMeterApp* app = context; + + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool lightmeter_back_event_callback(void* context) { + furi_assert(context); + LightMeterApp* app = context; + + return scene_manager_handle_back_event(app->scene_manager); +} + +static void lightmeter_tick_event_callback(void* context) { + furi_assert(context); + LightMeterApp* app = context; + + scene_manager_handle_tick_event(app->scene_manager); +} + +LightMeterApp* lightmeter_app_alloc(uint32_t first_scene) { + LightMeterApp* app = malloc(sizeof(LightMeterApp)); + + // Sensor + bh1750_set_power_state(1); + bh1750_init(); + bh1750_set_mode(ONETIME_HIGH_RES_MODE); + bh1750_set_mt_reg(100); + + // Set default values to config + app->config = malloc(sizeof(LightMeterConfig)); + app->config->iso = DEFAULT_ISO; + app->config->nd = DEFAULT_ND; + app->config->aperture = DEFAULT_APERTURE; + app->config->dome = DEFAULT_DOME; + + // Records + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + notification_message( + app->notifications, &sequence_display_backlight_enforce_on); // force on backlight + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&lightmeter_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, lightmeter_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, lightmeter_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, lightmeter_tick_event_callback, furi_ms_to_ticks(200)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Views + app->main_view = main_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewMainView, main_view_get_view(app->main_view)); + + // Set default values to main view from config + main_view_set_iso(app->main_view, app->config->iso); + main_view_set_nd(app->main_view, app->config->nd); + main_view_set_aperture(app->main_view, app->config->aperture); + main_view_set_speed(app->main_view, DEFAULT_SPEED); + main_view_set_dome(app->main_view, app->config->dome); + + // Variable item list + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + LightMeterAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewAbout, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, LightMeterAppViewHelp, widget_get_view(app->widget)); + + // Set first scene + scene_manager_next_scene(app->scene_manager, first_scene); + return app; +} + +void lightmeter_app_free(LightMeterApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewMainView); + main_view_free(app->main_view); + + // Variable item list + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewVarItemList); + variable_item_list_free(app->var_item_list); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewAbout); + view_dispatcher_remove_view(app->view_dispatcher, LightMeterAppViewHelp); + widget_free(app->widget); + + // View dispatcher + scene_manager_free(app->scene_manager); + view_dispatcher_free(app->view_dispatcher); + + // Records + furi_record_close(RECORD_GUI); + notification_message( + app->notifications, + &sequence_display_backlight_enforce_auto); // set backlight back to auto + furi_record_close(RECORD_NOTIFICATION); + + bh1750_set_power_state(0); + + free(app->config); + free(app); +} + +int32_t lightmeter_app(void* p) { + UNUSED(p); + uint32_t first_scene = LightMeterAppSceneMain; + LightMeterApp* app = lightmeter_app_alloc(first_scene); + view_dispatcher_run(app->view_dispatcher); + lightmeter_app_free(app); + return 0; +} + +void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config) { + LightMeterApp* app = context; + + app->config = config; +} + +void lightmeter_app_i2c_callback(LightMeterApp* context) { + LightMeterApp* app = context; + + float EV = 0; + float lux = 0; + bool response = 0; + + if(bh1750_trigger_manual_conversion() == BH1750_OK) response = 1; + + if(response) { + bh1750_read_light(&lux); + + if(main_view_get_dome(app->main_view)) lux *= DOME_COEFFICIENT; + + EV = lux2ev(lux); + } + + main_view_set_lux(app->main_view, lux); + main_view_set_EV(app->main_view, EV); + main_view_set_response(app->main_view, response); +} diff --git a/applications/plugins/lightmeter/lightmeter.h b/applications/plugins/lightmeter/lightmeter.h new file mode 100644 index 000000000..679b32d15 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include "gui/views/main_view.h" + +#include +#include + +#include "gui/scenes/config/lightmeter_scene.h" +#include + +#include "lightmeter_config.h" +#include + +typedef struct { + int iso; + int nd; + int aperture; + int dome; +} LightMeterConfig; + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + MainView* main_view; + VariableItemList* var_item_list; + LightMeterConfig* config; + NotificationApp* notifications; + Widget* widget; +} LightMeterApp; + +typedef enum { + LightMeterAppViewMainView, + LightMeterAppViewConfigView, + LightMeterAppViewVarItemList, + LightMeterAppViewAbout, + LightMeterAppViewHelp, +} LightMeterAppView; + +typedef enum { + LightMeterAppCustomEventConfig, + LightMeterAppCustomEventHelp, + LightMeterAppCustomEventAbout, +} LightMeterAppCustomEvent; + +void lightmeter_app_set_config(LightMeterApp* context, LightMeterConfig* config); + +void lightmeter_app_i2c_callback(LightMeterApp* context); diff --git a/applications/plugins/lightmeter/lightmeter.png b/applications/plugins/lightmeter/lightmeter.png new file mode 100644 index 000000000..cacd2276f Binary files /dev/null and b/applications/plugins/lightmeter/lightmeter.png differ diff --git a/applications/plugins/lightmeter/lightmeter_config.h b/applications/plugins/lightmeter/lightmeter_config.h new file mode 100644 index 000000000..023235cff --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_config.h @@ -0,0 +1,99 @@ +#pragma once + +#define LM_VERSION_APP "0.5" +#define LM_DEVELOPED "Oleksii Kutuzov" +#define LM_GITHUB "https://github.com/oleksiikutuzov/flipperzero-lightmeter" + +#define DOME_COEFFICIENT 2.3 +#define DEFAULT_ISO ISO_100 +#define DEFAULT_ND ND_0 +#define DEFAULT_APERTURE AP_2_8 +#define DEFAULT_SPEED SPEED_125 +#define DEFAULT_DOME WITHOUT_DOME + +typedef enum { + ISO_6, + ISO_12, + ISO_25, + ISO_50, + ISO_100, + ISO_200, + ISO_400, + ISO_800, + ISO_1600, + ISO_3200, + ISO_6400, + ISO_12800, + ISO_25600, + ISO_51200, + ISO_102400, + + ISO_NUM, +} LightMeterISONumbers; + +typedef enum { + ND_0, + ND_2, + ND_4, + ND_8, + ND_16, + ND_32, + ND_64, + ND_128, + ND_256, + ND_512, + ND_1024, + ND_2048, + ND_4096, + + ND_NUM, +} LightMeterNDNumbers; + +typedef enum { + AP_1, + AP_1_4, + AP_2, + AP_2_8, + AP_4, + AP_5_6, + AP_8, + AP_11, + AP_16, + AP_22, + AP_32, + AP_45, + AP_64, + AP_90, + AP_128, + + AP_NUM, +} LightMeterApertureNumbers; + +typedef enum { + SPEED_8000, + SPEED_4000, + SPEED_2000, + SPEED_1000, + SPEED_500, + SPEED_250, + SPEED_125, + SPEED_60, + SPEED_30, + SPEED_15, + SPEED_8, + SPEED_4, + SPEED_2, + SPEED_1S, + SPEED_2S, + SPEED_4S, + SPEED_8S, + SPEED_15S, + SPEED_30S, + + SPEED_NUM, +} LightMeterSpeedNumbers; + +typedef enum { + WITHOUT_DOME, + WITH_DOME, +} LightMeterDomePresence; diff --git a/applications/plugins/lightmeter/lightmeter_helper.c b/applications/plugins/lightmeter/lightmeter_helper.c new file mode 100644 index 000000000..1cdddfca9 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_helper.c @@ -0,0 +1,69 @@ +#include "lightmeter_helper.h" +#include "lightmeter_config.h" + +static const float aperture_numbers[] = { + [AP_1] = 1.0, + [AP_1_4] = 1.4, + [AP_2] = 2.0, + [AP_2_8] = 2.8, + [AP_4] = 4.0, + [AP_5_6] = 5.6, + [AP_8] = 8, + [AP_11] = 11, + [AP_16] = 16, + [AP_22] = 22, + [AP_32] = 32, + [AP_45] = 45, + [AP_64] = 64, + [AP_90] = 90, + [AP_128] = 128, +}; + +static const float time_numbers[] = { + [SPEED_8000] = 1.0 / 8000, [SPEED_4000] = 1.0 / 4000, [SPEED_2000] = 1.0 / 2000, + [SPEED_1000] = 1.0 / 1000, [SPEED_500] = 1.0 / 500, [SPEED_250] = 1.0 / 250, + [SPEED_125] = 1.0 / 125, [SPEED_60] = 1.0 / 60, [SPEED_30] = 1.0 / 30, + [SPEED_15] = 1.0 / 15, [SPEED_8] = 1.0 / 8, [SPEED_4] = 1.0 / 4, + [SPEED_2] = 1.0 / 2, [SPEED_1S] = 1.0, [SPEED_2S] = 2.0, + [SPEED_4S] = 4.0, [SPEED_8S] = 8.0, [SPEED_15S] = 15.0, + [SPEED_30S] = 30.0, +}; + +float lux2ev(float lux) { + return log2(lux / 2.5); +} + +float getMinDistance(float x, float v1, float v2) { + if(x - v1 > v2 - x) { + return v2; + } + + return v1; +} + +// Convert calculated aperture value to photography style aperture value. +float normalizeAperture(float a) { + for(int i = 0; i < AP_NUM; i++) { + float a1 = aperture_numbers[i]; + float a2 = aperture_numbers[i + 1]; + + if(a1 < a && a2 >= a) { + return getMinDistance(a, a1, a2); + } + } + + return 0; +} + +float normalizeTime(float a) { + for(int i = 0; i < SPEED_NUM; i++) { + float a1 = time_numbers[i]; + float a2 = time_numbers[i + 1]; + + if(a1 < a && a2 >= a) { + return getMinDistance(a, a1, a2); + } + } + + return 0; +} diff --git a/applications/plugins/lightmeter/lightmeter_helper.h b/applications/plugins/lightmeter/lightmeter_helper.h new file mode 100644 index 000000000..78ea6a8d8 --- /dev/null +++ b/applications/plugins/lightmeter/lightmeter_helper.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +float lux2ev(float lux); + +float getMinDistance(float x, float v1, float v2); + +float normalizeAperture(float a); + +float normalizeTime(float a); diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_0.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_0.png index 1b4de2fcd..e8a8fcd06 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_0.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_1.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_1.png index 5c05ab5e1..82fa5a8cc 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_1.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_2.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_2.png index 2065ab2bf..1120d8539 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_2.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_3.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_3.png index 3a04ec159..e78ff10dd 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_3.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_4.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_4.png index 67eb6274a..59cb890f8 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_4.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_5.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_5.png index c35307497..da89112ca 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_5.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_6.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_6.png index fa85be7cc..49f040bb5 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_6.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_7.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_7.png index 4adaf63a8..de0efa639 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_7.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_8.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_8.png index c1f7c02f5..5d9841fe2 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_8.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L2_FlipperCity_128x64/frame_9.png b/assets/dolphin/external/L2_FlipperCity_128x64/frame_9.png index 64df2cb16..1c6f7c903 100644 Binary files a/assets/dolphin/external/L2_FlipperCity_128x64/frame_9.png and b/assets/dolphin/external/L2_FlipperCity_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_0.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_0.png new file mode 100644 index 000000000..6f058ed55 Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_1.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_1.png new file mode 100644 index 000000000..ab34e0fa9 Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_10.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_10.png new file mode 100644 index 000000000..382e161fa Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_11.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_11.png new file mode 100644 index 000000000..5638a1a27 Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_2.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_2.png new file mode 100644 index 000000000..0f39e356a Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_3.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_3.png new file mode 100644 index 000000000..fc5f2d2c4 Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_4.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_4.png new file mode 100644 index 000000000..bbab8543d Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_5.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_5.png new file mode 100644 index 000000000..dabadecab Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_6.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_6.png new file mode 100644 index 000000000..098c2f528 Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_7.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_7.png new file mode 100644 index 000000000..c3b82a23e Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_8.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_8.png new file mode 100644 index 000000000..8067df06e Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/frame_9.png b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_9.png new file mode 100644 index 000000000..03875fb30 Binary files /dev/null and b/assets/dolphin/external/L3_FlipperMustache_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L3_FlipperMustache_128x64/meta.txt b/assets/dolphin/external/L3_FlipperMustache_128x64/meta.txt new file mode 100644 index 000000000..8440c67d5 --- /dev/null +++ b/assets/dolphin/external/L3_FlipperMustache_128x64/meta.txt @@ -0,0 +1,34 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 10 +Active frames: 2 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 +Active cycles: 8 +Frame rate: 3 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 60 +Y: 30 +Text: Some hack\nwe made +AlignH: Left +AlignV: Center +StartFrame: 10 +EndFrame: 17 + +Slot: 0 +X: 60 +Y: 30 +Text: It's a firmware\nupgrade! +AlignH: Left +AlignV: Center +StartFrame: 18 +EndFrame: 25 + + diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 857465392..af42b5ce5 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -64,6 +64,13 @@ Min level: 2 Max level: 30 Weight: 7 +Name: L3_FlipperMustache_128x64 +Min butthurt: 0 +Max butthurt: 14 +Min level: 3 +Max level: 30 +Weight: 7 + Name: L3_Fireplace_128x64 Min butthurt: 0 Max butthurt: 14 diff --git a/assets/resources/ibtnfuzzer/example_uids_cyfral.txt b/assets/resources/ibtnfuzzer/example_uids_cyfral.txt new file mode 100644 index 000000000..497d2211a --- /dev/null +++ b/assets/resources/ibtnfuzzer/example_uids_cyfral.txt @@ -0,0 +1,8 @@ +# Example file, P.S. keep empty line at the end! +0000 +F000 +FE00 +CAFE +00CA +FF00 +FFFF diff --git a/assets/resources/ibtnfuzzer/example_uids_ds1990.txt b/assets/resources/ibtnfuzzer/example_uids_ds1990.txt new file mode 100644 index 000000000..6828bb423 --- /dev/null +++ b/assets/resources/ibtnfuzzer/example_uids_ds1990.txt @@ -0,0 +1,11 @@ +# Example file, P.S. keep empty line at the end! +0000000000000000 +FE00000000000000 +CAFE000000000000 +00CAFE0000000000 +0000CAFE00000000 +000000CAFE000000 +00000000CA000000 +0000000000A00000 +00000000000123FF +FFFFFFFFFFFFFFFF diff --git a/assets/resources/ibtnfuzzer/example_uids_metakom.txt b/assets/resources/ibtnfuzzer/example_uids_metakom.txt new file mode 100644 index 000000000..911ea73b2 --- /dev/null +++ b/assets/resources/ibtnfuzzer/example_uids_metakom.txt @@ -0,0 +1,9 @@ +# Example file, P.S. keep empty line at the end! +00000000 +F0000000 +E0000000 +FE000000 +CAFE0000 +00CAFE00 +0000CA00 +FFFFFFFF diff --git a/assets/resources/subghz/assets/setting_user.txt b/assets/resources/subghz/assets/setting_user.txt index 36f5e479a..d049f3395 100644 --- a/assets/resources/subghz/assets/setting_user.txt +++ b/assets/resources/subghz/assets/setting_user.txt @@ -39,8 +39,10 @@ Frequency: 433420000 Frequency: 433657070 Frequency: 433889000 Frequency: 433920000 +Frequency: 434075000 Frequency: 434176948 Frequency: 434190000 +Frequency: 434390000 Frequency: 434420000 Frequency: 434620000 Frequency: 434775000 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 9601c4f73..b0c05be85 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.5,, +Version,+,7.51,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index 714975254..67d3fe3d3 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -385,7 +385,7 @@ static void subghz_protocol_magellan_get_event_serialize(uint8_t event, FuriStri ((event >> 2) & 0x1 ? ", ?" : ""), ((event >> 3) & 0x1 ? ", Power On" : ""), ((event >> 4) & 0x1 ? ", MT:Wireless_Reed" : ""), - ((event >> 5) & 0x1 ? ", MT:Motion_Sensor" : ""), + ((event >> 5) & 0x1 ? ", MT:Motion_\nSensor" : ""), ((event >> 6) & 0x1 ? ", ?" : ""), ((event >> 7) & 0x1 ? ", ?" : "")); }