Merge remote-tracking branch 'origin/release-candidate' into release
							
								
								
									
										2
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -42,6 +42,8 @@ | ||||
| 
 | ||||
| /applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm | ||||
| 
 | ||||
| /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| 
 | ||||
| # Assets | ||||
| /assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										20
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -60,8 +60,9 @@ jobs: | ||||
|         run: | | ||||
|           set -e | ||||
|           for TARGET in ${TARGETS}; do | ||||
|                 ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ | ||||
|                 copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||
|             TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ | ||||
|             ./fbt TARGET_HW=$TARGET copro_dist updater_package \ | ||||
|             ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||
|           done | ||||
| 
 | ||||
|       - name: 'Move upload files' | ||||
| @ -95,14 +96,14 @@ jobs: | ||||
| 
 | ||||
|       - name: 'Upload map analyser files to storage' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         uses: keithweaver/aws-s3-github-action@v1.0.0 | ||||
|         uses: prewk/s3-cp-action@v2 | ||||
|         with: | ||||
|           source: map_analyser_files/ | ||||
|           destination: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" | ||||
|           aws_s3_endpoint: "${{ secrets.MAP_REPORT_AWS_ENDPOINT }}" | ||||
|           aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}" | ||||
|           aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}" | ||||
|           aws_region: "${{ secrets.MAP_REPORT_AWS_REGION }}" | ||||
|           flags: --recursive | ||||
|           source: "./map_analyser_files/" | ||||
|           dest: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" | ||||
|           flags: "--recursive --acl public-read" | ||||
| 
 | ||||
|       - name: 'Trigger map file reporter' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
| @ -113,7 +114,6 @@ jobs: | ||||
|           event-type: map-file-analyse | ||||
|           client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}' | ||||
| 
 | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
| @ -186,6 +186,6 @@ jobs: | ||||
|         run: | | ||||
|           set -e | ||||
|           for TARGET in ${TARGETS}; do | ||||
|                 ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ | ||||
|                 updater_package DEBUG=0 COMPACT=1 | ||||
|             TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ | ||||
|             ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package | ||||
|           done | ||||
|  | ||||
							
								
								
									
										23
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -54,17 +54,16 @@ jobs: | ||||
|           ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 | ||||
|           echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|       - name: 'Upload report' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }} | ||||
|         run: | | ||||
|           mkdir -p ~/.ssh | ||||
|           ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts | ||||
|           echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; | ||||
|           chmod 600 ./deploy_key; | ||||
|           rsync -avrzP --mkpath \ | ||||
|               -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ | ||||
|               build/f7-firmware-DC/pvsreport/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${BRANCH_NAME}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/"; | ||||
|           rm ./deploy_key; | ||||
|         uses: prewk/s3-cp-action@v2 | ||||
|         with: | ||||
|           aws_s3_endpoint: "${{ secrets.PVS_AWS_ENDPOINT }}" | ||||
|           aws_access_key_id: "${{ secrets.PVS_AWS_ACCESS_KEY }}" | ||||
|           aws_secret_access_key: "${{ secrets.PVS_AWS_SECRET_KEY }}" | ||||
|           source: "./build/f7-firmware-DC/pvsreport" | ||||
|           dest: "s3://${{ secrets.PVS_AWS_BUCKET }}/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/" | ||||
|           flags: "--recursive --acl public-read" | ||||
| 
 | ||||
|       - name: 'Find Previous Comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} | ||||
| @ -83,12 +82,12 @@ jobs: | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           body: | | ||||
|             **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** | ||||
|             - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) | ||||
|             - [Report](https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) | ||||
|           edit-mode: replace | ||||
| 
 | ||||
|       - name: 'Raise exception' | ||||
|         if: ${{ steps.pvs-warn.outputs.warnings != 0 }} | ||||
|         run: | | ||||
|           echo "Please fix all PVS varnings before merge" | ||||
|           echo "Please fix all PVS warnings before merge" | ||||
|           exit 1 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,4 +1,5 @@ | ||||
| *.swp | ||||
| *.swo | ||||
| *.gdb_history | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										5
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -11,5 +11,8 @@ | ||||
| 		"augustocdias.tasks-shell-input" | ||||
| 	], | ||||
| 	// List of extensions recommended by VS Code that should not be recommended for users of this workspace. | ||||
| 	"unwantedRecommendations": [] | ||||
| 	"unwantedRecommendations": [ | ||||
| 		"twxs.cmake", | ||||
| 		"ms-vscode.cmake-tools" | ||||
| 	] | ||||
| } | ||||
| @ -34,7 +34,7 @@ void AccessorApp::run(void) { | ||||
| AccessorApp::AccessorApp() | ||||
|     : text_store{0} { | ||||
|     notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION)); | ||||
|     onewire_host = onewire_host_alloc(); | ||||
|     onewire_host = onewire_host_alloc(&ibutton_gpio); | ||||
|     furi_hal_power_enable_otg(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ App( | ||||
|     appid="accessor", | ||||
|     name="Accessor", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     targets=["f7"], | ||||
|     entry_point="accessor_app", | ||||
|     cdefines=["APP_ACCESSOR"], | ||||
|     requires=["gui"], | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| #include "bt_carrier_test.h" | ||||
| #include "bt_test.h" | ||||
| #include "bt_test_types.h" | ||||
| #include "furi_hal_bt.h" | ||||
| #include <furi_hal_bt.h> | ||||
| 
 | ||||
| struct BtCarrierTest { | ||||
|     BtTest* bt_test; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| #include "bt_packet_test.h" | ||||
| #include "bt_test.h" | ||||
| #include "bt_test_types.h" | ||||
| #include "furi_hal_bt.h" | ||||
| #include <furi_hal_bt.h> | ||||
| 
 | ||||
| struct BtPacketTest { | ||||
|     BtTest* bt_test; | ||||
|  | ||||
							
								
								
									
										9
									
								
								applications/debug/example_custom_font/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,9 @@ | ||||
| App( | ||||
|     appid="example_custom_font", | ||||
|     name="Example: custom font", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="example_custom_font_main", | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     fap_category="Debug", | ||||
| ) | ||||
							
								
								
									
										98
									
								
								applications/debug/example_custom_font/example_custom_font.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,98 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| #include <gui/gui.h> | ||||
| #include <input/input.h> | ||||
| 
 | ||||
| //This arrays contains the font itself. You can use any u8g2 font you want
 | ||||
| 
 | ||||
| /*
 | ||||
| Fontname: -Raccoon-Fixed4x6-Medium-R-Normal--6-60-75-75-P-40-ISO10646-1 | ||||
| Copyright:  | ||||
| Glyphs: 95/203 | ||||
| BBX Build Mode: 0 | ||||
| */ | ||||
| const uint8_t u8g2_font_tom_thumb_4x6_tr[725] = | ||||
|     "_\0\2\2\2\3\3\4\4\3\6\0\377\5\377\5\0\0\352\1\330\2\270 \5\340\315\0!\6\265\310" | ||||
|     "\254\0\42\6\213\313$\25#\10\227\310\244\241\206\12$\10\227\310\215\70b\2%\10\227\310d\324F\1" | ||||
|     "&\10\227\310(\65R\22'\5\251\313\10(\6\266\310\251\62)\10\226\310\304\224\24\0*\6\217\312\244" | ||||
|     "\16+\7\217\311\245\225\0,\6\212\310)\0-\5\207\312\14.\5\245\310\4/\7\227\310Ve\4\60" | ||||
|     "\7\227\310-k\1\61\6\226\310\255\6\62\10\227\310h\220\312\1\63\11\227\310h\220\62X\0\64\10\227" | ||||
|     "\310$\65b\1\65\10\227\310\214\250\301\2\66\10\227\310\315\221F\0\67\10\227\310\314TF\0\70\10\227" | ||||
|     "\310\214\64\324\10\71\10\227\310\214\64\342\2:\6\255\311\244\0;\7\222\310e\240\0<\10\227\310\246\32" | ||||
|     "d\20=\6\217\311l\60>\11\227\310d\220A*\1\77\10\227\310\314\224a\2@\10\227\310UC\3" | ||||
|     "\1A\10\227\310UC\251\0B\10\227\310\250\264\322\2C\7\227\310\315\32\10D\10\227\310\250d-\0" | ||||
|     "E\10\227\310\214\70\342\0F\10\227\310\214\70b\4G\10\227\310\315\221\222\0H\10\227\310$\65\224\12" | ||||
|     "I\7\227\310\254X\15J\7\227\310\226\252\2K\10\227\310$\265\222\12L\7\227\310\304\346\0M\10\227" | ||||
|     "\310\244\61\224\12N\10\227\310\244q\250\0O\7\227\310UV\5P\10\227\310\250\264b\4Q\10\227\310" | ||||
|     "Uj$\1R\10\227\310\250\64V\1S\10\227\310m\220\301\2T\7\227\310\254\330\2U\7\227\310$" | ||||
|     "W\22V\10\227\310$\253L\0W\10\227\310$\65\206\12X\10\227\310$\325R\1Y\10\227\310$U" | ||||
|     "V\0Z\7\227\310\314T\16[\7\227\310\214X\16\134\10\217\311d\220A\0]\7\227\310\314r\4^" | ||||
|     "\5\213\313\65_\5\207\310\14`\6\212\313\304\0a\7\223\310\310\65\2b\10\227\310D\225\324\2c\7" | ||||
|     "\223\310\315\14\4d\10\227\310\246\245\222\0e\6\223\310\235\2f\10\227\310\246\264b\2g\10\227\307\35" | ||||
|     "\61%\0h\10\227\310D\225\254\0i\6\265\310\244\1j\10\233\307f\30U\5k\10\227\310\304\264T" | ||||
|     "\1l\7\227\310\310\326\0m\7\223\310<R\0n\7\223\310\250d\5o\7\223\310U\252\2p\10\227" | ||||
|     "\307\250\244V\4q\10\227\307-\225d\0r\6\223\310\315\22s\10\223\310\215\70\22\0t\10\227\310\245" | ||||
|     "\25\243\0u\7\223\310$+\11v\10\223\310$\65R\2w\7\223\310\244q\4x\7\223\310\244\62\25" | ||||
|     "y\11\227\307$\225dJ\0z\7\223\310\254\221\6{\10\227\310\251\32D\1|\6\265\310(\1}\11" | ||||
|     "\227\310\310\14RR\0~\6\213\313\215\4\0\0\0\4\377\377\0"; | ||||
| 
 | ||||
| // Screen is 128x64 px
 | ||||
| static void app_draw_callback(Canvas* canvas, void* ctx) { | ||||
|     UNUSED(ctx); | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
| 
 | ||||
|     canvas_set_custom_u8g2_font(canvas, u8g2_font_tom_thumb_4x6_tr); | ||||
| 
 | ||||
|     canvas_draw_str(canvas, 0, 6, "This is a tiny custom font"); | ||||
|     canvas_draw_str(canvas, 0, 12, "012345.?! ,:;\"\'@#$%"); | ||||
| } | ||||
| 
 | ||||
| static void app_input_callback(InputEvent* input_event, void* ctx) { | ||||
|     furi_assert(ctx); | ||||
| 
 | ||||
|     FuriMessageQueue* event_queue = ctx; | ||||
|     furi_message_queue_put(event_queue, input_event, FuriWaitForever); | ||||
| } | ||||
| 
 | ||||
| int32_t example_custom_font_main(void* p) { | ||||
|     UNUSED(p); | ||||
|     FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); | ||||
| 
 | ||||
|     // Configure view port
 | ||||
|     ViewPort* view_port = view_port_alloc(); | ||||
|     view_port_draw_callback_set(view_port, app_draw_callback, view_port); | ||||
|     view_port_input_callback_set(view_port, app_input_callback, event_queue); | ||||
| 
 | ||||
|     // Register view port in GUI
 | ||||
|     Gui* gui = furi_record_open(RECORD_GUI); | ||||
|     gui_add_view_port(gui, view_port, GuiLayerFullscreen); | ||||
| 
 | ||||
|     InputEvent event; | ||||
| 
 | ||||
|     bool running = true; | ||||
| 
 | ||||
|     while(running) { | ||||
|         if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { | ||||
|             if((event.type == InputTypePress) || (event.type == InputTypeRepeat)) { | ||||
|                 switch(event.key) { | ||||
|                 case InputKeyBack: | ||||
|                     running = false; | ||||
|                     break; | ||||
|                 default: | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     view_port_enabled_set(view_port, false); | ||||
|     gui_remove_view_port(gui, view_port); | ||||
|     view_port_free(view_port); | ||||
|     furi_message_queue_free(event_queue); | ||||
| 
 | ||||
|     furi_record_close(RECORD_GUI); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| @ -1,10 +1,11 @@ | ||||
| #include <file_browser_test_icons.h> | ||||
| #include "file_browser_app_i.h" | ||||
| #include "gui/modules/file_browser.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <file_browser_test_icons.h> | ||||
| 
 | ||||
| #include <gui/modules/file_browser.h> | ||||
| #include <storage/storage.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { | ||||
|     furi_assert(context); | ||||
|  | ||||
| @ -2,6 +2,7 @@ App( | ||||
|     appid="lfrfid_debug", | ||||
|     name="LF-RFID Debug", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     targets=["f7"], | ||||
|     entry_point="lfrfid_debug_app", | ||||
|     requires=[ | ||||
|         "gui", | ||||
|  | ||||
| @ -348,13 +348,37 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { | ||||
|     memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2); | ||||
| 
 | ||||
|     MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data; | ||||
|     // Check the manufacturer block (should be uid[uid_len] + 0xFF[rest])
 | ||||
|     // Check the manufacturer block (should be uid[uid_len] + BCC (for 4byte only) + SAK + ATQA0 + ATQA1 + 0xFF[rest])
 | ||||
|     uint8_t manufacturer_block[16] = {0}; | ||||
|     memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); | ||||
|     mu_assert( | ||||
|         memcmp(manufacturer_block, uid, uid_len) == 0, | ||||
|         "manufacturer_block uid doesn't match the file\r\n"); | ||||
|     for(uint8_t i = uid_len; i < 16; i++) { | ||||
| 
 | ||||
|     uint8_t position = 0; | ||||
|     if(uid_len == 4) { | ||||
|         position = uid_len; | ||||
| 
 | ||||
|         uint8_t bcc = 0; | ||||
| 
 | ||||
|         for(int i = 0; i < uid_len; i++) { | ||||
|             bcc ^= uid[i]; | ||||
|         } | ||||
| 
 | ||||
|         mu_assert(manufacturer_block[position] == bcc, "manufacturer_block bcc assert failed\r\n"); | ||||
|     } else { | ||||
|         position = uid_len - 1; | ||||
|     } | ||||
| 
 | ||||
|     mu_assert(manufacturer_block[position + 1] == sak, "manufacturer_block sak assert failed\r\n"); | ||||
| 
 | ||||
|     mu_assert( | ||||
|         manufacturer_block[position + 2] == atqa[0], "manufacturer_block atqa0 assert failed\r\n"); | ||||
| 
 | ||||
|     mu_assert( | ||||
|         manufacturer_block[position + 3] == atqa[1], "manufacturer_block atqa1 assert failed\r\n"); | ||||
| 
 | ||||
|     for(uint8_t i = position + 4; i < 16; i++) { | ||||
|         mu_assert( | ||||
|             manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n"); | ||||
|     } | ||||
|  | ||||
| @ -89,7 +89,7 @@ static void test_rpc_setup(void) { | ||||
|     } | ||||
|     furi_check(rpc_session[0].session); | ||||
| 
 | ||||
|     rpc_session[0].output_stream = furi_stream_buffer_alloc(1000, 1); | ||||
|     rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1); | ||||
|     rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback); | ||||
|     rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); | ||||
|     rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); | ||||
|  | ||||
| @ -12,8 +12,9 @@ | ||||
| #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") | ||||
| #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") | ||||
| #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") | ||||
| #define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n") | ||||
| #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") | ||||
| #define TEST_RANDOM_COUNT_PARSE 273 | ||||
| #define TEST_RANDOM_COUNT_PARSE 329 | ||||
| #define TEST_TIMEOUT 10000 | ||||
| 
 | ||||
| static SubGhzEnvironment* environment_handler; | ||||
| @ -43,6 +44,8 @@ static void subghz_test_init(void) { | ||||
|         environment_handler, CAME_ATOMO_DIR_NAME); | ||||
|     subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||
|         environment_handler, NICE_FLOR_S_DIR_NAME); | ||||
|     subghz_environment_set_alutech_at_4n_rainbow_table_file_name( | ||||
|         environment_handler, ALUTECH_AT_4N_DIR_NAME); | ||||
|     subghz_environment_set_protocol_registry( | ||||
|         environment_handler, (void*)&subghz_protocol_registry); | ||||
| 
 | ||||
| @ -489,6 +492,14 @@ MU_TEST(subghz_decoder_linear_test) { | ||||
|         "Test decoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_decoder_linear_delta3_test) { | ||||
|     mu_assert( | ||||
|         subghz_decoder_test( | ||||
|             EXT_PATH("unit_tests/subghz/linear_delta3_raw.sub"), | ||||
|             SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME), | ||||
|         "Test decoder " SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_decoder_megacode_test) { | ||||
|     mu_assert( | ||||
|         subghz_decoder_test( | ||||
| @ -604,6 +615,36 @@ MU_TEST(subghz_decoder_holtek_ht12x_test) { | ||||
|         "Test decoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_decoder_dooya_test) { | ||||
|     mu_assert( | ||||
|         subghz_decoder_test( | ||||
|             EXT_PATH("unit_tests/subghz/dooya_raw.sub"), SUBGHZ_PROTOCOL_DOOYA_NAME), | ||||
|         "Test decoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_decoder_alutech_at_4n_test) { | ||||
|     mu_assert( | ||||
|         subghz_decoder_test( | ||||
|             EXT_PATH("unit_tests/subghz/alutech_at_4n_raw.sub"), | ||||
|             SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME), | ||||
|         "Test decoder " SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_decoder_nice_one_test) { | ||||
|     mu_assert( | ||||
|         subghz_decoder_test( | ||||
|             EXT_PATH("unit_tests/subghz/nice_one_raw.sub"), SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME), | ||||
|         "Test decoder " SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_decoder_kinggates_stylo4k_test) { | ||||
|     mu_assert( | ||||
|         subghz_decoder_test( | ||||
|             EXT_PATH("unit_tests/subghz/kinggates_stylo4k_raw.sub"), | ||||
|             SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME), | ||||
|         "Test decoder " SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| //test encoders
 | ||||
| MU_TEST(subghz_encoder_princeton_test) { | ||||
|     mu_assert( | ||||
| @ -647,6 +688,12 @@ MU_TEST(subghz_encoder_linear_test) { | ||||
|         "Test encoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_encoder_linear_delta3_test) { | ||||
|     mu_assert( | ||||
|         subghz_encoder_test(EXT_PATH("unit_tests/subghz/linear_delta3.sub")), | ||||
|         "Test encoder " SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_encoder_megacode_test) { | ||||
|     mu_assert( | ||||
|         subghz_encoder_test(EXT_PATH("unit_tests/subghz/megacode.sub")), | ||||
| @ -743,6 +790,12 @@ MU_TEST(subghz_encoder_holtek_ht12x_test) { | ||||
|         "Test encoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_encoder_dooya_test) { | ||||
|     mu_assert( | ||||
|         subghz_encoder_test(EXT_PATH("unit_tests/subghz/dooya.sub")), | ||||
|         "Test encoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_random_test) { | ||||
|     mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); | ||||
| } | ||||
| @ -772,6 +825,7 @@ MU_TEST_SUITE(subghz) { | ||||
|     MU_RUN_TEST(subghz_decoder_somfy_telis_test); | ||||
|     MU_RUN_TEST(subghz_decoder_star_line_test); | ||||
|     MU_RUN_TEST(subghz_decoder_linear_test); | ||||
|     MU_RUN_TEST(subghz_decoder_linear_delta3_test); | ||||
|     MU_RUN_TEST(subghz_decoder_megacode_test); | ||||
|     MU_RUN_TEST(subghz_decoder_secplus_v1_test); | ||||
|     MU_RUN_TEST(subghz_decoder_secplus_v2_test); | ||||
| @ -788,6 +842,10 @@ MU_TEST_SUITE(subghz) { | ||||
|     MU_RUN_TEST(subghz_decoder_ansonic_test); | ||||
|     MU_RUN_TEST(subghz_decoder_smc5326_test); | ||||
|     MU_RUN_TEST(subghz_decoder_holtek_ht12x_test); | ||||
|     MU_RUN_TEST(subghz_decoder_dooya_test); | ||||
|     MU_RUN_TEST(subghz_decoder_alutech_at_4n_test); | ||||
|     MU_RUN_TEST(subghz_decoder_nice_one_test); | ||||
|     MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test); | ||||
| 
 | ||||
|     MU_RUN_TEST(subghz_encoder_princeton_test); | ||||
|     MU_RUN_TEST(subghz_encoder_came_test); | ||||
| @ -796,6 +854,7 @@ MU_TEST_SUITE(subghz) { | ||||
|     MU_RUN_TEST(subghz_encoder_nice_flo_test); | ||||
|     MU_RUN_TEST(subghz_encoder_keelog_test); | ||||
|     MU_RUN_TEST(subghz_encoder_linear_test); | ||||
|     MU_RUN_TEST(subghz_encoder_linear_delta3_test); | ||||
|     MU_RUN_TEST(subghz_encoder_megacode_test); | ||||
|     MU_RUN_TEST(subghz_encoder_holtek_test); | ||||
|     MU_RUN_TEST(subghz_encoder_secplus_v1_test); | ||||
| @ -812,6 +871,7 @@ MU_TEST_SUITE(subghz) { | ||||
|     MU_RUN_TEST(subghz_encoder_ansonic_test); | ||||
|     MU_RUN_TEST(subghz_encoder_smc5326_test); | ||||
|     MU_RUN_TEST(subghz_encoder_holtek_ht12x_test); | ||||
|     MU_RUN_TEST(subghz_encoder_dooya_test); | ||||
| 
 | ||||
|     MU_RUN_TEST(subghz_random_test); | ||||
|     subghz_test_deinit(); | ||||
|  | ||||
| @ -70,7 +70,7 @@ void minunit_print_progress() { | ||||
| } | ||||
| 
 | ||||
| void minunit_print_fail(const char* str) { | ||||
|     printf(FURI_LOG_CLR_E "%s\r\n" FURI_LOG_CLR_RESET, str); | ||||
|     printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str); | ||||
| } | ||||
| 
 | ||||
| void unit_tests_cli(Cli* cli, FuriString* args, void* context) { | ||||
|  | ||||
							
								
								
									
										44
									
								
								applications/examples/example_thermo/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,44 @@ | ||||
| # 1-Wire Thermometer | ||||
| This example application demonstrates the use of the 1-Wire library with a DS18B20 thermometer.  | ||||
| It also covers basic GUI, input handling, threads and localisation. | ||||
| 
 | ||||
| ## Electrical connections | ||||
| Before launching the application, connect the sensor to Flipper's external GPIO according to the table below: | ||||
| | DS18B20 | Flipper | | ||||
| | :-----: | :-----: | | ||||
| | VDD | 9 | | ||||
| | GND | 18 | | ||||
| | DQ  | 17 | | ||||
| 
 | ||||
| *NOTE 1*: GND is also available on pins 8 and 11. | ||||
| 
 | ||||
| *NOTE 2*: For any other pin than 17, connect an external 4.7k pull-up resistor to pin 9. | ||||
| 
 | ||||
| ## Launching the application | ||||
| In order to launch this demo, follow the steps below: | ||||
| 1. Make sure your Flipper has an SD card installed. | ||||
| 2. Connect your Flipper to the computer via a USB cable. | ||||
| 3. Run `./fbt launch_app APPSRC=example_thermo` in your terminal emulator of choice. | ||||
| 
 | ||||
| ## Changing the data pin | ||||
| It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below: | ||||
| 
 | ||||
| ```c | ||||
| /* Possible GPIO pin choices: | ||||
|  - gpio_ext_pc0 | ||||
|  - gpio_ext_pc1 | ||||
|  - gpio_ext_pc3 | ||||
|  - gpio_ext_pb2 | ||||
|  - gpio_ext_pb3 | ||||
|  - gpio_ext_pa4 | ||||
|  - gpio_ext_pa6 | ||||
|  - gpio_ext_pa7 | ||||
|  - ibutton_gpio | ||||
| */ | ||||
| 
 | ||||
| #define THERMO_GPIO_PIN (ibutton_gpio) | ||||
| ``` | ||||
| Do not forget about the external pull-up resistor as these pins do not have one built-in. | ||||
| 
 | ||||
| With the changes been made, recompile and launch the application again.  | ||||
| The on-screen text should reflect it by asking to connect the thermometer to another pin. | ||||
							
								
								
									
										10
									
								
								applications/examples/example_thermo/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| App( | ||||
|     appid="example_thermo", | ||||
|     name="Example: Thermometer", | ||||
|     apptype=FlipperAppType.EXTERNAL, | ||||
|     entry_point="example_thermo_main", | ||||
|     requires=["gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     fap_icon="example_thermo_10px.png", | ||||
|     fap_category="Examples", | ||||
| ) | ||||
							
								
								
									
										356
									
								
								applications/examples/example_thermo/example_thermo.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,356 @@ | ||||
| /*
 | ||||
|  * This file contains an example application that reads and displays | ||||
|  * the temperature from a DS18B20 1-wire thermometer. | ||||
|  * | ||||
|  * It also covers basic GUI, input handling, threads and localisation. | ||||
|  * | ||||
|  * References: | ||||
|  * [1] DS18B20 Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/DS18B20.pdf
 | ||||
|  */ | ||||
| 
 | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view_port.h> | ||||
| 
 | ||||
| #include <core/thread.h> | ||||
| #include <core/kernel.h> | ||||
| 
 | ||||
| #include <locale/locale.h> | ||||
| 
 | ||||
| #include <one_wire/maxim_crc.h> | ||||
| #include <one_wire/one_wire_host.h> | ||||
| 
 | ||||
| #define UPDATE_PERIOD_MS 1000UL | ||||
| #define TEXT_STORE_SIZE 64U | ||||
| 
 | ||||
| #define DS18B20_CMD_CONVERT 0x44U | ||||
| #define DS18B20_CMD_READ_SCRATCHPAD 0xbeU | ||||
| 
 | ||||
| #define DS18B20_CFG_RESOLUTION_POS 5U | ||||
| #define DS18B20_CFG_RESOLUTION_MASK 0x03U | ||||
| #define DS18B20_DECIMAL_PART_MASK 0x0fU | ||||
| 
 | ||||
| #define DS18B20_SIGN_MASK 0xf0U | ||||
| 
 | ||||
| /* Possible GPIO pin choices:
 | ||||
|  - gpio_ext_pc0 | ||||
|  - gpio_ext_pc1 | ||||
|  - gpio_ext_pc3 | ||||
|  - gpio_ext_pb2 | ||||
|  - gpio_ext_pb3 | ||||
|  - gpio_ext_pa4 | ||||
|  - gpio_ext_pa6 | ||||
|  - gpio_ext_pa7 | ||||
|  - ibutton_gpio | ||||
| */ | ||||
| 
 | ||||
| #define THERMO_GPIO_PIN (ibutton_gpio) | ||||
| 
 | ||||
| /* Flags which the reader thread responds to */ | ||||
| typedef enum { | ||||
|     ReaderThreadFlagExit = 1, | ||||
| } ReaderThreadFlag; | ||||
| 
 | ||||
| typedef union { | ||||
|     struct { | ||||
|         uint8_t temp_lsb; /* Least significant byte of the temperature */ | ||||
|         uint8_t temp_msb; /* Most significant byte of the temperature */ | ||||
|         uint8_t user_alarm_high; /* User register 1 (Temp high alarm) */ | ||||
|         uint8_t user_alarm_low; /* User register 2 (Temp low alarm) */ | ||||
|         uint8_t config; /* Configuration register */ | ||||
|         uint8_t reserved[3]; /* Not used */ | ||||
|         uint8_t crc; /* CRC checksum for error detection */ | ||||
|     } fields; | ||||
|     uint8_t bytes[9]; | ||||
| } DS18B20Scratchpad; | ||||
| 
 | ||||
| /* Application context structure */ | ||||
| typedef struct { | ||||
|     Gui* gui; | ||||
|     ViewPort* view_port; | ||||
|     FuriThread* reader_thread; | ||||
|     FuriMessageQueue* event_queue; | ||||
|     OneWireHost* onewire; | ||||
|     float temp_celsius; | ||||
|     bool has_device; | ||||
| } ExampleThermoContext; | ||||
| 
 | ||||
| /*************** 1-Wire Communication and Processing *****************/ | ||||
| 
 | ||||
| /* Commands the thermometer to begin measuring the temperature. */ | ||||
| static void example_thermo_request_temperature(ExampleThermoContext* context) { | ||||
|     OneWireHost* onewire = context->onewire; | ||||
| 
 | ||||
|     /* All 1-wire transactions must happen in a critical section, i.e
 | ||||
|        not interrupted by other threads. */ | ||||
|     FURI_CRITICAL_ENTER(); | ||||
| 
 | ||||
|     bool success = false; | ||||
|     do { | ||||
|         /* Each communication with a 1-wire device starts by a reset.
 | ||||
|            The functon will return true if a device responded with a presence pulse. */ | ||||
|         if(!onewire_host_reset(onewire)) break; | ||||
|         /* After the reset, a ROM operation must follow.
 | ||||
|            If there is only one device connected, the "Skip ROM" command is most appropriate | ||||
|            (it can also be used to address all of the connected devices in some cases).*/ | ||||
|         onewire_host_skip(onewire); | ||||
|         /* After the ROM operation, a device-specific command is issued.
 | ||||
|            In this case, it's a request to start measuring the temperature. */ | ||||
|         onewire_host_write(onewire, DS18B20_CMD_CONVERT); | ||||
| 
 | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     context->has_device = success; | ||||
| 
 | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| 
 | ||||
| /* Reads the measured temperature from the thermometer. */ | ||||
| static void example_thermo_read_temperature(ExampleThermoContext* context) { | ||||
|     /* If there was no device detected, don't try to read the temperature */ | ||||
|     if(!context->has_device) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     OneWireHost* onewire = context->onewire; | ||||
| 
 | ||||
|     /* All 1-wire transactions must happen in a critical section, i.e
 | ||||
|        not interrupted by other threads. */ | ||||
|     FURI_CRITICAL_ENTER(); | ||||
| 
 | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         DS18B20Scratchpad buf; | ||||
| 
 | ||||
|         /* Attempt reading the temperature 10 times before giving up */ | ||||
|         size_t attempts_left = 10; | ||||
|         do { | ||||
|             /* Each communication with a 1-wire device starts by a reset.
 | ||||
|             The functon will return true if a device responded with a presence pulse. */ | ||||
|             if(!onewire_host_reset(onewire)) continue; | ||||
| 
 | ||||
|             /* After the reset, a ROM operation must follow.
 | ||||
|             If there is only one device connected, the "Skip ROM" command is most appropriate | ||||
|             (it can also be used to address all of the connected devices in some cases).*/ | ||||
|             onewire_host_skip(onewire); | ||||
| 
 | ||||
|             /* After the ROM operation, a device-specific command is issued.
 | ||||
|             This time, it will be the "Read Scratchpad" command which will | ||||
|             prepare the device's internal buffer memory for reading. */ | ||||
|             onewire_host_write(onewire, DS18B20_CMD_READ_SCRATCHPAD); | ||||
| 
 | ||||
|             /* The actual reading happens here. A total of 9 bytes is read. */ | ||||
|             onewire_host_read_bytes(onewire, buf.bytes, sizeof(buf.bytes)); | ||||
| 
 | ||||
|             /* Calculate the checksum and compare it with one provided by the device. */ | ||||
|             const uint8_t crc = maxim_crc8(buf.bytes, sizeof(buf.bytes) - 1, MAXIM_CRC8_INIT); | ||||
| 
 | ||||
|             /* Checksums match, exit the loop */ | ||||
|             if(crc == buf.fields.crc) break; | ||||
| 
 | ||||
|         } while(--attempts_left); | ||||
| 
 | ||||
|         if(attempts_left == 0) break; | ||||
| 
 | ||||
|         /* Get the measurement resolution from the configuration register. (See [1] page 9) */ | ||||
|         const uint8_t resolution_mode = (buf.fields.config >> DS18B20_CFG_RESOLUTION_POS) & | ||||
|                                         DS18B20_CFG_RESOLUTION_MASK; | ||||
| 
 | ||||
|         /* Generate a mask for undefined bits in the decimal part. (See [1] page 6) */ | ||||
|         const uint8_t decimal_mask = | ||||
|             (DS18B20_DECIMAL_PART_MASK << (DS18B20_CFG_RESOLUTION_MASK - resolution_mode)) & | ||||
|             DS18B20_DECIMAL_PART_MASK; | ||||
| 
 | ||||
|         /* Get the integer and decimal part of the temperature (See [1] page 6) */ | ||||
|         const uint8_t integer_part = (buf.fields.temp_msb << 4U) | (buf.fields.temp_lsb >> 4U); | ||||
|         const uint8_t decimal_part = buf.fields.temp_lsb & decimal_mask; | ||||
| 
 | ||||
|         /* Calculate the sign of the temperature (See [1] page 6) */ | ||||
|         const bool is_negative = (buf.fields.temp_msb & DS18B20_SIGN_MASK) != 0; | ||||
| 
 | ||||
|         /* Combine the integer and decimal part together */ | ||||
|         const float temp_celsius_abs = integer_part + decimal_part / 16.f; | ||||
| 
 | ||||
|         /* Set the appropriate sign */ | ||||
|         context->temp_celsius = is_negative ? -temp_celsius_abs : temp_celsius_abs; | ||||
| 
 | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     context->has_device = success; | ||||
| 
 | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| 
 | ||||
| /* Periodically requests measurements and reads temperature. This function runs in a separare thread. */ | ||||
| static int32_t example_thermo_reader_thread_callback(void* ctx) { | ||||
|     ExampleThermoContext* context = ctx; | ||||
| 
 | ||||
|     for(;;) { | ||||
|         /* Tell the termometer to start measuring the temperature. The process may take up to 750ms. */ | ||||
|         example_thermo_request_temperature(context); | ||||
| 
 | ||||
|         /* Wait for the measurement to finish. At the same time wait for an exit signal. */ | ||||
|         const uint32_t flags = | ||||
|             furi_thread_flags_wait(ReaderThreadFlagExit, FuriFlagWaitAny, UPDATE_PERIOD_MS); | ||||
| 
 | ||||
|         /* If an exit signal was received, return from this thread. */ | ||||
|         if(flags != (unsigned)FuriFlagErrorTimeout) break; | ||||
| 
 | ||||
|         /* The measurement is now ready, read it from the termometer. */ | ||||
|         example_thermo_read_temperature(context); | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /*************** GUI, Input and Main Loop *****************/ | ||||
| 
 | ||||
| /* Draw the GUI of the application. The screen is completely redrawn during each call. */ | ||||
| static void example_thermo_draw_callback(Canvas* canvas, void* ctx) { | ||||
|     ExampleThermoContext* context = ctx; | ||||
|     char text_store[TEXT_STORE_SIZE]; | ||||
|     const size_t middle_x = canvas_width(canvas) / 2U; | ||||
| 
 | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str_aligned(canvas, middle_x, 12, AlignCenter, AlignBottom, "Thermometer Demo"); | ||||
|     canvas_draw_line(canvas, 0, 16, 128, 16); | ||||
| 
 | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     canvas_draw_str_aligned( | ||||
|         canvas, middle_x, 30, AlignCenter, AlignBottom, "Connnect thermometer"); | ||||
| 
 | ||||
|     snprintf( | ||||
|         text_store, | ||||
|         TEXT_STORE_SIZE, | ||||
|         "to GPIO pin %ld", | ||||
|         furi_hal_resources_get_ext_pin_number(&THERMO_GPIO_PIN)); | ||||
|     canvas_draw_str_aligned(canvas, middle_x, 42, AlignCenter, AlignBottom, text_store); | ||||
| 
 | ||||
|     canvas_set_font(canvas, FontKeyboard); | ||||
| 
 | ||||
|     if(context->has_device) { | ||||
|         float temp; | ||||
|         char temp_units; | ||||
| 
 | ||||
|         /* The applicaton is locale-aware.
 | ||||
|            Change Settings->System->Units to check it out. */ | ||||
|         switch(locale_get_measurement_unit()) { | ||||
|         case LocaleMeasurementUnitsMetric: | ||||
|             temp = context->temp_celsius; | ||||
|             temp_units = 'C'; | ||||
|             break; | ||||
|         case LocaleMeasurementUnitsImperial: | ||||
|             temp = locale_celsius_to_fahrenheit(context->temp_celsius); | ||||
|             temp_units = 'F'; | ||||
|             break; | ||||
|         default: | ||||
|             furi_crash("Illegal measurement units"); | ||||
|         } | ||||
|         /* If a reading is available, display it */ | ||||
|         snprintf(text_store, TEXT_STORE_SIZE, "Temperature: %+.1f%c", (double)temp, temp_units); | ||||
|     } else { | ||||
|         /* Or show a message that no data is available */ | ||||
|         strncpy(text_store, "-- No data --", TEXT_STORE_SIZE); | ||||
|     } | ||||
| 
 | ||||
|     canvas_draw_str_aligned(canvas, middle_x, 58, AlignCenter, AlignBottom, text_store); | ||||
| } | ||||
| 
 | ||||
| /* This function is called from the GUI thread. All it does is put the event
 | ||||
|    into the application's queue so it can be processed later. */ | ||||
| static void example_thermo_input_callback(InputEvent* event, void* ctx) { | ||||
|     ExampleThermoContext* context = ctx; | ||||
|     furi_message_queue_put(context->event_queue, event, FuriWaitForever); | ||||
| } | ||||
| 
 | ||||
| /* Starts the reader thread and handles the input */ | ||||
| static void example_thermo_run(ExampleThermoContext* context) { | ||||
|     /* Configure the hardware in host mode */ | ||||
|     onewire_host_start(context->onewire); | ||||
| 
 | ||||
|     /* Start the reader thread. It will talk to the thermometer in the background. */ | ||||
|     furi_thread_start(context->reader_thread); | ||||
| 
 | ||||
|     /* An endless loop which handles the input*/ | ||||
|     for(bool is_running = true; is_running;) { | ||||
|         InputEvent event; | ||||
|         /* Wait for an input event. Input events come from the GUI thread via a callback. */ | ||||
|         const FuriStatus status = | ||||
|             furi_message_queue_get(context->event_queue, &event, FuriWaitForever); | ||||
| 
 | ||||
|         /* This application is only interested in short button presses. */ | ||||
|         if((status != FuriStatusOk) || (event.type != InputTypeShort)) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         /* When the user presses the "Back" button, break the loop and exit the application. */ | ||||
|         if(event.key == InputKeyBack) { | ||||
|             is_running = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* Signal the reader thread to cease operation and exit */ | ||||
|     furi_thread_flags_set(furi_thread_get_id(context->reader_thread), ReaderThreadFlagExit); | ||||
| 
 | ||||
|     /* Wait for the reader thread to finish */ | ||||
|     furi_thread_join(context->reader_thread); | ||||
| 
 | ||||
|     /* Reset the hardware */ | ||||
|     onewire_host_stop(context->onewire); | ||||
| } | ||||
| 
 | ||||
| /******************** Initialisation & startup *****************************/ | ||||
| 
 | ||||
| /* Allocate the memory and initialise the variables */ | ||||
| static ExampleThermoContext* example_thermo_context_alloc() { | ||||
|     ExampleThermoContext* context = malloc(sizeof(ExampleThermoContext)); | ||||
| 
 | ||||
|     context->view_port = view_port_alloc(); | ||||
|     view_port_draw_callback_set(context->view_port, example_thermo_draw_callback, context); | ||||
|     view_port_input_callback_set(context->view_port, example_thermo_input_callback, context); | ||||
| 
 | ||||
|     context->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); | ||||
| 
 | ||||
|     context->reader_thread = furi_thread_alloc(); | ||||
|     furi_thread_set_stack_size(context->reader_thread, 1024U); | ||||
|     furi_thread_set_context(context->reader_thread, context); | ||||
|     furi_thread_set_callback(context->reader_thread, example_thermo_reader_thread_callback); | ||||
| 
 | ||||
|     context->gui = furi_record_open(RECORD_GUI); | ||||
|     gui_add_view_port(context->gui, context->view_port, GuiLayerFullscreen); | ||||
| 
 | ||||
|     context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN); | ||||
| 
 | ||||
|     return context; | ||||
| } | ||||
| 
 | ||||
| /* Release the unused resources and deallocate memory */ | ||||
| static void example_thermo_context_free(ExampleThermoContext* context) { | ||||
|     view_port_enabled_set(context->view_port, false); | ||||
|     gui_remove_view_port(context->gui, context->view_port); | ||||
| 
 | ||||
|     onewire_host_free(context->onewire); | ||||
|     furi_thread_free(context->reader_thread); | ||||
|     furi_message_queue_free(context->event_queue); | ||||
|     view_port_free(context->view_port); | ||||
| 
 | ||||
|     furi_record_close(RECORD_GUI); | ||||
| } | ||||
| 
 | ||||
| /* The application's entry point. Execution starts from here. */ | ||||
| int32_t example_thermo_main(void* p) { | ||||
|     UNUSED(p); | ||||
| 
 | ||||
|     /* Allocate all of the necessary structures */ | ||||
|     ExampleThermoContext* context = example_thermo_context_alloc(); | ||||
| 
 | ||||
|     /* Start the applicaton's main loop. It won't return until the application was requested to exit. */ | ||||
|     example_thermo_run(context); | ||||
| 
 | ||||
|     /* Release all unneeded resources */ | ||||
|     example_thermo_context_free(context); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								applications/examples/example_thermo/example_thermo_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.1 KiB | 
| @ -1,10 +1,11 @@ | ||||
| #include <archive/views/archive_browser_view.h> | ||||
| #include "archive_files.h" | ||||
| #include "archive_apps.h" | ||||
| #include "archive_browser.h" | ||||
| #include "../views/archive_browser_view.h" | ||||
| 
 | ||||
| #include <core/common_defines.h> | ||||
| #include <core/log.h> | ||||
| #include "gui/modules/file_browser_worker.h" | ||||
| #include <gui/modules/file_browser_worker.h> | ||||
| #include <fap_loader/fap_loader_app.h> | ||||
| #include <math.h> | ||||
| 
 | ||||
|  | ||||
| @ -1,14 +1,15 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "../helpers/archive_files.h" | ||||
| #include "../helpers/archive_favorites.h" | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include <gui/modules/file_browser_worker.h> | ||||
| #include <storage/storage.h> | ||||
| #include "../helpers/archive_files.h" | ||||
| #include "../helpers/archive_favorites.h" | ||||
| #include "gui/modules/file_browser_worker.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define MAX_LEN_PX 110 | ||||
| #define MAX_NAME_LEN 255 | ||||
|  | ||||
| @ -1,9 +1,12 @@ | ||||
| #include "bad_usb_app_i.h" | ||||
| #include "bad_usb_settings_filename.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <storage/storage.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| 
 | ||||
| #define BAD_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/" BAD_USB_SETTINGS_FILE_NAME | ||||
| 
 | ||||
| static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) { | ||||
|     furi_assert(context); | ||||
|     BadUsbApp* app = context; | ||||
| @ -22,15 +25,62 @@ static void bad_usb_app_tick_event_callback(void* context) { | ||||
|     scene_manager_handle_tick_event(app->scene_manager); | ||||
| } | ||||
| 
 | ||||
| static void bad_usb_load_settings(BadUsbApp* app) { | ||||
|     File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); | ||||
|     if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         char chr; | ||||
|         while((storage_file_read(settings_file, &chr, 1) == 1) && | ||||
|               !storage_file_eof(settings_file) && !isspace(chr)) { | ||||
|             furi_string_push_back(app->keyboard_layout, chr); | ||||
|         } | ||||
|     } else { | ||||
|         furi_string_reset(app->keyboard_layout); | ||||
|     } | ||||
|     storage_file_close(settings_file); | ||||
|     storage_file_free(settings_file); | ||||
| 
 | ||||
|     if(!furi_string_empty(app->keyboard_layout)) { | ||||
|         Storage* fs_api = furi_record_open(RECORD_STORAGE); | ||||
|         FileInfo layout_file_info; | ||||
|         FS_Error file_check_err = storage_common_stat( | ||||
|             fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); | ||||
|         furi_record_close(RECORD_STORAGE); | ||||
|         if(file_check_err != FSE_OK) { | ||||
|             furi_string_reset(app->keyboard_layout); | ||||
|             return; | ||||
|         } | ||||
|         if(layout_file_info.size != 256) { | ||||
|             furi_string_reset(app->keyboard_layout); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void bad_usb_save_settings(BadUsbApp* app) { | ||||
|     File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); | ||||
|     if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||
|         storage_file_write( | ||||
|             settings_file, | ||||
|             furi_string_get_cstr(app->keyboard_layout), | ||||
|             furi_string_size(app->keyboard_layout)); | ||||
|         storage_file_write(settings_file, "\n", 1); | ||||
|     } | ||||
|     storage_file_close(settings_file); | ||||
|     storage_file_free(settings_file); | ||||
| } | ||||
| 
 | ||||
| BadUsbApp* bad_usb_app_alloc(char* arg) { | ||||
|     BadUsbApp* app = malloc(sizeof(BadUsbApp)); | ||||
| 
 | ||||
|     app->file_path = furi_string_alloc(); | ||||
|     app->bad_usb_script = NULL; | ||||
| 
 | ||||
|     app->file_path = furi_string_alloc(); | ||||
|     app->keyboard_layout = furi_string_alloc(); | ||||
|     if(arg && strlen(arg)) { | ||||
|         furi_string_set(app->file_path, arg); | ||||
|     } | ||||
| 
 | ||||
|     bad_usb_load_settings(app); | ||||
| 
 | ||||
|     app->gui = furi_record_open(RECORD_GUI); | ||||
|     app->notifications = furi_record_open(RECORD_NOTIFICATION); | ||||
|     app->dialogs = furi_record_open(RECORD_DIALOGS); | ||||
| @ -53,6 +103,10 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget)); | ||||
| 
 | ||||
|     app->submenu = submenu_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, BadUsbAppViewConfig, submenu_get_view(app->submenu)); | ||||
| 
 | ||||
|     app->bad_usb_view = bad_usb_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view)); | ||||
| @ -61,12 +115,18 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { | ||||
| 
 | ||||
|     if(furi_hal_usb_is_locked()) { | ||||
|         app->error = BadUsbAppErrorCloseRpc; | ||||
|         app->usb_if_prev = NULL; | ||||
|         scene_manager_next_scene(app->scene_manager, BadUsbSceneError); | ||||
|     } else { | ||||
|         app->usb_if_prev = furi_hal_usb_get_config(); | ||||
|         furi_check(furi_hal_usb_set_config(NULL, NULL)); | ||||
| 
 | ||||
|         if(!furi_string_empty(app->file_path)) { | ||||
|             app->bad_usb_script = bad_usb_script_open(app->file_path); | ||||
|             bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); | ||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); | ||||
|         } else { | ||||
|             furi_string_set(app->file_path, BAD_USB_APP_PATH_FOLDER); | ||||
|             furi_string_set(app->file_path, BAD_USB_APP_BASE_FOLDER); | ||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); | ||||
|         } | ||||
|     } | ||||
| @ -77,6 +137,15 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { | ||||
| void bad_usb_app_free(BadUsbApp* app) { | ||||
|     furi_assert(app); | ||||
| 
 | ||||
|     if(app->bad_usb_script) { | ||||
|         bad_usb_script_close(app->bad_usb_script); | ||||
|         app->bad_usb_script = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if(app->usb_if_prev) { | ||||
|         furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL)); | ||||
|     } | ||||
| 
 | ||||
|     // Views
 | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); | ||||
|     bad_usb_free(app->bad_usb_view); | ||||
| @ -85,6 +154,10 @@ void bad_usb_app_free(BadUsbApp* app) { | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError); | ||||
|     widget_free(app->widget); | ||||
| 
 | ||||
|     // Submenu
 | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); | ||||
|     submenu_free(app->submenu); | ||||
| 
 | ||||
|     // View dispatcher
 | ||||
|     view_dispatcher_free(app->view_dispatcher); | ||||
|     scene_manager_free(app->scene_manager); | ||||
| @ -94,7 +167,10 @@ void bad_usb_app_free(BadUsbApp* app) { | ||||
|     furi_record_close(RECORD_NOTIFICATION); | ||||
|     furi_record_close(RECORD_DIALOGS); | ||||
| 
 | ||||
|     bad_usb_save_settings(app); | ||||
| 
 | ||||
|     furi_string_free(app->file_path); | ||||
|     furi_string_free(app->keyboard_layout); | ||||
| 
 | ||||
|     free(app); | ||||
| } | ||||
|  | ||||
| @ -14,9 +14,12 @@ | ||||
| #include <gui/modules/variable_item_list.h> | ||||
| #include <gui/modules/widget.h> | ||||
| #include "views/bad_usb_view.h" | ||||
| #include <furi_hal_usb.h> | ||||
| 
 | ||||
| #define BAD_USB_APP_PATH_FOLDER ANY_PATH("badusb") | ||||
| #define BAD_USB_APP_EXTENSION ".txt" | ||||
| #define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb") | ||||
| #define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/assets/layouts" | ||||
| #define BAD_USB_APP_SCRIPT_EXTENSION ".txt" | ||||
| #define BAD_USB_APP_LAYOUT_EXTENSION ".kl" | ||||
| 
 | ||||
| typedef enum { | ||||
|     BadUsbAppErrorNoFiles, | ||||
| @ -30,14 +33,19 @@ struct BadUsbApp { | ||||
|     NotificationApp* notifications; | ||||
|     DialogsApp* dialogs; | ||||
|     Widget* widget; | ||||
|     Submenu* submenu; | ||||
| 
 | ||||
|     BadUsbAppError error; | ||||
|     FuriString* file_path; | ||||
|     FuriString* keyboard_layout; | ||||
|     BadUsb* bad_usb_view; | ||||
|     BadUsbScript* bad_usb_script; | ||||
| 
 | ||||
|     FuriHalUsbInterface* usb_if_prev; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     BadUsbAppViewError, | ||||
|     BadUsbAppViewWork, | ||||
|     BadUsbAppViewConfig, | ||||
| } BadUsbAppView; | ||||
| @ -16,6 +16,9 @@ | ||||
| #define SCRIPT_STATE_END (-2) | ||||
| #define SCRIPT_STATE_NEXT_LINE (-3) | ||||
| 
 | ||||
| #define BADUSB_ASCII_TO_KEY(script, x) \ | ||||
|     (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) | ||||
| 
 | ||||
| typedef enum { | ||||
|     WorkerEvtToggle = (1 << 0), | ||||
|     WorkerEvtEnd = (1 << 1), | ||||
| @ -28,6 +31,7 @@ struct BadUsbScript { | ||||
|     BadUsbState st; | ||||
|     FuriString* file_path; | ||||
|     uint32_t defdelay; | ||||
|     uint16_t layout[128]; | ||||
|     FuriThread* thread; | ||||
|     uint8_t file_buf[FILE_BUFFER_LEN + 1]; | ||||
|     uint8_t buf_start; | ||||
| @ -205,10 +209,10 @@ static bool ducky_altstring(const char* param) { | ||||
|     return state; | ||||
| } | ||||
| 
 | ||||
| static bool ducky_string(const char* param) { | ||||
| static bool ducky_string(BadUsbScript* bad_usb, const char* param) { | ||||
|     uint32_t i = 0; | ||||
|     while(param[i] != '\0') { | ||||
|         uint16_t keycode = HID_ASCII_TO_KEY(param[i]); | ||||
|         uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); | ||||
|         if(keycode != HID_KEYBOARD_NONE) { | ||||
|             furi_hal_hid_kb_press(keycode); | ||||
|             furi_hal_hid_kb_release(keycode); | ||||
| @ -218,7 +222,7 @@ static bool ducky_string(const char* param) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { | ||||
| static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { | ||||
|     for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { | ||||
|         size_t key_cmd_len = strlen(ducky_keys[i].name); | ||||
|         if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && | ||||
| @ -227,7 +231,7 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) { | ||||
|         } | ||||
|     } | ||||
|     if((accept_chars) && (strlen(param) > 0)) { | ||||
|         return (HID_ASCII_TO_KEY(param[0]) & 0xFF); | ||||
|         return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| @ -276,7 +280,7 @@ static int32_t | ||||
|     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { | ||||
|         // STRING
 | ||||
|         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||
|         state = ducky_string(line_tmp); | ||||
|         state = ducky_string(bad_usb, line_tmp); | ||||
|         if(!state && error != NULL) { | ||||
|             snprintf(error, error_len, "Invalid string %s", line_tmp); | ||||
|         } | ||||
| @ -312,14 +316,14 @@ static int32_t | ||||
|     } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { | ||||
|         // SYSRQ
 | ||||
|         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||
|         uint16_t key = ducky_get_keycode(line_tmp, true); | ||||
|         uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true); | ||||
|         furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); | ||||
|         furi_hal_hid_kb_press(key); | ||||
|         furi_hal_hid_kb_release_all(); | ||||
|         return (0); | ||||
|     } else { | ||||
|         // Special keys + modifiers
 | ||||
|         uint16_t key = ducky_get_keycode(line_tmp, false); | ||||
|         uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false); | ||||
|         if(key == HID_KEYBOARD_NONE) { | ||||
|             if(error != NULL) { | ||||
|                 snprintf(error, error_len, "No keycode defined for %s", line_tmp); | ||||
| @ -329,7 +333,7 @@ static int32_t | ||||
|         if((key & 0xFF00) != 0) { | ||||
|             // It's a modifier key
 | ||||
|             line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||
|             key |= ducky_get_keycode(line_tmp, true); | ||||
|             key |= ducky_get_keycode(bad_usb, line_tmp, true); | ||||
|         } | ||||
|         furi_hal_hid_kb_press(key); | ||||
|         furi_hal_hid_kb_release(key); | ||||
| @ -486,8 +490,6 @@ static int32_t bad_usb_worker(void* context) { | ||||
|     BadUsbWorkerState worker_state = BadUsbStateInit; | ||||
|     int32_t delay_val = 0; | ||||
| 
 | ||||
|     FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); | ||||
| 
 | ||||
|     FURI_LOG_I(WORKER_TAG, "Init"); | ||||
|     File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); | ||||
|     bad_usb->line = furi_string_alloc(); | ||||
| @ -638,8 +640,6 @@ static int32_t bad_usb_worker(void* context) { | ||||
| 
 | ||||
|     furi_hal_hid_set_state_callback(NULL, NULL); | ||||
| 
 | ||||
|     furi_hal_usb_set_config(usb_mode_prev, NULL); | ||||
| 
 | ||||
|     storage_file_close(script_file); | ||||
|     storage_file_free(script_file); | ||||
|     furi_string_free(bad_usb->line); | ||||
| @ -650,12 +650,19 @@ static int32_t bad_usb_worker(void* context) { | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
|     memset(bad_usb->layout, HID_KEYBOARD_NONE, sizeof(bad_usb->layout)); | ||||
|     memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); | ||||
| } | ||||
| 
 | ||||
| BadUsbScript* bad_usb_script_open(FuriString* file_path) { | ||||
|     furi_assert(file_path); | ||||
| 
 | ||||
|     BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); | ||||
|     bad_usb->file_path = furi_string_alloc(); | ||||
|     furi_string_set(bad_usb->file_path, file_path); | ||||
|     bad_usb_script_set_default_keyboard_layout(bad_usb); | ||||
| 
 | ||||
|     bad_usb->st.state = BadUsbStateInit; | ||||
|     bad_usb->st.error[0] = '\0'; | ||||
| @ -674,6 +681,30 @@ void bad_usb_script_close(BadUsbScript* bad_usb) { | ||||
|     free(bad_usb); | ||||
| } | ||||
| 
 | ||||
| void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path) { | ||||
|     furi_assert(bad_usb); | ||||
| 
 | ||||
|     if((bad_usb->st.state == BadUsbStateRunning) || (bad_usb->st.state == BadUsbStateDelay)) { | ||||
|         // do not update keyboard layout while a script is running
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); | ||||
|     if(!furi_string_empty(layout_path)) { //-V1051
 | ||||
|         if(storage_file_open( | ||||
|                layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|             uint16_t layout[128]; | ||||
|             if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { | ||||
|                 memcpy(bad_usb->layout, layout, sizeof(layout)); | ||||
|             } | ||||
|         } | ||||
|         storage_file_close(layout_file); | ||||
|     } else { | ||||
|         bad_usb_script_set_default_keyboard_layout(bad_usb); | ||||
|     } | ||||
|     storage_file_free(layout_file); | ||||
| } | ||||
| 
 | ||||
| void bad_usb_script_toggle(BadUsbScript* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
|     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle); | ||||
|  | ||||
| @ -33,6 +33,8 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path); | ||||
| 
 | ||||
| void bad_usb_script_close(BadUsbScript* bad_usb); | ||||
| 
 | ||||
| void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, FuriString* layout_path); | ||||
| 
 | ||||
| void bad_usb_script_start(BadUsbScript* bad_usb); | ||||
| 
 | ||||
| void bad_usb_script_stop(BadUsbScript* bad_usb); | ||||
|  | ||||
							
								
								
									
										3
									
								
								applications/main/bad_usb/bad_usb_settings_filename.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #define BAD_USB_SETTINGS_FILE_NAME ".badusb.settings" | ||||
							
								
								
									
										53
									
								
								applications/main/bad_usb/scenes/bad_usb_scene_config.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,53 @@ | ||||
| #include "../bad_usb_app_i.h" | ||||
| #include "furi_hal_power.h" | ||||
| #include "furi_hal_usb.h" | ||||
| 
 | ||||
| enum SubmenuIndex { | ||||
|     SubmenuIndexKeyboardLayout, | ||||
| }; | ||||
| 
 | ||||
| void bad_usb_scene_config_submenu_callback(void* context, uint32_t index) { | ||||
|     BadUsbApp* bad_usb = context; | ||||
|     view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index); | ||||
| } | ||||
| 
 | ||||
| void bad_usb_scene_config_on_enter(void* context) { | ||||
|     BadUsbApp* bad_usb = context; | ||||
|     Submenu* submenu = bad_usb->submenu; | ||||
| 
 | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Keyboard layout", | ||||
|         SubmenuIndexKeyboardLayout, | ||||
|         bad_usb_scene_config_submenu_callback, | ||||
|         bad_usb); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         submenu, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig)); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); | ||||
| } | ||||
| 
 | ||||
| bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { | ||||
|     BadUsbApp* bad_usb = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event); | ||||
|         consumed = true; | ||||
|         if(event.event == SubmenuIndexKeyboardLayout) { | ||||
|             scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); | ||||
|         } else { | ||||
|             furi_crash("Unknown key type"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void bad_usb_scene_config_on_exit(void* context) { | ||||
|     BadUsbApp* bad_usb = context; | ||||
|     Submenu* submenu = bad_usb->submenu; | ||||
| 
 | ||||
|     submenu_reset(submenu); | ||||
| } | ||||
| @ -1,3 +1,5 @@ | ||||
| ADD_SCENE(bad_usb, file_select, FileSelect) | ||||
| ADD_SCENE(bad_usb, work, Work) | ||||
| ADD_SCENE(bad_usb, error, Error) | ||||
| ADD_SCENE(bad_usb, config, Config) | ||||
| ADD_SCENE(bad_usb, config_layout, ConfigLayout) | ||||
|  | ||||
| @ -0,0 +1,50 @@ | ||||
| #include "../bad_usb_app_i.h" | ||||
| #include "furi_hal_power.h" | ||||
| #include "furi_hal_usb.h" | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| static bool bad_usb_layout_select(BadUsbApp* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
| 
 | ||||
|     FuriString* predefined_path; | ||||
|     predefined_path = furi_string_alloc(); | ||||
|     if(!furi_string_empty(bad_usb->keyboard_layout)) { | ||||
|         furi_string_set(predefined_path, bad_usb->keyboard_layout); | ||||
|     } else { | ||||
|         furi_string_set(predefined_path, BAD_USB_APP_PATH_LAYOUT_FOLDER); | ||||
|     } | ||||
| 
 | ||||
|     DialogsFileBrowserOptions browser_options; | ||||
|     dialog_file_browser_set_basic_options( | ||||
|         &browser_options, BAD_USB_APP_LAYOUT_EXTENSION, &I_keyboard_10px); | ||||
|     browser_options.base_path = BAD_USB_APP_PATH_LAYOUT_FOLDER; | ||||
|     browser_options.skip_assets = false; | ||||
| 
 | ||||
|     // Input events and views are managed by file_browser
 | ||||
|     bool res = dialog_file_browser_show( | ||||
|         bad_usb->dialogs, bad_usb->keyboard_layout, predefined_path, &browser_options); | ||||
| 
 | ||||
|     furi_string_free(predefined_path); | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| void bad_usb_scene_config_layout_on_enter(void* context) { | ||||
|     BadUsbApp* bad_usb = context; | ||||
| 
 | ||||
|     if(bad_usb_layout_select(bad_usb)) { | ||||
|         bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout); | ||||
|     } | ||||
|     scene_manager_previous_scene(bad_usb->scene_manager); | ||||
| } | ||||
| 
 | ||||
| bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) { | ||||
|     UNUSED(context); | ||||
|     UNUSED(event); | ||||
|     // BadUsbApp* bad_usb = context;
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void bad_usb_scene_config_layout_on_exit(void* context) { | ||||
|     UNUSED(context); | ||||
|     // BadUsbApp* bad_usb = context;
 | ||||
| } | ||||
| @ -1,14 +1,16 @@ | ||||
| #include "../bad_usb_app_i.h" | ||||
| #include "furi_hal_power.h" | ||||
| #include "furi_hal_usb.h" | ||||
| #include <furi_hal_power.h> | ||||
| #include <furi_hal_usb.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| static bool bad_usb_file_select(BadUsbApp* bad_usb) { | ||||
|     furi_assert(bad_usb); | ||||
| 
 | ||||
|     DialogsFileBrowserOptions browser_options; | ||||
|     dialog_file_browser_set_basic_options(&browser_options, BAD_USB_APP_EXTENSION, &I_badusb_10px); | ||||
|     browser_options.base_path = BAD_USB_APP_PATH_FOLDER; | ||||
|     dialog_file_browser_set_basic_options( | ||||
|         &browser_options, BAD_USB_APP_SCRIPT_EXTENSION, &I_badusb_10px); | ||||
|     browser_options.base_path = BAD_USB_APP_BASE_FOLDER; | ||||
|     browser_options.skip_assets = true; | ||||
| 
 | ||||
|     // Input events and views are managed by file_browser
 | ||||
|     bool res = dialog_file_browser_show( | ||||
| @ -21,12 +23,18 @@ void bad_usb_scene_file_select_on_enter(void* context) { | ||||
|     BadUsbApp* bad_usb = context; | ||||
| 
 | ||||
|     furi_hal_usb_disable(); | ||||
|     if(bad_usb->bad_usb_script) { | ||||
|         bad_usb_script_close(bad_usb->bad_usb_script); | ||||
|         bad_usb->bad_usb_script = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if(bad_usb_file_select(bad_usb)) { | ||||
|         bad_usb->bad_usb_script = bad_usb_script_open(bad_usb->file_path); | ||||
|         bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout); | ||||
| 
 | ||||
|         scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork); | ||||
|     } else { | ||||
|         furi_hal_usb_enable(); | ||||
|         //scene_manager_previous_scene(bad_usb->scene_manager);
 | ||||
|         view_dispatcher_stop(bad_usb->view_dispatcher); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,13 +1,13 @@ | ||||
| #include "../bad_usb_script.h" | ||||
| #include "../bad_usb_app_i.h" | ||||
| #include "../views/bad_usb_view.h" | ||||
| #include "furi_hal.h" | ||||
| #include <furi_hal.h> | ||||
| #include "toolbox/path.h" | ||||
| 
 | ||||
| void bad_usb_scene_work_ok_callback(InputType type, void* context) { | ||||
| void bad_usb_scene_work_button_callback(InputKey key, void* context) { | ||||
|     furi_assert(context); | ||||
|     BadUsbApp* app = context; | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, type); | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, key); | ||||
| } | ||||
| 
 | ||||
| bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { | ||||
| @ -15,8 +15,13 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         bad_usb_script_toggle(app->bad_usb_script); | ||||
|         consumed = true; | ||||
|         if(event.event == InputKeyLeft) { | ||||
|             scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); | ||||
|             consumed = true; | ||||
|         } else if(event.event == InputKeyOk) { | ||||
|             bad_usb_script_toggle(app->bad_usb_script); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|         bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script)); | ||||
|     } | ||||
| @ -28,20 +33,22 @@ void bad_usb_scene_work_on_enter(void* context) { | ||||
| 
 | ||||
|     FuriString* file_name; | ||||
|     file_name = furi_string_alloc(); | ||||
| 
 | ||||
|     path_extract_filename(app->file_path, file_name, true); | ||||
|     bad_usb_set_file_name(app->bad_usb_view, furi_string_get_cstr(file_name)); | ||||
|     app->bad_usb_script = bad_usb_script_open(app->file_path); | ||||
| 
 | ||||
|     furi_string_free(file_name); | ||||
| 
 | ||||
|     FuriString* layout; | ||||
|     layout = furi_string_alloc(); | ||||
|     path_extract_filename(app->keyboard_layout, layout, true); | ||||
|     bad_usb_set_layout(app->bad_usb_view, furi_string_get_cstr(layout)); | ||||
|     furi_string_free(layout); | ||||
| 
 | ||||
|     bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script)); | ||||
| 
 | ||||
|     bad_usb_set_ok_callback(app->bad_usb_view, bad_usb_scene_work_ok_callback, app); | ||||
|     bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app); | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork); | ||||
| } | ||||
| 
 | ||||
| void bad_usb_scene_work_on_exit(void* context) { | ||||
|     BadUsbApp* app = context; | ||||
|     bad_usb_script_close(app->bad_usb_script); | ||||
|     UNUSED(context); | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #include "bad_usb_view.h" | ||||
| #include "../bad_usb_script.h" | ||||
| #include <toolbox/path.h> | ||||
| #include <gui/elements.h> | ||||
| #include <assets_icons.h> | ||||
| 
 | ||||
| @ -7,12 +8,13 @@ | ||||
| 
 | ||||
| struct BadUsb { | ||||
|     View* view; | ||||
|     BadUsbOkCallback callback; | ||||
|     BadUsbButtonCallback callback; | ||||
|     void* context; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     char file_name[MAX_NAME_LEN]; | ||||
|     char layout[MAX_NAME_LEN]; | ||||
|     BadUsbState state; | ||||
|     uint8_t anim_frame; | ||||
| } BadUsbModel; | ||||
| @ -25,9 +27,23 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
|     elements_string_fit_width(canvas, disp_str, 128 - 2); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); | ||||
| 
 | ||||
|     if(strlen(model->layout) == 0) { | ||||
|         furi_string_set(disp_str, "(default)"); | ||||
|     } else { | ||||
|         furi_string_reset(disp_str); | ||||
|         furi_string_push_back(disp_str, '('); | ||||
|         for(size_t i = 0; i < strlen(model->layout); i++) | ||||
|             furi_string_push_back(disp_str, model->layout[i]); | ||||
|         furi_string_push_back(disp_str, ')'); | ||||
|     } | ||||
|     elements_string_fit_width(canvas, disp_str, 128 - 2); | ||||
|     canvas_draw_str( | ||||
|         canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); | ||||
| 
 | ||||
|     furi_string_reset(disp_str); | ||||
| 
 | ||||
|     canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22); | ||||
|     canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); | ||||
| 
 | ||||
|     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || | ||||
|        (model->state.state == BadUsbStateNotConnected)) { | ||||
| @ -38,23 +54,28 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
|         elements_button_center(canvas, "Cancel"); | ||||
|     } | ||||
| 
 | ||||
|     if((model->state.state == BadUsbStateNotConnected) || | ||||
|        (model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) { | ||||
|         elements_button_left(canvas, "Config"); | ||||
|     } | ||||
| 
 | ||||
|     if(model->state.state == BadUsbStateNotConnected) { | ||||
|         canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to USB"); | ||||
|     } else if(model->state.state == BadUsbStateWillRun) { | ||||
|         canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Will run"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "on connect"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); | ||||
|     } else if(model->state.state == BadUsbStateFileError) { | ||||
|         canvas_draw_icon(canvas, 4, 22, &I_Error_18x18); | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "File"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "ERROR"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); | ||||
|         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); | ||||
|     } else if(model->state.state == BadUsbStateScriptError) { | ||||
|         canvas_draw_icon(canvas, 4, 22, &I_Error_18x18); | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
| @ -64,49 +85,49 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | ||||
|         furi_string_reset(disp_str); | ||||
|         canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); | ||||
|     } else if(model->state.state == BadUsbStateIdle) { | ||||
|         canvas_draw_icon(canvas, 4, 22, &I_Smile_18x18); | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); | ||||
|         canvas_set_font(canvas, FontBigNumbers); | ||||
|         canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "0"); | ||||
|         canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); | ||||
|         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|     } else if(model->state.state == BadUsbStateRunning) { | ||||
|         if(model->anim_frame == 0) { | ||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21); | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); | ||||
|         } else { | ||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviSmile2_18x21); | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); | ||||
|         } | ||||
|         canvas_set_font(canvas, FontBigNumbers); | ||||
|         furi_string_printf( | ||||
|             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|             canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|         furi_string_reset(disp_str); | ||||
|         canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|     } else if(model->state.state == BadUsbStateDone) { | ||||
|         canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21); | ||||
|         canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); | ||||
|         canvas_set_font(canvas, FontBigNumbers); | ||||
|         canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "100"); | ||||
|         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); | ||||
|         furi_string_reset(disp_str); | ||||
|         canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|     } else if(model->state.state == BadUsbStateDelay) { | ||||
|         if(model->anim_frame == 0) { | ||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviWaiting1_18x21); | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); | ||||
|         } else { | ||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviWaiting2_18x21); | ||||
|             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); | ||||
|         } | ||||
|         canvas_set_font(canvas, FontBigNumbers); | ||||
|         furi_string_printf( | ||||
|             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, 114, 36, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|             canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|         furi_string_reset(disp_str); | ||||
|         canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); | ||||
|         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|         furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|             canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); | ||||
|         furi_string_reset(disp_str); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); | ||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||
|     } | ||||
| 
 | ||||
|     furi_string_free(disp_str); | ||||
| @ -118,10 +139,10 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         if(event->key == InputKeyOk) { | ||||
|         if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { | ||||
|             consumed = true; | ||||
|             furi_assert(bad_usb->callback); | ||||
|             bad_usb->callback(InputTypeShort, bad_usb->context); | ||||
|             bad_usb->callback(event->key, bad_usb->context); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -151,7 +172,7 @@ View* bad_usb_get_view(BadUsb* bad_usb) { | ||||
|     return bad_usb->view; | ||||
| } | ||||
| 
 | ||||
| void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context) { | ||||
| void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context) { | ||||
|     furi_assert(bad_usb); | ||||
|     furi_assert(callback); | ||||
|     with_view_model( | ||||
| @ -174,6 +195,14 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { | ||||
|         true); | ||||
| } | ||||
| 
 | ||||
| void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) { | ||||
|     furi_assert(layout); | ||||
|     with_view_model( | ||||
|         bad_usb->view, | ||||
|         BadUsbModel * model, | ||||
|         { strlcpy(model->layout, layout, MAX_NAME_LEN); }, | ||||
|         true); | ||||
| } | ||||
| void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { | ||||
|     furi_assert(st); | ||||
|     with_view_model( | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| #include "../bad_usb_script.h" | ||||
| 
 | ||||
| typedef struct BadUsb BadUsb; | ||||
| typedef void (*BadUsbOkCallback)(InputType type, void* context); | ||||
| typedef void (*BadUsbButtonCallback)(InputKey key, void* context); | ||||
| 
 | ||||
| BadUsb* bad_usb_alloc(); | ||||
| 
 | ||||
| @ -12,8 +12,10 @@ void bad_usb_free(BadUsb* bad_usb); | ||||
| 
 | ||||
| View* bad_usb_get_view(BadUsb* bad_usb); | ||||
| 
 | ||||
| void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); | ||||
| void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context); | ||||
| 
 | ||||
| void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); | ||||
| 
 | ||||
| void bad_usb_set_layout(BadUsb* bad_usb, const char* layout); | ||||
| 
 | ||||
| void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | ||||
|  | ||||
| @ -25,6 +25,7 @@ GpioApp* gpio_app_alloc() { | ||||
|     GpioApp* app = malloc(sizeof(GpioApp)); | ||||
| 
 | ||||
|     app->gui = furi_record_open(RECORD_GUI); | ||||
|     app->gpio_items = gpio_items_alloc(); | ||||
| 
 | ||||
|     app->view_dispatcher = view_dispatcher_alloc(); | ||||
|     app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); | ||||
| @ -47,7 +48,7 @@ GpioApp* gpio_app_alloc() { | ||||
|         app->view_dispatcher, | ||||
|         GpioAppViewVarItemList, | ||||
|         variable_item_list_get_view(app->var_item_list)); | ||||
|     app->gpio_test = gpio_test_alloc(); | ||||
|     app->gpio_test = gpio_test_alloc(app->gpio_items); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, GpioAppViewGpioTest, gpio_test_get_view(app->gpio_test)); | ||||
| 
 | ||||
| @ -91,6 +92,7 @@ void gpio_app_free(GpioApp* app) { | ||||
|     furi_record_close(RECORD_GUI); | ||||
|     furi_record_close(RECORD_NOTIFICATION); | ||||
| 
 | ||||
|     gpio_items_free(app->gpio_items); | ||||
|     free(app); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "gpio_app.h" | ||||
| #include "gpio_item.h" | ||||
| #include "gpio_items.h" | ||||
| #include "scenes/gpio_scene.h" | ||||
| #include "gpio_custom_event.h" | ||||
| #include "usb_uart_bridge.h" | ||||
| @ -28,6 +28,7 @@ struct GpioApp { | ||||
|     VariableItem* var_item_flow; | ||||
|     GpioTest* gpio_test; | ||||
|     GpioUsbUart* gpio_usb_uart; | ||||
|     GPIOItems* gpio_items; | ||||
|     UsbUartBridge* usb_uart_bridge; | ||||
|     UsbUartConfig* usb_uart_cfg; | ||||
| }; | ||||
|  | ||||
| @ -1,51 +0,0 @@ | ||||
| #include "gpio_item.h" | ||||
| 
 | ||||
| #include <furi_hal_resources.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* name; | ||||
|     const GpioPin* pin; | ||||
| } GpioItem; | ||||
| 
 | ||||
| static const GpioItem gpio_item[GPIO_ITEM_COUNT] = { | ||||
|     {"1.2: PA7", &gpio_ext_pa7}, | ||||
|     {"1.3: PA6", &gpio_ext_pa6}, | ||||
|     {"1.4: PA4", &gpio_ext_pa4}, | ||||
|     {"1.5: PB3", &gpio_ext_pb3}, | ||||
|     {"1.6: PB2", &gpio_ext_pb2}, | ||||
|     {"1.7: PC3", &gpio_ext_pc3}, | ||||
|     {"2.7: PC1", &gpio_ext_pc1}, | ||||
|     {"2.8: PC0", &gpio_ext_pc0}, | ||||
| }; | ||||
| 
 | ||||
| void gpio_item_configure_pin(uint8_t index, GpioMode mode) { | ||||
|     furi_assert(index < GPIO_ITEM_COUNT); | ||||
|     furi_hal_gpio_write(gpio_item[index].pin, false); | ||||
|     furi_hal_gpio_init(gpio_item[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh); | ||||
| } | ||||
| 
 | ||||
| void gpio_item_configure_all_pins(GpioMode mode) { | ||||
|     for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { | ||||
|         gpio_item_configure_pin(i, mode); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void gpio_item_set_pin(uint8_t index, bool level) { | ||||
|     furi_assert(index < GPIO_ITEM_COUNT); | ||||
|     furi_hal_gpio_write(gpio_item[index].pin, level); | ||||
| } | ||||
| 
 | ||||
| void gpio_item_set_all_pins(bool level) { | ||||
|     for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { | ||||
|         gpio_item_set_pin(i, level); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char* gpio_item_get_pin_name(uint8_t index) { | ||||
|     furi_assert(index < GPIO_ITEM_COUNT + 1); | ||||
|     if(index == GPIO_ITEM_COUNT) { | ||||
|         return "ALL"; | ||||
|     } else { | ||||
|         return gpio_item[index].name; | ||||
|     } | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi_hal_gpio.h> | ||||
| 
 | ||||
| #define GPIO_ITEM_COUNT 8 | ||||
| 
 | ||||
| void gpio_item_configure_pin(uint8_t index, GpioMode mode); | ||||
| 
 | ||||
| void gpio_item_configure_all_pins(GpioMode mode); | ||||
| 
 | ||||
| void gpio_item_set_pin(uint8_t index, bool level); | ||||
| 
 | ||||
| void gpio_item_set_all_pins(bool level); | ||||
| 
 | ||||
| const char* gpio_item_get_pin_name(uint8_t index); | ||||
							
								
								
									
										69
									
								
								applications/main/gpio/gpio_items.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,69 @@ | ||||
| #include "gpio_items.h" | ||||
| 
 | ||||
| #include <furi_hal_resources.h> | ||||
| 
 | ||||
| struct GPIOItems { | ||||
|     GpioPinRecord* pins; | ||||
|     size_t count; | ||||
| }; | ||||
| 
 | ||||
| GPIOItems* gpio_items_alloc() { | ||||
|     GPIOItems* items = malloc(sizeof(GPIOItems)); | ||||
| 
 | ||||
|     items->count = 0; | ||||
|     for(size_t i = 0; i < gpio_pins_count; i++) { | ||||
|         if(!gpio_pins[i].debug) { | ||||
|             items->count++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     items->pins = malloc(sizeof(GpioPinRecord) * items->count); | ||||
|     for(size_t i = 0; i < items->count; i++) { | ||||
|         if(!gpio_pins[i].debug) { | ||||
|             items->pins[i].pin = gpio_pins[i].pin; | ||||
|             items->pins[i].name = gpio_pins[i].name; | ||||
|         } | ||||
|     } | ||||
|     return items; | ||||
| } | ||||
| 
 | ||||
| void gpio_items_free(GPIOItems* items) { | ||||
|     free(items->pins); | ||||
|     free(items); | ||||
| } | ||||
| 
 | ||||
| uint8_t gpio_items_get_count(GPIOItems* items) { | ||||
|     return items->count; | ||||
| } | ||||
| 
 | ||||
| void gpio_items_configure_pin(GPIOItems* items, uint8_t index, GpioMode mode) { | ||||
|     furi_assert(index < items->count); | ||||
|     furi_hal_gpio_write(items->pins[index].pin, false); | ||||
|     furi_hal_gpio_init(items->pins[index].pin, mode, GpioPullNo, GpioSpeedVeryHigh); | ||||
| } | ||||
| 
 | ||||
| void gpio_items_configure_all_pins(GPIOItems* items, GpioMode mode) { | ||||
|     for(uint8_t i = 0; i < items->count; i++) { | ||||
|         gpio_items_configure_pin(items, i, mode); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void gpio_items_set_pin(GPIOItems* items, uint8_t index, bool level) { | ||||
|     furi_assert(index < items->count); | ||||
|     furi_hal_gpio_write(items->pins[index].pin, level); | ||||
| } | ||||
| 
 | ||||
| void gpio_items_set_all_pins(GPIOItems* items, bool level) { | ||||
|     for(uint8_t i = 0; i < items->count; i++) { | ||||
|         gpio_items_set_pin(items, i, level); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char* gpio_items_get_pin_name(GPIOItems* items, uint8_t index) { | ||||
|     furi_assert(index < items->count + 1); | ||||
|     if(index == items->count) { | ||||
|         return "ALL"; | ||||
|     } else { | ||||
|         return items->pins[index].name; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								applications/main/gpio/gpio_items.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,29 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi_hal_gpio.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct GPIOItems GPIOItems; | ||||
| 
 | ||||
| GPIOItems* gpio_items_alloc(); | ||||
| 
 | ||||
| void gpio_items_free(GPIOItems* items); | ||||
| 
 | ||||
| uint8_t gpio_items_get_count(GPIOItems* items); | ||||
| 
 | ||||
| void gpio_items_configure_pin(GPIOItems* items, uint8_t index, GpioMode mode); | ||||
| 
 | ||||
| void gpio_items_configure_all_pins(GPIOItems* items, GpioMode mode); | ||||
| 
 | ||||
| void gpio_items_set_pin(GPIOItems* items, uint8_t index, bool level); | ||||
| 
 | ||||
| void gpio_items_set_all_pins(GPIOItems* items, bool level); | ||||
| 
 | ||||
| const char* gpio_items_get_pin_name(GPIOItems* items, uint8_t index); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -1,6 +1,6 @@ | ||||
| #include "../gpio_app_i.h" | ||||
| #include "furi_hal_power.h" | ||||
| #include "furi_hal_usb.h" | ||||
| #include <furi_hal_power.h> | ||||
| #include <furi_hal_usb.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| enum GpioItem { | ||||
|  | ||||
| @ -12,8 +12,9 @@ void gpio_scene_test_ok_callback(InputType type, void* context) { | ||||
| } | ||||
| 
 | ||||
| void gpio_scene_test_on_enter(void* context) { | ||||
|     furi_assert(context); | ||||
|     GpioApp* app = context; | ||||
|     gpio_item_configure_all_pins(GpioModeOutputPushPull); | ||||
|     gpio_items_configure_all_pins(app->gpio_items, GpioModeOutputPushPull); | ||||
|     gpio_test_set_ok_callback(app->gpio_test, gpio_scene_test_ok_callback, app); | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewGpioTest); | ||||
| } | ||||
| @ -25,6 +26,7 @@ bool gpio_scene_test_on_event(void* context, SceneManagerEvent event) { | ||||
| } | ||||
| 
 | ||||
| void gpio_scene_test_on_exit(void* context) { | ||||
|     UNUSED(context); | ||||
|     gpio_item_configure_all_pins(GpioModeAnalog); | ||||
|     furi_assert(context); | ||||
|     GpioApp* app = context; | ||||
|     gpio_items_configure_all_pins(app->gpio_items, GpioModeAnalog); | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| #include "../usb_uart_bridge.h" | ||||
| #include "../gpio_app_i.h" | ||||
| #include "furi_hal.h" | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     UsbUartLineIndexVcp, | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| #include "usb_uart_bridge.h" | ||||
| #include "furi_hal.h" | ||||
| #include <furi_hal_usb_cdc.h> | ||||
| #include "usb_cdc.h" | ||||
| #include "cli/cli_vcp.h" | ||||
| #include <cli/cli_vcp.h> | ||||
| #include <cli/cli.h> | ||||
| #include <toolbox/api_lock.h> | ||||
| #include "cli/cli.h" | ||||
| #include <furi_hal.h> | ||||
| #include <furi_hal_usb_cdc.h> | ||||
| 
 | ||||
| #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||
| #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| #include "gpio_test.h" | ||||
| #include "../gpio_item.h" | ||||
| #include "../gpio_items.h" | ||||
| 
 | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| @ -11,6 +11,7 @@ struct GpioTest { | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t pin_idx; | ||||
|     GPIOItems* gpio_items; | ||||
| } GpioTestModel; | ||||
| 
 | ||||
| static bool gpio_test_process_left(GpioTest* gpio_test); | ||||
| @ -25,7 +26,12 @@ static void gpio_test_draw_callback(Canvas* canvas, void* _model) { | ||||
|     elements_multiline_text_aligned( | ||||
|         canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change pin"); | ||||
|     elements_multiline_text_aligned( | ||||
|         canvas, 64, 32, AlignCenter, AlignTop, gpio_item_get_pin_name(model->pin_idx)); | ||||
|         canvas, | ||||
|         64, | ||||
|         32, | ||||
|         AlignCenter, | ||||
|         AlignTop, | ||||
|         gpio_items_get_pin_name(model->gpio_items, model->pin_idx)); | ||||
| } | ||||
| 
 | ||||
| static bool gpio_test_input_callback(InputEvent* event, void* context) { | ||||
| @ -64,7 +70,7 @@ static bool gpio_test_process_right(GpioTest* gpio_test) { | ||||
|         gpio_test->view, | ||||
|         GpioTestModel * model, | ||||
|         { | ||||
|             if(model->pin_idx < GPIO_ITEM_COUNT) { | ||||
|             if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { | ||||
|                 model->pin_idx++; | ||||
|             } | ||||
|         }, | ||||
| @ -80,17 +86,17 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { | ||||
|         GpioTestModel * model, | ||||
|         { | ||||
|             if(event->type == InputTypePress) { | ||||
|                 if(model->pin_idx < GPIO_ITEM_COUNT) { | ||||
|                     gpio_item_set_pin(model->pin_idx, true); | ||||
|                 if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { | ||||
|                     gpio_items_set_pin(model->gpio_items, model->pin_idx, true); | ||||
|                 } else { | ||||
|                     gpio_item_set_all_pins(true); | ||||
|                     gpio_items_set_all_pins(model->gpio_items, true); | ||||
|                 } | ||||
|                 consumed = true; | ||||
|             } else if(event->type == InputTypeRelease) { | ||||
|                 if(model->pin_idx < GPIO_ITEM_COUNT) { | ||||
|                     gpio_item_set_pin(model->pin_idx, false); | ||||
|                 if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { | ||||
|                     gpio_items_set_pin(model->gpio_items, model->pin_idx, false); | ||||
|                 } else { | ||||
|                     gpio_item_set_all_pins(false); | ||||
|                     gpio_items_set_all_pins(model->gpio_items, false); | ||||
|                 } | ||||
|                 consumed = true; | ||||
|             } | ||||
| @ -101,11 +107,15 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| GpioTest* gpio_test_alloc() { | ||||
| GpioTest* gpio_test_alloc(GPIOItems* gpio_items) { | ||||
|     GpioTest* gpio_test = malloc(sizeof(GpioTest)); | ||||
| 
 | ||||
|     gpio_test->view = view_alloc(); | ||||
|     view_allocate_model(gpio_test->view, ViewModelTypeLocking, sizeof(GpioTestModel)); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         gpio_test->view, GpioTestModel * model, { model->gpio_items = gpio_items; }, false); | ||||
| 
 | ||||
|     view_set_context(gpio_test->view, gpio_test); | ||||
|     view_set_draw_callback(gpio_test->view, gpio_test_draw_callback); | ||||
|     view_set_input_callback(gpio_test->view, gpio_test_input_callback); | ||||
|  | ||||
| @ -1,11 +1,13 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "../gpio_items.h" | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| typedef struct GpioTest GpioTest; | ||||
| typedef void (*GpioTestOkCallback)(InputType type, void* context); | ||||
| 
 | ||||
| GpioTest* gpio_test_alloc(); | ||||
| GpioTest* gpio_test_alloc(GPIOItems* gpio_items); | ||||
| 
 | ||||
| void gpio_test_free(GpioTest* gpio_test); | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| #include "../usb_uart_bridge.h" | ||||
| #include "../gpio_app_i.h" | ||||
| #include "furi_hal.h" | ||||
| #include <furi_hal.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| struct GpioUsbUart { | ||||
|  | ||||
| @ -2,6 +2,7 @@ App( | ||||
|     appid="ibutton", | ||||
|     name="iButton", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     targets=["f7"], | ||||
|     entry_point="ibutton_app", | ||||
|     cdefines=["APP_IBUTTON"], | ||||
|     requires=[ | ||||
|  | ||||
| @ -271,7 +271,7 @@ void onewire_cli_print_usage() { | ||||
| 
 | ||||
| static void onewire_cli_search(Cli* cli) { | ||||
|     UNUSED(cli); | ||||
|     OneWireHost* onewire = onewire_host_alloc(); | ||||
|     OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); | ||||
|     uint8_t address[8]; | ||||
|     bool done = false; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ App( | ||||
|     name="Infrared", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     entry_point="infrared_app", | ||||
|     targets=["f7"], | ||||
|     cdefines=["APP_INFRARED"], | ||||
|     requires=[ | ||||
|         "gui", | ||||
|  | ||||
| @ -86,7 +86,7 @@ static void infrared_cli_print_usage(void) { | ||||
|     printf("\tir universal <remote_name> <signal_name>\r\n"); | ||||
|     printf("\tir universal list <remote_name>\r\n"); | ||||
|     // TODO: Do not hardcode universal remote names
 | ||||
|     printf("\tAvailable universal remotes: tv audio ac\r\n"); | ||||
|     printf("\tAvailable universal remotes: tv audio ac projector\r\n"); | ||||
| } | ||||
| 
 | ||||
| static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { | ||||
|  | ||||
| @ -17,6 +17,7 @@ ADD_SCENE(infrared, universal, Universal) | ||||
| ADD_SCENE(infrared, universal_tv, UniversalTV) | ||||
| ADD_SCENE(infrared, universal_ac, UniversalAC) | ||||
| ADD_SCENE(infrared, universal_audio, UniversalAudio) | ||||
| ADD_SCENE(infrared, universal_projector, UniversalProjector) | ||||
| ADD_SCENE(infrared, debug, Debug) | ||||
| ADD_SCENE(infrared, error_databases, ErrorDatabases) | ||||
| ADD_SCENE(infrared, rpc, Rpc) | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| #include "../infrared_i.h" | ||||
| #include "gui/canvas.h" | ||||
| #include <gui/canvas.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     InfraredRpcStateIdle, | ||||
|  | ||||
| @ -4,6 +4,7 @@ typedef enum { | ||||
|     SubmenuIndexUniversalTV, | ||||
|     SubmenuIndexUniversalAC, | ||||
|     SubmenuIndexUniversalAudio, | ||||
|     SubmenuIndexUniversalProjector, | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) { | ||||
| @ -27,13 +28,20 @@ void infrared_scene_universal_on_enter(void* context) { | ||||
|         SubmenuIndexUniversalAudio, | ||||
|         infrared_scene_universal_submenu_callback, | ||||
|         context); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Projectors", | ||||
|         SubmenuIndexUniversalProjector, | ||||
|         infrared_scene_universal_submenu_callback, | ||||
|         context); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Air Conditioners", | ||||
|         SubmenuIndexUniversalAC, | ||||
|         infrared_scene_universal_submenu_callback, | ||||
|         context); | ||||
|     submenu_set_selected_item(submenu, 0); | ||||
|     submenu_set_selected_item( | ||||
|         submenu, scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneUniversal)); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu); | ||||
| } | ||||
| @ -53,7 +61,11 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { | ||||
|         } else if(event.event == SubmenuIndexUniversalAudio) { | ||||
|             scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexUniversalProjector) { | ||||
|             scene_manager_next_scene(scene_manager, InfraredSceneUniversalProjector); | ||||
|             consumed = true; | ||||
|         } | ||||
|         scene_manager_set_scene_state(scene_manager, InfraredSceneUniversal, event.event); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
|  | ||||
| @ -0,0 +1,86 @@ | ||||
| #include "../infrared_i.h" | ||||
| 
 | ||||
| #include "common/infrared_scene_universal_common.h" | ||||
| 
 | ||||
| void infrared_scene_universal_projector_on_enter(void* context) { | ||||
|     infrared_scene_universal_common_on_enter(context); | ||||
| 
 | ||||
|     Infrared* infrared = context; | ||||
|     ButtonPanel* button_panel = infrared->button_panel; | ||||
|     InfraredBruteForce* brute_force = infrared->brute_force; | ||||
| 
 | ||||
|     infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/projector.ir")); | ||||
| 
 | ||||
|     button_panel_reserve(button_panel, 2, 2); | ||||
|     uint32_t i = 0; | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         0, | ||||
|         0, | ||||
|         3, | ||||
|         19, | ||||
|         &I_Power_25x27, | ||||
|         &I_Power_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         context); | ||||
|     infrared_brute_force_add_record(brute_force, i++, "Power"); | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         1, | ||||
|         0, | ||||
|         36, | ||||
|         19, | ||||
|         &I_Mute_25x27, | ||||
|         &I_Mute_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         context); | ||||
|     infrared_brute_force_add_record(brute_force, i++, "Mute"); | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         0, | ||||
|         1, | ||||
|         3, | ||||
|         66, | ||||
|         &I_Vol_up_25x27, | ||||
|         &I_Vol_up_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         context); | ||||
|     infrared_brute_force_add_record(brute_force, i++, "Vol_up"); | ||||
|     button_panel_add_item( | ||||
|         button_panel, | ||||
|         i, | ||||
|         1, | ||||
|         1, | ||||
|         36, | ||||
|         66, | ||||
|         &I_Vol_down_25x27, | ||||
|         &I_Vol_down_hvr_25x27, | ||||
|         infrared_scene_universal_common_item_callback, | ||||
|         context); | ||||
|     infrared_brute_force_add_record(brute_force, i++, "Vol_dn"); | ||||
| 
 | ||||
|     button_panel_add_label(button_panel, 2, 11, FontPrimary, "Proj. remote"); | ||||
|     button_panel_add_label(button_panel, 17, 62, FontSecondary, "Volume"); | ||||
| 
 | ||||
|     view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); | ||||
|     view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); | ||||
| 
 | ||||
|     infrared_show_loading_popup(infrared, true); | ||||
|     bool success = infrared_brute_force_calculate_messages(brute_force); | ||||
|     infrared_show_loading_popup(infrared, false); | ||||
| 
 | ||||
|     if(!success) { | ||||
|         scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) { | ||||
|     return infrared_scene_universal_common_on_event(context, event); | ||||
| } | ||||
| 
 | ||||
| void infrared_scene_universal_projector_on_exit(void* context) { | ||||
|     infrared_scene_universal_common_on_exit(context); | ||||
| } | ||||
| @ -1,11 +1,11 @@ | ||||
| #include "infrared_debug_view.h" | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #define INFRARED_DEBUG_TEXT_LENGTH 64 | ||||
| 
 | ||||
| struct InfraredDebugView { | ||||
|  | ||||
| @ -1,13 +1,15 @@ | ||||
| #include <core/check.h> | ||||
| #include "furi_hal_resources.h" | ||||
| #include "assets_icons.h" | ||||
| #include "gui/canvas.h" | ||||
| #include "gui/view.h" | ||||
| #include "input/input.h" | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include "infrared_progress_view.h" | ||||
| #include "gui/modules/button_panel.h" | ||||
| 
 | ||||
| #include <assets_icons.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/elements.h> | ||||
| #include <gui/modules/button_panel.h> | ||||
| #include <input/input.h> | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <furi_hal_resources.h> | ||||
| #include <core/check.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| struct InfraredProgressView { | ||||
|  | ||||
| @ -2,6 +2,7 @@ App( | ||||
|     appid="lfrfid", | ||||
|     name="125 kHz RFID", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     targets=["f7"], | ||||
|     entry_point="lfrfid_app", | ||||
|     cdefines=["APP_LF_RFID"], | ||||
|     requires=[ | ||||
|  | ||||
| @ -47,21 +47,28 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubmenuIndexRead) { | ||||
|             scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexRead); | ||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); | ||||
|             DOLPHIN_DEED(DolphinDeedRfidRead); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexSaved) { | ||||
|             // Like in the other apps, explicitly save the scene state
 | ||||
|             // in each branch in case the user cancels loading a file.
 | ||||
|             scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexSaved); | ||||
|             furi_string_set(app->file_path, LFRFID_APP_FOLDER); | ||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectKey); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexAddManually) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 app->scene_manager, LfRfidSceneStart, SubmenuIndexAddManually); | ||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveType); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexExtraActions) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 app->scene_manager, LfRfidSceneStart, SubmenuIndexExtraActions); | ||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneExtraActions); | ||||
|             consumed = true; | ||||
|         } | ||||
|         scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, event.event); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
|  | ||||
| @ -2,6 +2,7 @@ App( | ||||
|     appid="nfc", | ||||
|     name="NFC", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     targets=["f7"], | ||||
|     entry_point="nfc_app", | ||||
|     cdefines=["APP_NFC"], | ||||
|     requires=[ | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| #include "nfc_i.h" | ||||
| #include "furi_hal_nfc.h" | ||||
| #include <furi_hal_nfc.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| bool nfc_custom_event_callback(void* context, uint32_t event) { | ||||
|  | ||||
| @ -4,7 +4,7 @@ ADD_SCENE(nfc, saved_menu, SavedMenu) | ||||
| ADD_SCENE(nfc, extra_actions, ExtraActions) | ||||
| ADD_SCENE(nfc, set_type, SetType) | ||||
| ADD_SCENE(nfc, set_sak, SetSak) | ||||
| ADD_SCENE(nfc, set_atqa, SetAtqua) | ||||
| ADD_SCENE(nfc, set_atqa, SetAtqa) | ||||
| ADD_SCENE(nfc, set_uid, SetUid) | ||||
| ADD_SCENE(nfc, generate_info, GenerateInfo) | ||||
| ADD_SCENE(nfc, read_card_success, ReadCardSuccess) | ||||
|  | ||||
| @ -30,7 +30,7 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { | ||||
|                     nfc->scene_manager, NfcSceneMfClassicKeys); | ||||
|             } else { | ||||
|                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                     nfc->scene_manager, NfcSceneStart); | ||||
|                     nfc->scene_manager, NfcSceneFileSelect); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -34,6 +34,8 @@ void nfc_scene_extra_actions_on_enter(void* context) { | ||||
|         SubmenuIndexMfUltralightUnlock, | ||||
|         nfc_scene_extra_actions_submenu_callback, | ||||
|         nfc); | ||||
|     submenu_set_selected_item( | ||||
|         submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); | ||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -11,7 +11,7 @@ void nfc_scene_set_atqa_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     ByteInput* byte_input = nfc->byte_input; | ||||
|     byte_input_set_header_text(byte_input, "Enter atqa in hex"); | ||||
|     byte_input_set_header_text(byte_input, "Enter ATQA in hex"); | ||||
|     byte_input_set_result_callback( | ||||
|         byte_input, | ||||
|         nfc_scene_set_atqa_byte_input_callback, | ||||
|  | ||||
| @ -28,7 +28,7 @@ bool nfc_scene_set_sak_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == NfcCustomEventByteInputDone) { | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqua); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqa); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -11,7 +11,7 @@ void nfc_scene_set_uid_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     ByteInput* byte_input = nfc->byte_input; | ||||
|     byte_input_set_header_text(byte_input, "Enter uid in hex"); | ||||
|     byte_input_set_header_text(byte_input, "Enter UID in hex"); | ||||
|     nfc->dev_edit_data = nfc->dev->dev_data.nfc_data; | ||||
|     byte_input_set_result_callback( | ||||
|         byte_input, | ||||
|  | ||||
| @ -48,11 +48,14 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubmenuIndexRead) { | ||||
|             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); | ||||
|             nfc->dev->dev_data.read_mode = NfcReadModeAuto; | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||
|             DOLPHIN_DEED(DolphinDeedNfcRead); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexDetectReader) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc->scene_manager, NfcSceneStart, SubmenuIndexDetectReader); | ||||
|             bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; | ||||
|             if(sd_exist) { | ||||
|                 nfc_device_data_clear(&nfc->dev->dev_data); | ||||
| @ -63,19 +66,27 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
|             } | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexSaved) { | ||||
|             // Save the scene state explicitly in each branch, so that
 | ||||
|             // if the user cancels loading a file, the Saved menu item
 | ||||
|             // is properly reselected.
 | ||||
|             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexSaved); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexExtraAction) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc->scene_manager, NfcSceneStart, SubmenuIndexExtraAction); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexAddManually) { | ||||
|             scene_manager_set_scene_state( | ||||
|                 nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexDebug) { | ||||
|             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); | ||||
|             consumed = true; | ||||
|         } | ||||
|         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, event.event); | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
|  | ||||
| @ -2,6 +2,7 @@ App( | ||||
|     appid="subghz", | ||||
|     name="Sub-GHz", | ||||
|     apptype=FlipperAppType.APP, | ||||
|     targets=["f7"], | ||||
|     entry_point="subghz_app", | ||||
|     cdefines=["APP_SUBGHZ"], | ||||
|     requires=[ | ||||
| @ -11,7 +12,7 @@ App( | ||||
|     ], | ||||
|     provides=["subghz_start"], | ||||
|     icon="A_Sub1ghz_14", | ||||
|     stack_size=2 * 1024, | ||||
|     stack_size=3 * 1024, | ||||
|     order=10, | ||||
| ) | ||||
| 
 | ||||
|  | ||||
| @ -28,10 +28,8 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event | ||||
|         if(event.event == SubGhzCustomEventSceneDeleteSuccess) { | ||||
|             if(scene_manager_search_and_switch_to_previous_scene( | ||||
|                    subghz->scene_manager, SubGhzSceneReadRAW)) { | ||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); | ||||
|             } else if(scene_manager_search_and_switch_to_previous_scene( | ||||
|                           subghz->scene_manager, SubGhzSceneSaved)) { | ||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); | ||||
|             } else { | ||||
|                 scene_manager_search_and_switch_to_previous_scene( | ||||
|                     subghz->scene_manager, SubGhzSceneStart); | ||||
|  | ||||
| @ -411,5 +411,5 @@ void subghz_scene_read_raw_on_exit(void* context) { | ||||
|     notification_message(subghz->notifications, &sequence_reset_rgb); | ||||
| 
 | ||||
|     //filter restoration
 | ||||
|     subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); | ||||
|     subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| #include "../subghz_i.h" | ||||
| #include "../views/receiver.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| #include <lib/subghz/protocols/bin_raw.h> | ||||
| 
 | ||||
| static const NotificationSequence subghs_sequence_rx = { | ||||
|     &message_green_255, | ||||
| @ -143,6 +144,11 @@ void subghz_scene_receiver_on_enter(void* context) { | ||||
|     } | ||||
|     subghz_view_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); | ||||
| 
 | ||||
|     //to use a universal decoder, we are looking for a link to it
 | ||||
|     subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( | ||||
|         subghz->txrx->receiver, SUBGHZ_PROTOCOL_BIN_RAW_NAME); | ||||
|     furi_assert(subghz->txrx->decoder_result); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdReceiver); | ||||
| } | ||||
| 
 | ||||
| @ -208,6 +214,13 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { | ||||
|             subghz_hopper_update(subghz); | ||||
|             subghz_scene_receiver_update_statusbar(subghz); | ||||
|         } | ||||
| 
 | ||||
|         //get RSSI
 | ||||
|         float rssi = furi_hal_subghz_get_rssi(); | ||||
|         subghz_receiver_rssi(subghz->subghz_receiver, rssi); | ||||
|         subghz_protocol_decoder_bin_raw_data_input_rssi( | ||||
|             (SubGhzProtocolDecoderBinRAW*)subghz->txrx->decoder_result, rssi); | ||||
| 
 | ||||
|         switch(subghz->state_notifications) { | ||||
|         case SubGhzNotificationStateRx: | ||||
|             notification_message(subghz->notifications, &sequence_blink_cyan_10); | ||||
|  | ||||
| @ -5,6 +5,7 @@ enum SubGhzSettingIndex { | ||||
|     SubGhzSettingIndexFrequency, | ||||
|     SubGhzSettingIndexHopping, | ||||
|     SubGhzSettingIndexModulation, | ||||
|     SubGhzSettingIndexBinRAW, | ||||
|     SubGhzSettingIndexSound, | ||||
|     SubGhzSettingIndexLock, | ||||
|     SubGhzSettingIndexRAWThesholdRSSI, | ||||
| @ -58,6 +59,15 @@ const uint32_t speaker_value[SPEAKER_COUNT] = { | ||||
|     SubGhzSpeakerStateShutdown, | ||||
|     SubGhzSpeakerStateEnable, | ||||
| }; | ||||
| #define BIN_RAW_COUNT 2 | ||||
| const char* const bin_raw_text[BIN_RAW_COUNT] = { | ||||
|     "OFF", | ||||
|     "ON", | ||||
| }; | ||||
| const uint32_t bin_raw_value[BIN_RAW_COUNT] = { | ||||
|     SubGhzProtocolFlag_Decodable, | ||||
|     SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_BinRAW, | ||||
| }; | ||||
| 
 | ||||
| uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { | ||||
|     furi_assert(context); | ||||
| @ -186,6 +196,15 @@ static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { | ||||
|     subghz->txrx->speaker_state = speaker_value[index]; | ||||
| } | ||||
| 
 | ||||
| static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) { | ||||
|     SubGhz* subghz = variable_item_get_context(item); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| 
 | ||||
|     variable_item_set_current_value_text(item, bin_raw_text[index]); | ||||
|     subghz->txrx->filter = bin_raw_value[index]; | ||||
|     subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); | ||||
| } | ||||
| 
 | ||||
| static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { | ||||
|     SubGhz* subghz = variable_item_get_context(item); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| @ -254,6 +273,19 @@ void subghz_scene_receiver_config_on_enter(void* context) { | ||||
|     variable_item_set_current_value_text( | ||||
|         item, subghz_setting_get_preset_name(subghz->setting, value_index)); | ||||
| 
 | ||||
|     if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != | ||||
|        SubGhzCustomEventManagerSet) { | ||||
|         item = variable_item_list_add( | ||||
|             subghz->variable_item_list, | ||||
|             "Bin_RAW:", | ||||
|             BIN_RAW_COUNT, | ||||
|             subghz_scene_receiver_config_set_bin_raw, | ||||
|             subghz); | ||||
|         value_index = value_index_uint32(subghz->txrx->filter, bin_raw_value, BIN_RAW_COUNT); | ||||
|         variable_item_set_current_value_index(item, value_index); | ||||
|         variable_item_set_current_value_text(item, bin_raw_text[value_index]); | ||||
|     } | ||||
| 
 | ||||
|     item = variable_item_list_add( | ||||
|         subghz->variable_item_list, | ||||
|         "Sound:", | ||||
|  | ||||
| @ -129,6 +129,21 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | ||||
|                        subghz_history_get_raw_data( | ||||
|                            subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { | ||||
|                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); | ||||
|                     if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|                         subghz_tx_stop(subghz); | ||||
|                     } | ||||
|                     if(subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) { | ||||
|                         subghz_begin( | ||||
|                             subghz, | ||||
|                             subghz_setting_get_preset_data_by_name( | ||||
|                                 subghz->setting, | ||||
|                                 furi_string_get_cstr(subghz->txrx->preset->name))); | ||||
|                         subghz_rx(subghz, subghz->txrx->preset->frequency); | ||||
|                     } | ||||
|                     if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { | ||||
|                         subghz->txrx->hopper_state = SubGhzHopperStateRunnig; | ||||
|                     } | ||||
|                     subghz->state_notifications = SubGhzNotificationStateRx; | ||||
|                 } else { | ||||
|                     subghz->state_notifications = SubGhzNotificationStateTx; | ||||
|                 } | ||||
|  | ||||
| @ -187,12 +187,15 @@ SubGhz* subghz_alloc() { | ||||
|     subghz->txrx->environment = subghz_environment_alloc(); | ||||
|     subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||
|         subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo")); | ||||
|     subghz_environment_set_alutech_at_4n_rainbow_table_file_name( | ||||
|         subghz->txrx->environment, EXT_PATH("subghz/assets/alutech_at_4n")); | ||||
|     subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||
|         subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s")); | ||||
|     subghz_environment_set_protocol_registry( | ||||
|         subghz->txrx->environment, (void*)&subghz_protocol_registry); | ||||
|     subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment); | ||||
|     subghz_receiver_set_filter(subghz->txrx->receiver, SubGhzProtocolFlag_Decodable); | ||||
|     subghz->txrx->filter = SubGhzProtocolFlag_Decodable; | ||||
|     subghz_receiver_set_filter(subghz->txrx->receiver, subghz->txrx->filter); | ||||
| 
 | ||||
|     subghz_worker_set_overrun_callback( | ||||
|         subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); | ||||
| @ -216,6 +219,8 @@ void subghz_free(SubGhz* subghz) { | ||||
|         subghz->rpc_ctx = NULL; | ||||
|     } | ||||
| 
 | ||||
|     subghz_speaker_off(subghz); | ||||
| 
 | ||||
|     // Packet Test
 | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); | ||||
|     subghz_test_packet_free(subghz->subghz_test_packet); | ||||
|  | ||||
| @ -257,6 +257,8 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { | ||||
|     subghz_environment_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); | ||||
|     subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||
|         environment, EXT_PATH("subghz/assets/came_atomo")); | ||||
|     subghz_environment_set_alutech_at_4n_rainbow_table_file_name( | ||||
|         environment, EXT_PATH("subghz/assets/alutech_at_4n")); | ||||
|     subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||
|         environment, EXT_PATH("subghz/assets/nice_flor_s")); | ||||
|     subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); | ||||
| @ -309,6 +311,81 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { | ||||
|     UNUSED(context); | ||||
|     uint32_t frequency = 433920000; | ||||
| 
 | ||||
|     if(furi_string_size(args)) { | ||||
|         int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); | ||||
|         if(ret != 1) { | ||||
|             printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); | ||||
|             cli_print_usage("subghz rx", "<Frequency: in Hz>", furi_string_get_cstr(args)); | ||||
|             return; | ||||
|         } | ||||
|         if(!furi_hal_subghz_is_frequency_valid(frequency)) { | ||||
|             printf( | ||||
|                 "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", | ||||
|                 frequency); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Allocate context and buffers
 | ||||
|     SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); | ||||
|     instance->stream = | ||||
|         furi_stream_buffer_alloc(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); | ||||
|     furi_check(instance->stream); | ||||
| 
 | ||||
|     // Configure radio
 | ||||
|     furi_hal_subghz_reset(); | ||||
|     furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok270Async); | ||||
|     frequency = furi_hal_subghz_set_frequency_and_path(frequency); | ||||
|     furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); | ||||
| 
 | ||||
|     furi_hal_power_suppress_charge_enter(); | ||||
| 
 | ||||
|     // Prepare and start RX
 | ||||
|     furi_hal_subghz_start_async_rx(subghz_cli_command_rx_capture_callback, instance); | ||||
| 
 | ||||
|     // Wait for packets to arrive
 | ||||
|     printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); | ||||
|     LevelDuration level_duration; | ||||
|     size_t counter = 0; | ||||
|     while(!cli_cmd_interrupt_received(cli)) { | ||||
|         int ret = furi_stream_buffer_receive( | ||||
|             instance->stream, &level_duration, sizeof(LevelDuration), 10); | ||||
|         if(ret == 0) { | ||||
|             continue; | ||||
|         } | ||||
|         if(ret != sizeof(LevelDuration)) { | ||||
|             puts("stream corrupt"); | ||||
|             break; | ||||
|         } | ||||
|         if(level_duration_is_reset(level_duration)) { | ||||
|             puts(". "); | ||||
|         } else { | ||||
|             bool level = level_duration_get_level(level_duration); | ||||
|             uint32_t duration = level_duration_get_duration(level_duration); | ||||
|             printf("%c%lu ", level ? '+' : '-', duration); | ||||
|         } | ||||
|         furi_thread_stdout_flush(); | ||||
|         counter++; | ||||
|         if(counter > 255) { | ||||
|             puts("\r\n"); | ||||
|             counter = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Shutdown radio
 | ||||
|     furi_hal_subghz_stop_async_rx(); | ||||
|     furi_hal_subghz_sleep(); | ||||
| 
 | ||||
|     furi_hal_power_suppress_charge_exit(); | ||||
| 
 | ||||
|     // Cleanup
 | ||||
|     furi_stream_buffer_free(instance->stream); | ||||
|     free(instance); | ||||
| } | ||||
| void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { | ||||
|     UNUSED(context); | ||||
|     FuriString* file_name; | ||||
| @ -377,6 +454,8 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { | ||||
|         } | ||||
|         subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||
|             environment, EXT_PATH("subghz/assets/came_atomo")); | ||||
|         subghz_environment_set_alutech_at_4n_rainbow_table_file_name( | ||||
|             environment, EXT_PATH("subghz/assets/alutech_at_4n")); | ||||
|         subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||
|             environment, EXT_PATH("subghz/assets/nice_flor_s")); | ||||
|         subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); | ||||
| @ -431,7 +510,8 @@ static void subghz_cli_command_print_usage() { | ||||
|     printf("\tchat <frequency:in Hz>\t - Chat with other Flippers\r\n"); | ||||
|     printf( | ||||
|         "\ttx <3 byte Key: in hex> <frequency: in Hz> <te: us> <repeat: count>\t - Transmitting key\r\n"); | ||||
|     printf("\trx <frequency:in Hz>\t - Reception key\r\n"); | ||||
|     printf("\trx <frequency:in Hz>\t - Receive\r\n"); | ||||
|     printf("\trx_raw <frequency:in Hz>\t - Receive RAW\r\n"); | ||||
|     printf("\tdecode_raw <file_name: path_RAW_file>\t - Testing\r\n"); | ||||
| 
 | ||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { | ||||
| @ -733,6 +813,11 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(furi_string_cmp_str(cmd, "rx_raw") == 0) { | ||||
|             subghz_cli_command_rx_raw(cli, args, context); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(furi_string_cmp_str(cmd, "decode_raw") == 0) { | ||||
|             subghz_cli_command_decode_raw(cli, args, context); | ||||
|             break; | ||||
|  | ||||
| @ -5,6 +5,7 @@ | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define SUBGHZ_HISTORY_MAX 50 | ||||
| #define SUBGHZ_HISTORY_FREE_HEAP 20480 | ||||
| #define TAG "SubGhzHistory" | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -121,8 +122,12 @@ FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx | ||||
| } | ||||
| bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output) { | ||||
|     furi_assert(instance); | ||||
|     if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) { | ||||
|         if(output != NULL) furi_string_printf(output, "    Free heap LOW"); | ||||
|         return true; | ||||
|     } | ||||
|     if(instance->last_index_write == SUBGHZ_HISTORY_MAX) { | ||||
|         if(output != NULL) furi_string_printf(output, "Memory is FULL"); | ||||
|         if(output != NULL) furi_string_printf(output, "   Memory is FULL"); | ||||
|         return true; | ||||
|     } | ||||
|     if(output != NULL) | ||||
| @ -142,6 +147,7 @@ bool subghz_history_add_to_history( | ||||
|     furi_assert(instance); | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) return false; | ||||
|     if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false; | ||||
| 
 | ||||
|     SubGhzProtocolDecoderBase* decoder_base = context; | ||||
| @ -200,27 +206,31 @@ bool subghz_history_add_to_history( | ||||
|         } | ||||
|         uint8_t key_data[sizeof(uint64_t)] = {0}; | ||||
|         if(!flipper_format_read_hex(item->flipper_string, "Key", key_data, sizeof(uint64_t))) { | ||||
|             FURI_LOG_E(TAG, "Missing Key"); | ||||
|             break; | ||||
|             FURI_LOG_D(TAG, "No Key"); | ||||
|         } | ||||
|         uint64_t data = 0; | ||||
|         for(uint8_t i = 0; i < sizeof(uint64_t); i++) { | ||||
|             data = (data << 8) | key_data[i]; | ||||
|         } | ||||
|         if(!(uint32_t)(data >> 32)) { | ||||
|             furi_string_printf( | ||||
|                 item->item_str, | ||||
|                 "%s %lX", | ||||
|                 furi_string_get_cstr(instance->tmp_string), | ||||
|                 (uint32_t)(data & 0xFFFFFFFF)); | ||||
|         if(data != 0) { | ||||
|             if(!(uint32_t)(data >> 32)) { | ||||
|                 furi_string_printf( | ||||
|                     item->item_str, | ||||
|                     "%s %lX", | ||||
|                     furi_string_get_cstr(instance->tmp_string), | ||||
|                     (uint32_t)(data & 0xFFFFFFFF)); | ||||
|             } else { | ||||
|                 furi_string_printf( | ||||
|                     item->item_str, | ||||
|                     "%s %lX%08lX", | ||||
|                     furi_string_get_cstr(instance->tmp_string), | ||||
|                     (uint32_t)(data >> 32), | ||||
|                     (uint32_t)(data & 0xFFFFFFFF)); | ||||
|             } | ||||
|         } else { | ||||
|             furi_string_printf( | ||||
|                 item->item_str, | ||||
|                 "%s %lX%08lX", | ||||
|                 furi_string_get_cstr(instance->tmp_string), | ||||
|                 (uint32_t)(data >> 32), | ||||
|                 (uint32_t)(data & 0xFFFFFFFF)); | ||||
|             furi_string_printf(item->item_str, "%s", furi_string_get_cstr(instance->tmp_string)); | ||||
|         } | ||||
| 
 | ||||
|     } while(false); | ||||
| 
 | ||||
|     furi_string_free(text); | ||||
|  | ||||
| @ -45,6 +45,7 @@ struct SubGhzTxRx { | ||||
|     SubGhzEnvironment* environment; | ||||
|     SubGhzReceiver* receiver; | ||||
|     SubGhzTransmitter* transmitter; | ||||
|     SubGhzProtocolFlag filter; | ||||
|     SubGhzProtocolDecoderBase* decoder_result; | ||||
|     FlipperFormat* fff_data; | ||||
| 
 | ||||
|  | ||||
| @ -12,6 +12,8 @@ | ||||
| #define MENU_ITEMS 4u | ||||
| #define UNLOCK_CNT 3 | ||||
| 
 | ||||
| #define SUBGHZ_RAW_TRESHOLD_MIN -90.0f | ||||
| 
 | ||||
| typedef struct { | ||||
|     FuriString* item_str; | ||||
|     uint8_t type; | ||||
| @ -59,8 +61,24 @@ typedef struct { | ||||
|     uint16_t list_offset; | ||||
|     uint16_t history_item; | ||||
|     SubGhzViewReceiverBarShow bar_show; | ||||
|     uint8_t u_rssi; | ||||
| } SubGhzViewReceiverModel; | ||||
| 
 | ||||
| void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi) { | ||||
|     furi_assert(instance); | ||||
|     with_view_model( | ||||
|         instance->view, | ||||
|         SubGhzViewReceiverModel * model, | ||||
|         { | ||||
|             if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { | ||||
|                 model->u_rssi = 0; | ||||
|             } else { | ||||
|                 model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN); | ||||
|             } | ||||
|         }, | ||||
|         true); | ||||
| } | ||||
| 
 | ||||
| void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     subghz_receiver->lock_count = 0; | ||||
| @ -168,13 +186,22 @@ static void subghz_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool s | ||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); | ||||
| } | ||||
| 
 | ||||
| static void subghz_view_rssi_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { | ||||
|     for(uint8_t i = 1; i < model->u_rssi; i++) { | ||||
|         if(i % 5) { | ||||
|             canvas_draw_dot(canvas, 46 + i, 50); | ||||
|             canvas_draw_dot(canvas, 47 + i, 51); | ||||
|             canvas_draw_dot(canvas, 46 + i, 52); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
| 
 | ||||
|     elements_button_left(canvas, "Config"); | ||||
|     canvas_draw_line(canvas, 46, 51, 125, 51); | ||||
| 
 | ||||
|     bool scrollbar = model->history_item > 4; | ||||
|     FuriString* str_buff; | ||||
| @ -206,11 +233,11 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { | ||||
|     if(model->history_item == 0) { | ||||
|         canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str(canvas, 63, 46, "Scanning..."); | ||||
|         canvas_draw_line(canvas, 46, 51, 125, 51); | ||||
|         canvas_draw_str(canvas, 63, 44, "Scanning..."); | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|     } | ||||
| 
 | ||||
|     subghz_view_rssi_draw(canvas, model); | ||||
|     switch(model->bar_show) { | ||||
|     case SubGhzViewReceiverBarShowLock: | ||||
|         canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); | ||||
|  | ||||
| @ -8,6 +8,8 @@ typedef struct SubGhzViewReceiver SubGhzViewReceiver; | ||||
| 
 | ||||
| typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context); | ||||
| 
 | ||||
| void subghz_receiver_rssi(SubGhzViewReceiver* instance, float rssi); | ||||
| 
 | ||||
| void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard); | ||||
| 
 | ||||
| void subghz_view_receiver_set_callback( | ||||
|  | ||||
| @ -79,7 +79,6 @@ void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x | ||||
| void subghz_frequency_analyzer_draw_log_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { | ||||
|     uint8_t column_height = 6; | ||||
|     if(rssi) { | ||||
|         //rssi = rssi
 | ||||
|         if(rssi > 54) rssi = 54; | ||||
|         for(uint8_t i = 1; i < rssi; i++) { | ||||
|             if(i % 5) { | ||||
|  | ||||
| @ -84,9 +84,10 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     elements_multiline_text(canvas, 0, 8, furi_string_get_cstr(model->key_str)); | ||||
|     canvas_draw_str(canvas, 78, 8, furi_string_get_cstr(model->frequency_str)); | ||||
|     canvas_draw_str(canvas, 113, 8, furi_string_get_cstr(model->preset_str)); | ||||
|     elements_multiline_text_aligned( | ||||
|         canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->key_str)); | ||||
|     canvas_draw_str(canvas, 78, 7, furi_string_get_cstr(model->frequency_str)); | ||||
|     canvas_draw_str(canvas, 113, 7, furi_string_get_cstr(model->preset_str)); | ||||
|     if(model->show_button) subghz_view_transmitter_button_right(canvas, "Send"); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| #include "../u2f_app_i.h" | ||||
| #include "../views/u2f_view.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| #include "furi_hal.h" | ||||
| #include <furi_hal.h> | ||||
| #include "../u2f.h" | ||||
| 
 | ||||
| #define U2F_REQUEST_TIMEOUT 500 | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF10_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 172 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF11_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 173 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF12_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 180 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF1_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 177 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF2_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 179 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF3_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 178 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF4_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 177 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF5_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 178 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF6_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 177 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF7_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 176 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF8_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 176 B | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/hid_app/assets/ButtonF9_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 179 B | 
| @ -46,11 +46,25 @@ typedef struct { | ||||
| #define KEY_WIDTH 9 | ||||
| #define KEY_HEIGHT 12 | ||||
| #define KEY_PADDING 1 | ||||
| #define ROW_COUNT 6 | ||||
| #define ROW_COUNT 7 | ||||
| #define COLUMN_COUNT 12 | ||||
| 
 | ||||
| // 0 width items are not drawn, but there value is used
 | ||||
| const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | ||||
|     { | ||||
|         {.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1}, | ||||
|         {.width = 1, .icon = &I_ButtonF2_5x8, .value = HID_KEYBOARD_F2}, | ||||
|         {.width = 1, .icon = &I_ButtonF3_5x8, .value = HID_KEYBOARD_F3}, | ||||
|         {.width = 1, .icon = &I_ButtonF4_5x8, .value = HID_KEYBOARD_F4}, | ||||
|         {.width = 1, .icon = &I_ButtonF5_5x8, .value = HID_KEYBOARD_F5}, | ||||
|         {.width = 1, .icon = &I_ButtonF6_5x8, .value = HID_KEYBOARD_F6}, | ||||
|         {.width = 1, .icon = &I_ButtonF7_5x8, .value = HID_KEYBOARD_F7}, | ||||
|         {.width = 1, .icon = &I_ButtonF8_5x8, .value = HID_KEYBOARD_F8}, | ||||
|         {.width = 1, .icon = &I_ButtonF9_5x8, .value = HID_KEYBOARD_F9}, | ||||
|         {.width = 1, .icon = &I_ButtonF10_5x8, .value = HID_KEYBOARD_F10}, | ||||
|         {.width = 1, .icon = &I_ButtonF11_5x8, .value = HID_KEYBOARD_F11}, | ||||
|         {.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12}, | ||||
|     }, | ||||
|     { | ||||
|         {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, | ||||
|         {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, | ||||
| @ -224,7 +238,12 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     canvas_set_font(canvas, FontKeyboard); | ||||
|     // Start shifting the all keys up if on the next row (Scrolling)
 | ||||
|     uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; | ||||
|     uint8_t initY = model->y == 0 ? 0 : 1; | ||||
| 
 | ||||
|     if(model->y > 5) { | ||||
|         initY = model->y - 4; | ||||
|     } | ||||
| 
 | ||||
|     for(uint8_t y = initY; y < ROW_COUNT; y++) { | ||||
|         const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; | ||||
|         uint8_t x = 0; | ||||
| @ -365,7 +384,10 @@ HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { | ||||
|     with_view_model( | ||||
|         hid_keyboard->view, | ||||
|         HidKeyboardModel * model, | ||||
|         { model->transport = bt_hid->transport; }, | ||||
|         { | ||||
|             model->transport = bt_hid->transport; | ||||
|             model->y = 1; | ||||
|         }, | ||||
|         true); | ||||
| 
 | ||||
|     return hid_keyboard; | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
| #include <storage/storage.h> | ||||
| #include <lib/flipper_format/flipper_format.h> | ||||
| 
 | ||||
| #include <math.h> | ||||
| #include <m-array.h> | ||||
| 
 | ||||
| #define TAG "MusicPlayerWorker" | ||||
|  | ||||
| @ -2,6 +2,7 @@ App( | ||||
|     appid="nfc_magic", | ||||
|     name="Nfc Magic", | ||||
|     apptype=FlipperAppType.EXTERNAL, | ||||
|     targets=["f7"], | ||||
|     entry_point="nfc_magic_app", | ||||
|     requires=[ | ||||
|         "storage", | ||||
|  | ||||
| @ -136,9 +136,9 @@ void nfc_magic_free(NfcMagic* nfc_magic) { | ||||
|     free(nfc_magic); | ||||
| } | ||||
| 
 | ||||
| static const NotificationSequence nfc_magic_sequence_blink_start_blue = { | ||||
| static const NotificationSequence nfc_magic_sequence_blink_start_cyan = { | ||||
|     &message_blink_start_10, | ||||
|     &message_blink_set_color_blue, | ||||
|     &message_blink_set_color_cyan, | ||||
|     &message_do_not_reset, | ||||
|     NULL, | ||||
| }; | ||||
| @ -149,7 +149,7 @@ static const NotificationSequence nfc_magic_sequence_blink_stop = { | ||||
| }; | ||||
| 
 | ||||
| void nfc_magic_blink_start(NfcMagic* nfc_magic) { | ||||
|     notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_blue); | ||||
|     notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_cyan); | ||||
| } | ||||
| 
 | ||||
| void nfc_magic_blink_stop(NfcMagic* nfc_magic) { | ||||
|  | ||||
 Aleksandr Kutuzov
						Aleksandr Kutuzov