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/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm | ||||||
| 
 | 
 | ||||||
|  | /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov | ||||||
|  | 
 | ||||||
| # Assets | # Assets | ||||||
| /assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | /assets/resources/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										20
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -60,8 +60,9 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           set -e |           set -e | ||||||
|           for TARGET in ${TARGETS}; do |           for TARGET in ${TARGETS}; do | ||||||
|                 ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ |             TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ | ||||||
|                 copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} |             ./fbt TARGET_HW=$TARGET copro_dist updater_package \ | ||||||
|  |             ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||||
|           done |           done | ||||||
| 
 | 
 | ||||||
|       - name: 'Move upload files' |       - name: 'Move upload files' | ||||||
| @ -95,14 +96,14 @@ jobs: | |||||||
| 
 | 
 | ||||||
|       - name: 'Upload map analyser files to storage' |       - name: 'Upload map analyser files to storage' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|         uses: keithweaver/aws-s3-github-action@v1.0.0 |         uses: prewk/s3-cp-action@v2 | ||||||
|         with: |         with: | ||||||
|           source: map_analyser_files/ |           aws_s3_endpoint: "${{ secrets.MAP_REPORT_AWS_ENDPOINT }}" | ||||||
|           destination: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" |  | ||||||
|           aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}" |           aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}" | ||||||
|           aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}" |           aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}" | ||||||
|           aws_region: "${{ secrets.MAP_REPORT_AWS_REGION }}" |           source: "./map_analyser_files/" | ||||||
|           flags: --recursive |           dest: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}" | ||||||
|  |           flags: "--recursive --acl public-read" | ||||||
| 
 | 
 | ||||||
|       - name: 'Trigger map file reporter' |       - name: 'Trigger map file reporter' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
| @ -113,7 +114,6 @@ jobs: | |||||||
|           event-type: map-file-analyse |           event-type: map-file-analyse | ||||||
|           client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}' |           client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}' | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|       - name: 'Upload artifacts to update server' |       - name: 'Upload artifacts to update server' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|         run: | |         run: | | ||||||
| @ -186,6 +186,6 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           set -e |           set -e | ||||||
|           for TARGET in ${TARGETS}; do |           for TARGET in ${TARGETS}; do | ||||||
|                 ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ |             TARGET="$(echo "${TARGET}" | sed 's/f//')"; \ | ||||||
|                 updater_package DEBUG=0 COMPACT=1 |             ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package | ||||||
|           done |           done | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -54,17 +54,16 @@ jobs: | |||||||
|           ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 |           ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 | ||||||
|           echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT |           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) }} |         if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }} | ||||||
|         run: | |         uses: prewk/s3-cp-action@v2 | ||||||
|           mkdir -p ~/.ssh |         with: | ||||||
|           ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts |           aws_s3_endpoint: "${{ secrets.PVS_AWS_ENDPOINT }}" | ||||||
|           echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; |           aws_access_key_id: "${{ secrets.PVS_AWS_ACCESS_KEY }}" | ||||||
|           chmod 600 ./deploy_key; |           aws_secret_access_key: "${{ secrets.PVS_AWS_SECRET_KEY }}" | ||||||
|           rsync -avrzP --mkpath \ |           source: "./build/f7-firmware-DC/pvsreport" | ||||||
|               -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ |           dest: "s3://${{ secrets.PVS_AWS_BUCKET }}/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/" | ||||||
|               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}}/"; |           flags: "--recursive --acl public-read" | ||||||
|           rm ./deploy_key; |  | ||||||
| 
 | 
 | ||||||
|       - name: 'Find Previous Comment' |       - name: 'Find Previous Comment' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} |         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 }} |           issue-number: ${{ github.event.pull_request.number }} | ||||||
|           body: | |           body: | | ||||||
|             **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** |             **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 |           edit-mode: replace | ||||||
| 
 | 
 | ||||||
|       - name: 'Raise exception' |       - name: 'Raise exception' | ||||||
|         if: ${{ steps.pvs-warn.outputs.warnings != 0 }} |         if: ${{ steps.pvs-warn.outputs.warnings != 0 }} | ||||||
|         run: | |         run: | | ||||||
|           echo "Please fix all PVS varnings before merge" |           echo "Please fix all PVS warnings before merge" | ||||||
|           exit 1 |           exit 1 | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,4 +1,5 @@ | |||||||
| *.swp | *.swp | ||||||
|  | *.swo | ||||||
| *.gdb_history | *.gdb_history | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -11,5 +11,8 @@ | |||||||
| 		"augustocdias.tasks-shell-input" | 		"augustocdias.tasks-shell-input" | ||||||
| 	], | 	], | ||||||
| 	// List of extensions recommended by VS Code that should not be recommended for users of this workspace. | 	// 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() | AccessorApp::AccessorApp() | ||||||
|     : text_store{0} { |     : text_store{0} { | ||||||
|     notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION)); |     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(); |     furi_hal_power_enable_otg(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ App( | |||||||
|     appid="accessor", |     appid="accessor", | ||||||
|     name="Accessor", |     name="Accessor", | ||||||
|     apptype=FlipperAppType.DEBUG, |     apptype=FlipperAppType.DEBUG, | ||||||
|  |     targets=["f7"], | ||||||
|     entry_point="accessor_app", |     entry_point="accessor_app", | ||||||
|     cdefines=["APP_ACCESSOR"], |     cdefines=["APP_ACCESSOR"], | ||||||
|     requires=["gui"], |     requires=["gui"], | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "bt_carrier_test.h" | #include "bt_carrier_test.h" | ||||||
| #include "bt_test.h" | #include "bt_test.h" | ||||||
| #include "bt_test_types.h" | #include "bt_test_types.h" | ||||||
| #include "furi_hal_bt.h" | #include <furi_hal_bt.h> | ||||||
| 
 | 
 | ||||||
| struct BtCarrierTest { | struct BtCarrierTest { | ||||||
|     BtTest* bt_test; |     BtTest* bt_test; | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "bt_packet_test.h" | #include "bt_packet_test.h" | ||||||
| #include "bt_test.h" | #include "bt_test.h" | ||||||
| #include "bt_test_types.h" | #include "bt_test_types.h" | ||||||
| #include "furi_hal_bt.h" | #include <furi_hal_bt.h> | ||||||
| 
 | 
 | ||||||
| struct BtPacketTest { | struct BtPacketTest { | ||||||
|     BtTest* bt_test; |     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 "file_browser_app_i.h" | ||||||
| #include "gui/modules/file_browser.h" | #include <file_browser_test_icons.h> | ||||||
| #include <furi.h> | 
 | ||||||
| #include <furi_hal.h> | #include <gui/modules/file_browser.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <lib/toolbox/path.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) { | static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ App( | |||||||
|     appid="lfrfid_debug", |     appid="lfrfid_debug", | ||||||
|     name="LF-RFID Debug", |     name="LF-RFID Debug", | ||||||
|     apptype=FlipperAppType.DEBUG, |     apptype=FlipperAppType.DEBUG, | ||||||
|  |     targets=["f7"], | ||||||
|     entry_point="lfrfid_debug_app", |     entry_point="lfrfid_debug_app", | ||||||
|     requires=[ |     requires=[ | ||||||
|         "gui", |         "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); |     memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2); | ||||||
| 
 | 
 | ||||||
|     MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data; |     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}; |     uint8_t manufacturer_block[16] = {0}; | ||||||
|     memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); |     memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); | ||||||
|     mu_assert( |     mu_assert( | ||||||
|         memcmp(manufacturer_block, uid, uid_len) == 0, |         memcmp(manufacturer_block, uid, uid_len) == 0, | ||||||
|         "manufacturer_block uid doesn't match the file\r\n"); |         "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( |         mu_assert( | ||||||
|             manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n"); |             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); |     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_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback); | ||||||
|     rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); |     rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); | ||||||
|     rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); |     rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); | ||||||
|  | |||||||
| @ -12,8 +12,9 @@ | |||||||
| #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") | #define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes") | ||||||
| #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") | #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 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_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 | #define TEST_TIMEOUT 10000 | ||||||
| 
 | 
 | ||||||
| static SubGhzEnvironment* environment_handler; | static SubGhzEnvironment* environment_handler; | ||||||
| @ -43,6 +44,8 @@ static void subghz_test_init(void) { | |||||||
|         environment_handler, CAME_ATOMO_DIR_NAME); |         environment_handler, CAME_ATOMO_DIR_NAME); | ||||||
|     subghz_environment_set_nice_flor_s_rainbow_table_file_name( |     subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||||
|         environment_handler, NICE_FLOR_S_DIR_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( |     subghz_environment_set_protocol_registry( | ||||||
|         environment_handler, (void*)&subghz_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"); |         "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_TEST(subghz_decoder_megacode_test) { | ||||||
|     mu_assert( |     mu_assert( | ||||||
|         subghz_decoder_test( |         subghz_decoder_test( | ||||||
| @ -604,6 +615,36 @@ MU_TEST(subghz_decoder_holtek_ht12x_test) { | |||||||
|         "Test decoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); |         "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
 | //test encoders
 | ||||||
| MU_TEST(subghz_encoder_princeton_test) { | MU_TEST(subghz_encoder_princeton_test) { | ||||||
|     mu_assert( |     mu_assert( | ||||||
| @ -647,6 +688,12 @@ MU_TEST(subghz_encoder_linear_test) { | |||||||
|         "Test encoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n"); |         "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_TEST(subghz_encoder_megacode_test) { | ||||||
|     mu_assert( |     mu_assert( | ||||||
|         subghz_encoder_test(EXT_PATH("unit_tests/subghz/megacode.sub")), |         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"); |         "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_TEST(subghz_random_test) { | ||||||
|     mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); |     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_somfy_telis_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_star_line_test); |     MU_RUN_TEST(subghz_decoder_star_line_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_linear_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_megacode_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_secplus_v1_test); |     MU_RUN_TEST(subghz_decoder_secplus_v1_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_secplus_v2_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_ansonic_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_smc5326_test); |     MU_RUN_TEST(subghz_decoder_smc5326_test); | ||||||
|     MU_RUN_TEST(subghz_decoder_holtek_ht12x_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_princeton_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_came_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_nice_flo_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_keelog_test); |     MU_RUN_TEST(subghz_encoder_keelog_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_linear_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_megacode_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_holtek_test); |     MU_RUN_TEST(subghz_encoder_holtek_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_secplus_v1_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_ansonic_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_smc5326_test); |     MU_RUN_TEST(subghz_encoder_smc5326_test); | ||||||
|     MU_RUN_TEST(subghz_encoder_holtek_ht12x_test); |     MU_RUN_TEST(subghz_encoder_holtek_ht12x_test); | ||||||
|  |     MU_RUN_TEST(subghz_encoder_dooya_test); | ||||||
| 
 | 
 | ||||||
|     MU_RUN_TEST(subghz_random_test); |     MU_RUN_TEST(subghz_random_test); | ||||||
|     subghz_test_deinit(); |     subghz_test_deinit(); | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ void minunit_print_progress() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void minunit_print_fail(const char* str) { | 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) { | 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_files.h" | ||||||
| #include "archive_apps.h" | #include "archive_apps.h" | ||||||
| #include "archive_browser.h" | #include "archive_browser.h" | ||||||
|  | #include "../views/archive_browser_view.h" | ||||||
|  | 
 | ||||||
| #include <core/common_defines.h> | #include <core/common_defines.h> | ||||||
| #include <core/log.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 <fap_loader/fap_loader_app.h> | ||||||
| #include <math.h> | #include <math.h> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,14 +1,15 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include "../helpers/archive_files.h" | ||||||
|  | #include "../helpers/archive_favorites.h" | ||||||
|  | 
 | ||||||
| #include <gui/gui_i.h> | #include <gui/gui_i.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <furi.h> | #include <gui/modules/file_browser_worker.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include "../helpers/archive_files.h" | #include <furi.h> | ||||||
| #include "../helpers/archive_favorites.h" |  | ||||||
| #include "gui/modules/file_browser_worker.h" |  | ||||||
| 
 | 
 | ||||||
| #define MAX_LEN_PX 110 | #define MAX_LEN_PX 110 | ||||||
| #define MAX_NAME_LEN 255 | #define MAX_NAME_LEN 255 | ||||||
|  | |||||||
| @ -1,9 +1,12 @@ | |||||||
| #include "bad_usb_app_i.h" | #include "bad_usb_app_i.h" | ||||||
|  | #include "bad_usb_settings_filename.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <lib/toolbox/path.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) { | static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     BadUsbApp* app = 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); |     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* bad_usb_app_alloc(char* arg) { | ||||||
|     BadUsbApp* app = malloc(sizeof(BadUsbApp)); |     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)) { |     if(arg && strlen(arg)) { | ||||||
|         furi_string_set(app->file_path, arg); |         furi_string_set(app->file_path, arg); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bad_usb_load_settings(app); | ||||||
|  | 
 | ||||||
|     app->gui = furi_record_open(RECORD_GUI); |     app->gui = furi_record_open(RECORD_GUI); | ||||||
|     app->notifications = furi_record_open(RECORD_NOTIFICATION); |     app->notifications = furi_record_open(RECORD_NOTIFICATION); | ||||||
|     app->dialogs = furi_record_open(RECORD_DIALOGS); |     app->dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
| @ -53,6 +103,10 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { | |||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget)); |         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(); |     app->bad_usb_view = bad_usb_alloc(); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_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()) { |     if(furi_hal_usb_is_locked()) { | ||||||
|         app->error = BadUsbAppErrorCloseRpc; |         app->error = BadUsbAppErrorCloseRpc; | ||||||
|  |         app->usb_if_prev = NULL; | ||||||
|         scene_manager_next_scene(app->scene_manager, BadUsbSceneError); |         scene_manager_next_scene(app->scene_manager, BadUsbSceneError); | ||||||
|     } else { |     } 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)) { |         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); |             scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); | ||||||
|         } else { |         } 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); |             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) { | void bad_usb_app_free(BadUsbApp* app) { | ||||||
|     furi_assert(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
 |     // Views
 | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); |     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); | ||||||
|     bad_usb_free(app->bad_usb_view); |     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); |     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError); | ||||||
|     widget_free(app->widget); |     widget_free(app->widget); | ||||||
| 
 | 
 | ||||||
|  |     // Submenu
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); | ||||||
|  |     submenu_free(app->submenu); | ||||||
|  | 
 | ||||||
|     // View dispatcher
 |     // View dispatcher
 | ||||||
|     view_dispatcher_free(app->view_dispatcher); |     view_dispatcher_free(app->view_dispatcher); | ||||||
|     scene_manager_free(app->scene_manager); |     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_NOTIFICATION); | ||||||
|     furi_record_close(RECORD_DIALOGS); |     furi_record_close(RECORD_DIALOGS); | ||||||
| 
 | 
 | ||||||
|  |     bad_usb_save_settings(app); | ||||||
|  | 
 | ||||||
|     furi_string_free(app->file_path); |     furi_string_free(app->file_path); | ||||||
|  |     furi_string_free(app->keyboard_layout); | ||||||
| 
 | 
 | ||||||
|     free(app); |     free(app); | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,9 +14,12 @@ | |||||||
| #include <gui/modules/variable_item_list.h> | #include <gui/modules/variable_item_list.h> | ||||||
| #include <gui/modules/widget.h> | #include <gui/modules/widget.h> | ||||||
| #include "views/bad_usb_view.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_BASE_FOLDER ANY_PATH("badusb") | ||||||
| #define BAD_USB_APP_EXTENSION ".txt" | #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 { | typedef enum { | ||||||
|     BadUsbAppErrorNoFiles, |     BadUsbAppErrorNoFiles, | ||||||
| @ -30,14 +33,19 @@ struct BadUsbApp { | |||||||
|     NotificationApp* notifications; |     NotificationApp* notifications; | ||||||
|     DialogsApp* dialogs; |     DialogsApp* dialogs; | ||||||
|     Widget* widget; |     Widget* widget; | ||||||
|  |     Submenu* submenu; | ||||||
| 
 | 
 | ||||||
|     BadUsbAppError error; |     BadUsbAppError error; | ||||||
|     FuriString* file_path; |     FuriString* file_path; | ||||||
|  |     FuriString* keyboard_layout; | ||||||
|     BadUsb* bad_usb_view; |     BadUsb* bad_usb_view; | ||||||
|     BadUsbScript* bad_usb_script; |     BadUsbScript* bad_usb_script; | ||||||
|  | 
 | ||||||
|  |     FuriHalUsbInterface* usb_if_prev; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     BadUsbAppViewError, |     BadUsbAppViewError, | ||||||
|     BadUsbAppViewWork, |     BadUsbAppViewWork, | ||||||
|  |     BadUsbAppViewConfig, | ||||||
| } BadUsbAppView; | } BadUsbAppView; | ||||||
| @ -16,6 +16,9 @@ | |||||||
| #define SCRIPT_STATE_END (-2) | #define SCRIPT_STATE_END (-2) | ||||||
| #define SCRIPT_STATE_NEXT_LINE (-3) | #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 { | typedef enum { | ||||||
|     WorkerEvtToggle = (1 << 0), |     WorkerEvtToggle = (1 << 0), | ||||||
|     WorkerEvtEnd = (1 << 1), |     WorkerEvtEnd = (1 << 1), | ||||||
| @ -28,6 +31,7 @@ struct BadUsbScript { | |||||||
|     BadUsbState st; |     BadUsbState st; | ||||||
|     FuriString* file_path; |     FuriString* file_path; | ||||||
|     uint32_t defdelay; |     uint32_t defdelay; | ||||||
|  |     uint16_t layout[128]; | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     uint8_t file_buf[FILE_BUFFER_LEN + 1]; |     uint8_t file_buf[FILE_BUFFER_LEN + 1]; | ||||||
|     uint8_t buf_start; |     uint8_t buf_start; | ||||||
| @ -205,10 +209,10 @@ static bool ducky_altstring(const char* param) { | |||||||
|     return state; |     return state; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool ducky_string(const char* param) { | static bool ducky_string(BadUsbScript* bad_usb, const char* param) { | ||||||
|     uint32_t i = 0; |     uint32_t i = 0; | ||||||
|     while(param[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) { |         if(keycode != HID_KEYBOARD_NONE) { | ||||||
|             furi_hal_hid_kb_press(keycode); |             furi_hal_hid_kb_press(keycode); | ||||||
|             furi_hal_hid_kb_release(keycode); |             furi_hal_hid_kb_release(keycode); | ||||||
| @ -218,7 +222,7 @@ static bool ducky_string(const char* param) { | |||||||
|     return true; |     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++) { |     for(size_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) { | ||||||
|         size_t key_cmd_len = strlen(ducky_keys[i].name); |         size_t key_cmd_len = strlen(ducky_keys[i].name); | ||||||
|         if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && |         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)) { |     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; |     return 0; | ||||||
| } | } | ||||||
| @ -276,7 +280,7 @@ static int32_t | |||||||
|     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { |     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { | ||||||
|         // STRING
 |         // STRING
 | ||||||
|         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; |         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) { |         if(!state && error != NULL) { | ||||||
|             snprintf(error, error_len, "Invalid string %s", line_tmp); |             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) { |     } else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) { | ||||||
|         // SYSRQ
 |         // SYSRQ
 | ||||||
|         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; |         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_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); | ||||||
|         furi_hal_hid_kb_press(key); |         furi_hal_hid_kb_press(key); | ||||||
|         furi_hal_hid_kb_release_all(); |         furi_hal_hid_kb_release_all(); | ||||||
|         return (0); |         return (0); | ||||||
|     } else { |     } else { | ||||||
|         // Special keys + modifiers
 |         // 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(key == HID_KEYBOARD_NONE) { | ||||||
|             if(error != NULL) { |             if(error != NULL) { | ||||||
|                 snprintf(error, error_len, "No keycode defined for %s", line_tmp); |                 snprintf(error, error_len, "No keycode defined for %s", line_tmp); | ||||||
| @ -329,7 +333,7 @@ static int32_t | |||||||
|         if((key & 0xFF00) != 0) { |         if((key & 0xFF00) != 0) { | ||||||
|             // It's a modifier key
 |             // It's a modifier key
 | ||||||
|             line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; |             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_press(key); | ||||||
|         furi_hal_hid_kb_release(key); |         furi_hal_hid_kb_release(key); | ||||||
| @ -486,8 +490,6 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|     BadUsbWorkerState worker_state = BadUsbStateInit; |     BadUsbWorkerState worker_state = BadUsbStateInit; | ||||||
|     int32_t delay_val = 0; |     int32_t delay_val = 0; | ||||||
| 
 | 
 | ||||||
|     FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_I(WORKER_TAG, "Init"); |     FURI_LOG_I(WORKER_TAG, "Init"); | ||||||
|     File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); |     File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); | ||||||
|     bad_usb->line = furi_string_alloc(); |     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_hid_set_state_callback(NULL, NULL); | ||||||
| 
 | 
 | ||||||
|     furi_hal_usb_set_config(usb_mode_prev, NULL); |  | ||||||
| 
 |  | ||||||
|     storage_file_close(script_file); |     storage_file_close(script_file); | ||||||
|     storage_file_free(script_file); |     storage_file_free(script_file); | ||||||
|     furi_string_free(bad_usb->line); |     furi_string_free(bad_usb->line); | ||||||
| @ -650,12 +650,19 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|     return 0; |     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) { | BadUsbScript* bad_usb_script_open(FuriString* file_path) { | ||||||
|     furi_assert(file_path); |     furi_assert(file_path); | ||||||
| 
 | 
 | ||||||
|     BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); |     BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); | ||||||
|     bad_usb->file_path = furi_string_alloc(); |     bad_usb->file_path = furi_string_alloc(); | ||||||
|     furi_string_set(bad_usb->file_path, file_path); |     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.state = BadUsbStateInit; | ||||||
|     bad_usb->st.error[0] = '\0'; |     bad_usb->st.error[0] = '\0'; | ||||||
| @ -674,6 +681,30 @@ void bad_usb_script_close(BadUsbScript* bad_usb) { | |||||||
|     free(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) { | void bad_usb_script_toggle(BadUsbScript* bad_usb) { | ||||||
|     furi_assert(bad_usb); |     furi_assert(bad_usb); | ||||||
|     furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle); |     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_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_start(BadUsbScript* bad_usb); | ||||||
| 
 | 
 | ||||||
| void bad_usb_script_stop(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, file_select, FileSelect) | ||||||
| ADD_SCENE(bad_usb, work, Work) | ADD_SCENE(bad_usb, work, Work) | ||||||
| ADD_SCENE(bad_usb, error, Error) | 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 "../bad_usb_app_i.h" | ||||||
| #include "furi_hal_power.h" | #include <furi_hal_power.h> | ||||||
| #include "furi_hal_usb.h" | #include <furi_hal_usb.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| static bool bad_usb_file_select(BadUsbApp* bad_usb) { | static bool bad_usb_file_select(BadUsbApp* bad_usb) { | ||||||
|     furi_assert(bad_usb); |     furi_assert(bad_usb); | ||||||
| 
 | 
 | ||||||
|     DialogsFileBrowserOptions browser_options; |     DialogsFileBrowserOptions browser_options; | ||||||
|     dialog_file_browser_set_basic_options(&browser_options, BAD_USB_APP_EXTENSION, &I_badusb_10px); |     dialog_file_browser_set_basic_options( | ||||||
|     browser_options.base_path = BAD_USB_APP_PATH_FOLDER; |         &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
 |     // Input events and views are managed by file_browser
 | ||||||
|     bool res = dialog_file_browser_show( |     bool res = dialog_file_browser_show( | ||||||
| @ -21,12 +23,18 @@ void bad_usb_scene_file_select_on_enter(void* context) { | |||||||
|     BadUsbApp* bad_usb = context; |     BadUsbApp* bad_usb = context; | ||||||
| 
 | 
 | ||||||
|     furi_hal_usb_disable(); |     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)) { |     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); |         scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork); | ||||||
|     } else { |     } else { | ||||||
|         furi_hal_usb_enable(); |         furi_hal_usb_enable(); | ||||||
|         //scene_manager_previous_scene(bad_usb->scene_manager);
 |  | ||||||
|         view_dispatcher_stop(bad_usb->view_dispatcher); |         view_dispatcher_stop(bad_usb->view_dispatcher); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| #include "../bad_usb_script.h" | #include "../bad_usb_script.h" | ||||||
| #include "../bad_usb_app_i.h" | #include "../bad_usb_app_i.h" | ||||||
| #include "../views/bad_usb_view.h" | #include "../views/bad_usb_view.h" | ||||||
| #include "furi_hal.h" | #include <furi_hal.h> | ||||||
| #include "toolbox/path.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); |     furi_assert(context); | ||||||
|     BadUsbApp* app = 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) { | 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; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         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); |             bad_usb_script_toggle(app->bad_usb_script); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  |         } | ||||||
|     } else if(event.type == SceneManagerEventTypeTick) { |     } else if(event.type == SceneManagerEventTypeTick) { | ||||||
|         bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script)); |         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; |     FuriString* file_name; | ||||||
|     file_name = furi_string_alloc(); |     file_name = furi_string_alloc(); | ||||||
| 
 |  | ||||||
|     path_extract_filename(app->file_path, file_name, true); |     path_extract_filename(app->file_path, file_name, true); | ||||||
|     bad_usb_set_file_name(app->bad_usb_view, furi_string_get_cstr(file_name)); |     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); |     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_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); |     view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bad_usb_scene_work_on_exit(void* context) { | void bad_usb_scene_work_on_exit(void* context) { | ||||||
|     BadUsbApp* app = context; |     UNUSED(context); | ||||||
|     bad_usb_script_close(app->bad_usb_script); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| #include "bad_usb_view.h" | #include "bad_usb_view.h" | ||||||
| #include "../bad_usb_script.h" | #include "../bad_usb_script.h" | ||||||
|  | #include <toolbox/path.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| 
 | 
 | ||||||
| @ -7,12 +8,13 @@ | |||||||
| 
 | 
 | ||||||
| struct BadUsb { | struct BadUsb { | ||||||
|     View* view; |     View* view; | ||||||
|     BadUsbOkCallback callback; |     BadUsbButtonCallback callback; | ||||||
|     void* context; |     void* context; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     char file_name[MAX_NAME_LEN]; |     char file_name[MAX_NAME_LEN]; | ||||||
|  |     char layout[MAX_NAME_LEN]; | ||||||
|     BadUsbState state; |     BadUsbState state; | ||||||
|     uint8_t anim_frame; |     uint8_t anim_frame; | ||||||
| } BadUsbModel; | } BadUsbModel; | ||||||
| @ -25,9 +27,23 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     elements_string_fit_width(canvas, disp_str, 128 - 2); |     elements_string_fit_width(canvas, disp_str, 128 - 2); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
|     canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); |     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); |     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) || |     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || | ||||||
|        (model->state.state == BadUsbStateNotConnected)) { |        (model->state.state == BadUsbStateNotConnected)) { | ||||||
| @ -38,23 +54,28 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
|         elements_button_center(canvas, "Cancel"); |         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) { |     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_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect"); |         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB"); |         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to USB"); | ||||||
|     } else if(model->state.state == BadUsbStateWillRun) { |     } 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_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Will run"); |         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "on connect"); |         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); | ||||||
|     } else if(model->state.state == BadUsbStateFileError) { |     } 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_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "File"); |         canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "ERROR"); |         canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); | ||||||
|     } else if(model->state.state == BadUsbStateScriptError) { |     } 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_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); |         canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); | ||||||
|         canvas_set_font(canvas, FontSecondary); |         canvas_set_font(canvas, FontSecondary); | ||||||
| @ -64,49 +85,49 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
|         furi_string_reset(disp_str); |         furi_string_reset(disp_str); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); |         canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error); | ||||||
|     } else if(model->state.state == BadUsbStateIdle) { |     } 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_set_font(canvas, FontBigNumbers); | ||||||
|         canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "0"); |         canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); | ||||||
|         canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14); |         canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); | ||||||
|     } else if(model->state.state == BadUsbStateRunning) { |     } else if(model->state.state == BadUsbStateRunning) { | ||||||
|         if(model->anim_frame == 0) { |         if(model->anim_frame == 0) { | ||||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21); |             canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); | ||||||
|         } else { |         } else { | ||||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviSmile2_18x21); |             canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); | ||||||
|         } |         } | ||||||
|         canvas_set_font(canvas, FontBigNumbers); |         canvas_set_font(canvas, FontBigNumbers); | ||||||
|         furi_string_printf( |         furi_string_printf( | ||||||
|             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); |             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); | ||||||
|         canvas_draw_str_aligned( |         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); |         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) { |     } 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_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); |         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) { |     } else if(model->state.state == BadUsbStateDelay) { | ||||||
|         if(model->anim_frame == 0) { |         if(model->anim_frame == 0) { | ||||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviWaiting1_18x21); |             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); | ||||||
|         } else { |         } else { | ||||||
|             canvas_draw_icon(canvas, 4, 19, &I_EviWaiting2_18x21); |             canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); | ||||||
|         } |         } | ||||||
|         canvas_set_font(canvas, FontBigNumbers); |         canvas_set_font(canvas, FontBigNumbers); | ||||||
|         furi_string_printf( |         furi_string_printf( | ||||||
|             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); |             disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); | ||||||
|         canvas_draw_str_aligned( |         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); |         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); |         canvas_set_font(canvas, FontSecondary); | ||||||
|         furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); |         furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); | ||||||
|         canvas_draw_str_aligned( |         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); |         furi_string_reset(disp_str); | ||||||
|     } else { |     } else { | ||||||
|         canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); |         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     furi_string_free(disp_str); |     furi_string_free(disp_str); | ||||||
| @ -118,10 +139,10 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) { | |||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == InputTypeShort) { |     if(event->type == InputTypeShort) { | ||||||
|         if(event->key == InputKeyOk) { |         if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) { | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             furi_assert(bad_usb->callback); |             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; |     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(bad_usb); | ||||||
|     furi_assert(callback); |     furi_assert(callback); | ||||||
|     with_view_model( |     with_view_model( | ||||||
| @ -174,6 +195,14 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { | |||||||
|         true); |         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) { | void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { | ||||||
|     furi_assert(st); |     furi_assert(st); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| #include "../bad_usb_script.h" | #include "../bad_usb_script.h" | ||||||
| 
 | 
 | ||||||
| typedef struct BadUsb BadUsb; | typedef struct BadUsb BadUsb; | ||||||
| typedef void (*BadUsbOkCallback)(InputType type, void* context); | typedef void (*BadUsbButtonCallback)(InputKey key, void* context); | ||||||
| 
 | 
 | ||||||
| BadUsb* bad_usb_alloc(); | BadUsb* bad_usb_alloc(); | ||||||
| 
 | 
 | ||||||
| @ -12,8 +12,10 @@ void bad_usb_free(BadUsb* bad_usb); | |||||||
| 
 | 
 | ||||||
| View* bad_usb_get_view(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_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); | void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ GpioApp* gpio_app_alloc() { | |||||||
|     GpioApp* app = malloc(sizeof(GpioApp)); |     GpioApp* app = malloc(sizeof(GpioApp)); | ||||||
| 
 | 
 | ||||||
|     app->gui = furi_record_open(RECORD_GUI); |     app->gui = furi_record_open(RECORD_GUI); | ||||||
|  |     app->gpio_items = gpio_items_alloc(); | ||||||
| 
 | 
 | ||||||
|     app->view_dispatcher = view_dispatcher_alloc(); |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|     app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); |     app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); | ||||||
| @ -47,7 +48,7 @@ GpioApp* gpio_app_alloc() { | |||||||
|         app->view_dispatcher, |         app->view_dispatcher, | ||||||
|         GpioAppViewVarItemList, |         GpioAppViewVarItemList, | ||||||
|         variable_item_list_get_view(app->var_item_list)); |         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( |     view_dispatcher_add_view( | ||||||
|         app->view_dispatcher, GpioAppViewGpioTest, gpio_test_get_view(app->gpio_test)); |         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_GUI); | ||||||
|     furi_record_close(RECORD_NOTIFICATION); |     furi_record_close(RECORD_NOTIFICATION); | ||||||
| 
 | 
 | ||||||
|  |     gpio_items_free(app->gpio_items); | ||||||
|     free(app); |     free(app); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "gpio_app.h" | #include "gpio_app.h" | ||||||
| #include "gpio_item.h" | #include "gpio_items.h" | ||||||
| #include "scenes/gpio_scene.h" | #include "scenes/gpio_scene.h" | ||||||
| #include "gpio_custom_event.h" | #include "gpio_custom_event.h" | ||||||
| #include "usb_uart_bridge.h" | #include "usb_uart_bridge.h" | ||||||
| @ -28,6 +28,7 @@ struct GpioApp { | |||||||
|     VariableItem* var_item_flow; |     VariableItem* var_item_flow; | ||||||
|     GpioTest* gpio_test; |     GpioTest* gpio_test; | ||||||
|     GpioUsbUart* gpio_usb_uart; |     GpioUsbUart* gpio_usb_uart; | ||||||
|  |     GPIOItems* gpio_items; | ||||||
|     UsbUartBridge* usb_uart_bridge; |     UsbUartBridge* usb_uart_bridge; | ||||||
|     UsbUartConfig* usb_uart_cfg; |     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 "../gpio_app_i.h" | ||||||
| #include "furi_hal_power.h" | #include <furi_hal_power.h> | ||||||
| #include "furi_hal_usb.h" | #include <furi_hal_usb.h> | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
| enum GpioItem { | enum GpioItem { | ||||||
|  | |||||||
| @ -12,8 +12,9 @@ void gpio_scene_test_ok_callback(InputType type, void* context) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gpio_scene_test_on_enter(void* context) { | void gpio_scene_test_on_enter(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|     GpioApp* app = 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); |     gpio_test_set_ok_callback(app->gpio_test, gpio_scene_test_ok_callback, app); | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewGpioTest); |     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) { | void gpio_scene_test_on_exit(void* context) { | ||||||
|     UNUSED(context); |     furi_assert(context); | ||||||
|     gpio_item_configure_all_pins(GpioModeAnalog); |     GpioApp* app = context; | ||||||
|  |     gpio_items_configure_all_pins(app->gpio_items, GpioModeAnalog); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "../usb_uart_bridge.h" | #include "../usb_uart_bridge.h" | ||||||
| #include "../gpio_app_i.h" | #include "../gpio_app_i.h" | ||||||
| #include "furi_hal.h" | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     UsbUartLineIndexVcp, |     UsbUartLineIndexVcp, | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| #include "usb_uart_bridge.h" | #include "usb_uart_bridge.h" | ||||||
| #include "furi_hal.h" |  | ||||||
| #include <furi_hal_usb_cdc.h> |  | ||||||
| #include "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 <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_CDC_PKT_LEN CDC_DATA_SZ | ||||||
| #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) | #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "gpio_test.h" | #include "gpio_test.h" | ||||||
| #include "../gpio_item.h" | #include "../gpio_items.h" | ||||||
| 
 | 
 | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
| @ -11,6 +11,7 @@ struct GpioTest { | |||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t pin_idx; |     uint8_t pin_idx; | ||||||
|  |     GPIOItems* gpio_items; | ||||||
| } GpioTestModel; | } GpioTestModel; | ||||||
| 
 | 
 | ||||||
| static bool gpio_test_process_left(GpioTest* gpio_test); | 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( |     elements_multiline_text_aligned( | ||||||
|         canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change pin"); |         canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change pin"); | ||||||
|     elements_multiline_text_aligned( |     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) { | 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, |         gpio_test->view, | ||||||
|         GpioTestModel * model, |         GpioTestModel * model, | ||||||
|         { |         { | ||||||
|             if(model->pin_idx < GPIO_ITEM_COUNT) { |             if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { | ||||||
|                 model->pin_idx++; |                 model->pin_idx++; | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
| @ -80,17 +86,17 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { | |||||||
|         GpioTestModel * model, |         GpioTestModel * model, | ||||||
|         { |         { | ||||||
|             if(event->type == InputTypePress) { |             if(event->type == InputTypePress) { | ||||||
|                 if(model->pin_idx < GPIO_ITEM_COUNT) { |                 if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { | ||||||
|                     gpio_item_set_pin(model->pin_idx, true); |                     gpio_items_set_pin(model->gpio_items, model->pin_idx, true); | ||||||
|                 } else { |                 } else { | ||||||
|                     gpio_item_set_all_pins(true); |                     gpio_items_set_all_pins(model->gpio_items, true); | ||||||
|                 } |                 } | ||||||
|                 consumed = true; |                 consumed = true; | ||||||
|             } else if(event->type == InputTypeRelease) { |             } else if(event->type == InputTypeRelease) { | ||||||
|                 if(model->pin_idx < GPIO_ITEM_COUNT) { |                 if(model->pin_idx < gpio_items_get_count(model->gpio_items)) { | ||||||
|                     gpio_item_set_pin(model->pin_idx, false); |                     gpio_items_set_pin(model->gpio_items, model->pin_idx, false); | ||||||
|                 } else { |                 } else { | ||||||
|                     gpio_item_set_all_pins(false); |                     gpio_items_set_all_pins(model->gpio_items, false); | ||||||
|                 } |                 } | ||||||
|                 consumed = true; |                 consumed = true; | ||||||
|             } |             } | ||||||
| @ -101,11 +107,15 @@ static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { | |||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| GpioTest* gpio_test_alloc() { | GpioTest* gpio_test_alloc(GPIOItems* gpio_items) { | ||||||
|     GpioTest* gpio_test = malloc(sizeof(GpioTest)); |     GpioTest* gpio_test = malloc(sizeof(GpioTest)); | ||||||
| 
 | 
 | ||||||
|     gpio_test->view = view_alloc(); |     gpio_test->view = view_alloc(); | ||||||
|     view_allocate_model(gpio_test->view, ViewModelTypeLocking, sizeof(GpioTestModel)); |     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_context(gpio_test->view, gpio_test); | ||||||
|     view_set_draw_callback(gpio_test->view, gpio_test_draw_callback); |     view_set_draw_callback(gpio_test->view, gpio_test_draw_callback); | ||||||
|     view_set_input_callback(gpio_test->view, gpio_test_input_callback); |     view_set_input_callback(gpio_test->view, gpio_test_input_callback); | ||||||
|  | |||||||
| @ -1,11 +1,13 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include "../gpio_items.h" | ||||||
|  | 
 | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| 
 | 
 | ||||||
| typedef struct GpioTest GpioTest; | typedef struct GpioTest GpioTest; | ||||||
| typedef void (*GpioTestOkCallback)(InputType type, void* context); | 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); | void gpio_test_free(GpioTest* gpio_test); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "../usb_uart_bridge.h" | #include "../usb_uart_bridge.h" | ||||||
| #include "../gpio_app_i.h" | #include "../gpio_app_i.h" | ||||||
| #include "furi_hal.h" | #include <furi_hal.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
| struct GpioUsbUart { | struct GpioUsbUart { | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ App( | |||||||
|     appid="ibutton", |     appid="ibutton", | ||||||
|     name="iButton", |     name="iButton", | ||||||
|     apptype=FlipperAppType.APP, |     apptype=FlipperAppType.APP, | ||||||
|  |     targets=["f7"], | ||||||
|     entry_point="ibutton_app", |     entry_point="ibutton_app", | ||||||
|     cdefines=["APP_IBUTTON"], |     cdefines=["APP_IBUTTON"], | ||||||
|     requires=[ |     requires=[ | ||||||
|  | |||||||
| @ -271,7 +271,7 @@ void onewire_cli_print_usage() { | |||||||
| 
 | 
 | ||||||
| static void onewire_cli_search(Cli* cli) { | static void onewire_cli_search(Cli* cli) { | ||||||
|     UNUSED(cli); |     UNUSED(cli); | ||||||
|     OneWireHost* onewire = onewire_host_alloc(); |     OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio); | ||||||
|     uint8_t address[8]; |     uint8_t address[8]; | ||||||
|     bool done = false; |     bool done = false; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ App( | |||||||
|     name="Infrared", |     name="Infrared", | ||||||
|     apptype=FlipperAppType.APP, |     apptype=FlipperAppType.APP, | ||||||
|     entry_point="infrared_app", |     entry_point="infrared_app", | ||||||
|  |     targets=["f7"], | ||||||
|     cdefines=["APP_INFRARED"], |     cdefines=["APP_INFRARED"], | ||||||
|     requires=[ |     requires=[ | ||||||
|         "gui", |         "gui", | ||||||
|  | |||||||
| @ -86,7 +86,7 @@ static void infrared_cli_print_usage(void) { | |||||||
|     printf("\tir universal <remote_name> <signal_name>\r\n"); |     printf("\tir universal <remote_name> <signal_name>\r\n"); | ||||||
|     printf("\tir universal list <remote_name>\r\n"); |     printf("\tir universal list <remote_name>\r\n"); | ||||||
|     // TODO: Do not hardcode universal remote names
 |     // 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) { | 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_tv, UniversalTV) | ||||||
| ADD_SCENE(infrared, universal_ac, UniversalAC) | ADD_SCENE(infrared, universal_ac, UniversalAC) | ||||||
| ADD_SCENE(infrared, universal_audio, UniversalAudio) | ADD_SCENE(infrared, universal_audio, UniversalAudio) | ||||||
|  | ADD_SCENE(infrared, universal_projector, UniversalProjector) | ||||||
| ADD_SCENE(infrared, debug, Debug) | ADD_SCENE(infrared, debug, Debug) | ||||||
| ADD_SCENE(infrared, error_databases, ErrorDatabases) | ADD_SCENE(infrared, error_databases, ErrorDatabases) | ||||||
| ADD_SCENE(infrared, rpc, Rpc) | ADD_SCENE(infrared, rpc, Rpc) | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "../infrared_i.h" | #include "../infrared_i.h" | ||||||
| #include "gui/canvas.h" | #include <gui/canvas.h> | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     InfraredRpcStateIdle, |     InfraredRpcStateIdle, | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ typedef enum { | |||||||
|     SubmenuIndexUniversalTV, |     SubmenuIndexUniversalTV, | ||||||
|     SubmenuIndexUniversalAC, |     SubmenuIndexUniversalAC, | ||||||
|     SubmenuIndexUniversalAudio, |     SubmenuIndexUniversalAudio, | ||||||
|  |     SubmenuIndexUniversalProjector, | ||||||
| } SubmenuIndex; | } SubmenuIndex; | ||||||
| 
 | 
 | ||||||
| static void infrared_scene_universal_submenu_callback(void* context, uint32_t index) { | 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, |         SubmenuIndexUniversalAudio, | ||||||
|         infrared_scene_universal_submenu_callback, |         infrared_scene_universal_submenu_callback, | ||||||
|         context); |         context); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Projectors", | ||||||
|  |         SubmenuIndexUniversalProjector, | ||||||
|  |         infrared_scene_universal_submenu_callback, | ||||||
|  |         context); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, |         submenu, | ||||||
|         "Air Conditioners", |         "Air Conditioners", | ||||||
|         SubmenuIndexUniversalAC, |         SubmenuIndexUniversalAC, | ||||||
|         infrared_scene_universal_submenu_callback, |         infrared_scene_universal_submenu_callback, | ||||||
|         context); |         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); |     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) { |         } else if(event.event == SubmenuIndexUniversalAudio) { | ||||||
|             scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio); |             scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio); | ||||||
|             consumed = true; |             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; |     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 "infrared_debug_view.h" | ||||||
| 
 | 
 | ||||||
| #include <stdlib.h> |  | ||||||
| #include <string.h> |  | ||||||
| 
 |  | ||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
| #define INFRARED_DEBUG_TEXT_LENGTH 64 | #define INFRARED_DEBUG_TEXT_LENGTH 64 | ||||||
| 
 | 
 | ||||||
| struct InfraredDebugView { | 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 "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> | #include <stdint.h> | ||||||
| 
 | 
 | ||||||
| struct InfraredProgressView { | struct InfraredProgressView { | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ App( | |||||||
|     appid="lfrfid", |     appid="lfrfid", | ||||||
|     name="125 kHz RFID", |     name="125 kHz RFID", | ||||||
|     apptype=FlipperAppType.APP, |     apptype=FlipperAppType.APP, | ||||||
|  |     targets=["f7"], | ||||||
|     entry_point="lfrfid_app", |     entry_point="lfrfid_app", | ||||||
|     cdefines=["APP_LF_RFID"], |     cdefines=["APP_LF_RFID"], | ||||||
|     requires=[ |     requires=[ | ||||||
|  | |||||||
| @ -47,21 +47,28 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubmenuIndexRead) { |         if(event.event == SubmenuIndexRead) { | ||||||
|  |             scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexRead); | ||||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); |             scene_manager_next_scene(app->scene_manager, LfRfidSceneRead); | ||||||
|             DOLPHIN_DEED(DolphinDeedRfidRead); |             DOLPHIN_DEED(DolphinDeedRfidRead); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexSaved) { |         } 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); |             furi_string_set(app->file_path, LFRFID_APP_FOLDER); | ||||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectKey); |             scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectKey); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexAddManually) { |         } else if(event.event == SubmenuIndexAddManually) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, LfRfidSceneStart, SubmenuIndexAddManually); | ||||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveType); |             scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveType); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexExtraActions) { |         } else if(event.event == SubmenuIndexExtraActions) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, LfRfidSceneStart, SubmenuIndexExtraActions); | ||||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneExtraActions); |             scene_manager_next_scene(app->scene_manager, LfRfidSceneExtraActions); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|         scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, event.event); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ App( | |||||||
|     appid="nfc", |     appid="nfc", | ||||||
|     name="NFC", |     name="NFC", | ||||||
|     apptype=FlipperAppType.APP, |     apptype=FlipperAppType.APP, | ||||||
|  |     targets=["f7"], | ||||||
|     entry_point="nfc_app", |     entry_point="nfc_app", | ||||||
|     cdefines=["APP_NFC"], |     cdefines=["APP_NFC"], | ||||||
|     requires=[ |     requires=[ | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "nfc_i.h" | #include "nfc_i.h" | ||||||
| #include "furi_hal_nfc.h" | #include <furi_hal_nfc.h> | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
| bool nfc_custom_event_callback(void* context, uint32_t event) { | 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, extra_actions, ExtraActions) | ||||||
| ADD_SCENE(nfc, set_type, SetType) | ADD_SCENE(nfc, set_type, SetType) | ||||||
| ADD_SCENE(nfc, set_sak, SetSak) | 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, set_uid, SetUid) | ||||||
| ADD_SCENE(nfc, generate_info, GenerateInfo) | ADD_SCENE(nfc, generate_info, GenerateInfo) | ||||||
| ADD_SCENE(nfc, read_card_success, ReadCardSuccess) | 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); |                     nfc->scene_manager, NfcSceneMfClassicKeys); | ||||||
|             } else { |             } else { | ||||||
|                 consumed = scene_manager_search_and_switch_to_previous_scene( |                 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, |         SubmenuIndexMfUltralightUnlock, | ||||||
|         nfc_scene_extra_actions_submenu_callback, |         nfc_scene_extra_actions_submenu_callback, | ||||||
|         nfc); |         nfc); | ||||||
|  |     submenu_set_selected_item( | ||||||
|  |         submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions)); | ||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ void nfc_scene_set_atqa_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     // Setup view
 |     // Setup view
 | ||||||
|     ByteInput* byte_input = nfc->byte_input; |     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_set_result_callback( | ||||||
|         byte_input, |         byte_input, | ||||||
|         nfc_scene_set_atqa_byte_input_callback, |         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.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == NfcCustomEventByteInputDone) { |         if(event.event == NfcCustomEventByteInputDone) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqua); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqa); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ void nfc_scene_set_uid_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     // Setup view
 |     // Setup view
 | ||||||
|     ByteInput* byte_input = nfc->byte_input; |     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; |     nfc->dev_edit_data = nfc->dev->dev_data.nfc_data; | ||||||
|     byte_input_set_result_callback( |     byte_input_set_result_callback( | ||||||
|         byte_input, |         byte_input, | ||||||
|  | |||||||
| @ -48,11 +48,14 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubmenuIndexRead) { |         if(event.event == SubmenuIndexRead) { | ||||||
|  |             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); | ||||||
|             nfc->dev->dev_data.read_mode = NfcReadModeAuto; |             nfc->dev->dev_data.read_mode = NfcReadModeAuto; | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|             DOLPHIN_DEED(DolphinDeedNfcRead); |             DOLPHIN_DEED(DolphinDeedNfcRead); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexDetectReader) { |         } 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; |             bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; | ||||||
|             if(sd_exist) { |             if(sd_exist) { | ||||||
|                 nfc_device_data_clear(&nfc->dev->dev_data); |                 nfc_device_data_clear(&nfc->dev->dev_data); | ||||||
| @ -63,19 +66,27 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|             } |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexSaved) { |         } 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); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexExtraAction) { |         } else if(event.event == SubmenuIndexExtraAction) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, NfcSceneStart, SubmenuIndexExtraAction); | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexAddManually) { |         } else if(event.event == SubmenuIndexAddManually) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually); | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexDebug) { |         } else if(event.event == SubmenuIndexDebug) { | ||||||
|  |             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, event.event); |  | ||||||
|     } |     } | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ App( | |||||||
|     appid="subghz", |     appid="subghz", | ||||||
|     name="Sub-GHz", |     name="Sub-GHz", | ||||||
|     apptype=FlipperAppType.APP, |     apptype=FlipperAppType.APP, | ||||||
|  |     targets=["f7"], | ||||||
|     entry_point="subghz_app", |     entry_point="subghz_app", | ||||||
|     cdefines=["APP_SUBGHZ"], |     cdefines=["APP_SUBGHZ"], | ||||||
|     requires=[ |     requires=[ | ||||||
| @ -11,7 +12,7 @@ App( | |||||||
|     ], |     ], | ||||||
|     provides=["subghz_start"], |     provides=["subghz_start"], | ||||||
|     icon="A_Sub1ghz_14", |     icon="A_Sub1ghz_14", | ||||||
|     stack_size=2 * 1024, |     stack_size=3 * 1024, | ||||||
|     order=10, |     order=10, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -28,10 +28,8 @@ bool subghz_scene_delete_success_on_event(void* context, SceneManagerEvent event | |||||||
|         if(event.event == SubGhzCustomEventSceneDeleteSuccess) { |         if(event.event == SubGhzCustomEventSceneDeleteSuccess) { | ||||||
|             if(scene_manager_search_and_switch_to_previous_scene( |             if(scene_manager_search_and_switch_to_previous_scene( | ||||||
|                    subghz->scene_manager, SubGhzSceneReadRAW)) { |                    subghz->scene_manager, SubGhzSceneReadRAW)) { | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); |  | ||||||
|             } else if(scene_manager_search_and_switch_to_previous_scene( |             } else if(scene_manager_search_and_switch_to_previous_scene( | ||||||
|                           subghz->scene_manager, SubGhzSceneSaved)) { |                           subghz->scene_manager, SubGhzSceneSaved)) { | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); |  | ||||||
|             } else { |             } else { | ||||||
|                 scene_manager_search_and_switch_to_previous_scene( |                 scene_manager_search_and_switch_to_previous_scene( | ||||||
|                     subghz->scene_manager, SubGhzSceneStart); |                     subghz->scene_manager, SubGhzSceneStart); | ||||||
|  | |||||||
| @ -411,5 +411,5 @@ void subghz_scene_read_raw_on_exit(void* context) { | |||||||
|     notification_message(subghz->notifications, &sequence_reset_rgb); |     notification_message(subghz->notifications, &sequence_reset_rgb); | ||||||
| 
 | 
 | ||||||
|     //filter restoration
 |     //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 "../subghz_i.h" | ||||||
| #include "../views/receiver.h" | #include "../views/receiver.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
|  | #include <lib/subghz/protocols/bin_raw.h> | ||||||
| 
 | 
 | ||||||
| static const NotificationSequence subghs_sequence_rx = { | static const NotificationSequence subghs_sequence_rx = { | ||||||
|     &message_green_255, |     &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); |     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); |     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_hopper_update(subghz); | ||||||
|             subghz_scene_receiver_update_statusbar(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) { |         switch(subghz->state_notifications) { | ||||||
|         case SubGhzNotificationStateRx: |         case SubGhzNotificationStateRx: | ||||||
|             notification_message(subghz->notifications, &sequence_blink_cyan_10); |             notification_message(subghz->notifications, &sequence_blink_cyan_10); | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ enum SubGhzSettingIndex { | |||||||
|     SubGhzSettingIndexFrequency, |     SubGhzSettingIndexFrequency, | ||||||
|     SubGhzSettingIndexHopping, |     SubGhzSettingIndexHopping, | ||||||
|     SubGhzSettingIndexModulation, |     SubGhzSettingIndexModulation, | ||||||
|  |     SubGhzSettingIndexBinRAW, | ||||||
|     SubGhzSettingIndexSound, |     SubGhzSettingIndexSound, | ||||||
|     SubGhzSettingIndexLock, |     SubGhzSettingIndexLock, | ||||||
|     SubGhzSettingIndexRAWThesholdRSSI, |     SubGhzSettingIndexRAWThesholdRSSI, | ||||||
| @ -58,6 +59,15 @@ const uint32_t speaker_value[SPEAKER_COUNT] = { | |||||||
|     SubGhzSpeakerStateShutdown, |     SubGhzSpeakerStateShutdown, | ||||||
|     SubGhzSpeakerStateEnable, |     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) { | uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { | ||||||
|     furi_assert(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]; |     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) { | static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { | ||||||
|     SubGhz* subghz = variable_item_get_context(item); |     SubGhz* subghz = variable_item_get_context(item); | ||||||
|     uint8_t index = variable_item_get_current_value_index(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( |     variable_item_set_current_value_text( | ||||||
|         item, subghz_setting_get_preset_name(subghz->setting, value_index)); |         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( |     item = variable_item_list_add( | ||||||
|         subghz->variable_item_list, |         subghz->variable_item_list, | ||||||
|         "Sound:", |         "Sound:", | ||||||
|  | |||||||
| @ -129,6 +129,21 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | |||||||
|                        subghz_history_get_raw_data( |                        subghz_history_get_raw_data( | ||||||
|                            subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { |                            subghz->txrx->history, subghz->txrx->idx_menu_chosen))) { | ||||||
|                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx); |                     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 { |                 } else { | ||||||
|                     subghz->state_notifications = SubGhzNotificationStateTx; |                     subghz->state_notifications = SubGhzNotificationStateTx; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -187,12 +187,15 @@ SubGhz* subghz_alloc() { | |||||||
|     subghz->txrx->environment = subghz_environment_alloc(); |     subghz->txrx->environment = subghz_environment_alloc(); | ||||||
|     subghz_environment_set_came_atomo_rainbow_table_file_name( |     subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||||
|         subghz->txrx->environment, EXT_PATH("subghz/assets/came_atomo")); |         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_environment_set_nice_flor_s_rainbow_table_file_name( | ||||||
|         subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s")); |         subghz->txrx->environment, EXT_PATH("subghz/assets/nice_flor_s")); | ||||||
|     subghz_environment_set_protocol_registry( |     subghz_environment_set_protocol_registry( | ||||||
|         subghz->txrx->environment, (void*)&subghz_protocol_registry); |         subghz->txrx->environment, (void*)&subghz_protocol_registry); | ||||||
|     subghz->txrx->receiver = subghz_receiver_alloc_init(subghz->txrx->environment); |     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_worker_set_overrun_callback( | ||||||
|         subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); |         subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); | ||||||
| @ -216,6 +219,8 @@ void subghz_free(SubGhz* subghz) { | |||||||
|         subghz->rpc_ctx = NULL; |         subghz->rpc_ctx = NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     subghz_speaker_off(subghz); | ||||||
|  | 
 | ||||||
|     // Packet Test
 |     // Packet Test
 | ||||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); |     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); | ||||||
|     subghz_test_packet_free(subghz->subghz_test_packet); |     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_load_keystore(environment, EXT_PATH("subghz/assets/keeloq_mfcodes_user")); | ||||||
|     subghz_environment_set_came_atomo_rainbow_table_file_name( |     subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||||
|         environment, EXT_PATH("subghz/assets/came_atomo")); |         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( |     subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||||
|         environment, EXT_PATH("subghz/assets/nice_flor_s")); |         environment, EXT_PATH("subghz/assets/nice_flor_s")); | ||||||
|     subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); |     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); |     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) { | void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { | ||||||
|     UNUSED(context); |     UNUSED(context); | ||||||
|     FuriString* file_name; |     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( |         subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||||
|             environment, EXT_PATH("subghz/assets/came_atomo")); |             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( |         subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||||
|             environment, EXT_PATH("subghz/assets/nice_flor_s")); |             environment, EXT_PATH("subghz/assets/nice_flor_s")); | ||||||
|         subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); |         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("\tchat <frequency:in Hz>\t - Chat with other Flippers\r\n"); | ||||||
|     printf( |     printf( | ||||||
|         "\ttx <3 byte Key: in hex> <frequency: in Hz> <te: us> <repeat: count>\t - Transmitting key\r\n"); |         "\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"); |     printf("\tdecode_raw <file_name: path_RAW_file>\t - Testing\r\n"); | ||||||
| 
 | 
 | ||||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { |     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { | ||||||
| @ -733,6 +813,11 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { | |||||||
|             break; |             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) { |         if(furi_string_cmp_str(cmd, "decode_raw") == 0) { | ||||||
|             subghz_cli_command_decode_raw(cli, args, context); |             subghz_cli_command_decode_raw(cli, args, context); | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| #define SUBGHZ_HISTORY_MAX 50 | #define SUBGHZ_HISTORY_MAX 50 | ||||||
|  | #define SUBGHZ_HISTORY_FREE_HEAP 20480 | ||||||
| #define TAG "SubGhzHistory" | #define TAG "SubGhzHistory" | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -121,6 +122,10 @@ FlipperFormat* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx | |||||||
| } | } | ||||||
| bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output) { | bool subghz_history_get_text_space_left(SubGhzHistory* instance, FuriString* output) { | ||||||
|     furi_assert(instance); |     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(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; |         return true; | ||||||
| @ -142,6 +147,7 @@ bool subghz_history_add_to_history( | |||||||
|     furi_assert(instance); |     furi_assert(instance); | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|  |     if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) return false; | ||||||
|     if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false; |     if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false; | ||||||
| 
 | 
 | ||||||
|     SubGhzProtocolDecoderBase* decoder_base = context; |     SubGhzProtocolDecoderBase* decoder_base = context; | ||||||
| @ -200,13 +206,13 @@ bool subghz_history_add_to_history( | |||||||
|         } |         } | ||||||
|         uint8_t key_data[sizeof(uint64_t)] = {0}; |         uint8_t key_data[sizeof(uint64_t)] = {0}; | ||||||
|         if(!flipper_format_read_hex(item->flipper_string, "Key", key_data, sizeof(uint64_t))) { |         if(!flipper_format_read_hex(item->flipper_string, "Key", key_data, sizeof(uint64_t))) { | ||||||
|             FURI_LOG_E(TAG, "Missing Key"); |             FURI_LOG_D(TAG, "No Key"); | ||||||
|             break; |  | ||||||
|         } |         } | ||||||
|         uint64_t data = 0; |         uint64_t data = 0; | ||||||
|         for(uint8_t i = 0; i < sizeof(uint64_t); i++) { |         for(uint8_t i = 0; i < sizeof(uint64_t); i++) { | ||||||
|             data = (data << 8) | key_data[i]; |             data = (data << 8) | key_data[i]; | ||||||
|         } |         } | ||||||
|  |         if(data != 0) { | ||||||
|             if(!(uint32_t)(data >> 32)) { |             if(!(uint32_t)(data >> 32)) { | ||||||
|                 furi_string_printf( |                 furi_string_printf( | ||||||
|                     item->item_str, |                     item->item_str, | ||||||
| @ -221,6 +227,10 @@ bool subghz_history_add_to_history( | |||||||
|                     (uint32_t)(data >> 32), |                     (uint32_t)(data >> 32), | ||||||
|                     (uint32_t)(data & 0xFFFFFFFF)); |                     (uint32_t)(data & 0xFFFFFFFF)); | ||||||
|             } |             } | ||||||
|  |         } else { | ||||||
|  |             furi_string_printf(item->item_str, "%s", furi_string_get_cstr(instance->tmp_string)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|     furi_string_free(text); |     furi_string_free(text); | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ struct SubGhzTxRx { | |||||||
|     SubGhzEnvironment* environment; |     SubGhzEnvironment* environment; | ||||||
|     SubGhzReceiver* receiver; |     SubGhzReceiver* receiver; | ||||||
|     SubGhzTransmitter* transmitter; |     SubGhzTransmitter* transmitter; | ||||||
|  |     SubGhzProtocolFlag filter; | ||||||
|     SubGhzProtocolDecoderBase* decoder_result; |     SubGhzProtocolDecoderBase* decoder_result; | ||||||
|     FlipperFormat* fff_data; |     FlipperFormat* fff_data; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,6 +12,8 @@ | |||||||
| #define MENU_ITEMS 4u | #define MENU_ITEMS 4u | ||||||
| #define UNLOCK_CNT 3 | #define UNLOCK_CNT 3 | ||||||
| 
 | 
 | ||||||
|  | #define SUBGHZ_RAW_TRESHOLD_MIN -90.0f | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     FuriString* item_str; |     FuriString* item_str; | ||||||
|     uint8_t type; |     uint8_t type; | ||||||
| @ -59,8 +61,24 @@ typedef struct { | |||||||
|     uint16_t list_offset; |     uint16_t list_offset; | ||||||
|     uint16_t history_item; |     uint16_t history_item; | ||||||
|     SubGhzViewReceiverBarShow bar_show; |     SubGhzViewReceiverBarShow bar_show; | ||||||
|  |     uint8_t u_rssi; | ||||||
| } SubGhzViewReceiverModel; | } 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) { | void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) { | ||||||
|     furi_assert(subghz_receiver); |     furi_assert(subghz_receiver); | ||||||
|     subghz_receiver->lock_count = 0; |     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); |     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) { | void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
| 
 | 
 | ||||||
|     elements_button_left(canvas, "Config"); |     elements_button_left(canvas, "Config"); | ||||||
|     canvas_draw_line(canvas, 46, 51, 125, 51); |  | ||||||
| 
 | 
 | ||||||
|     bool scrollbar = model->history_item > 4; |     bool scrollbar = model->history_item > 4; | ||||||
|     FuriString* str_buff; |     FuriString* str_buff; | ||||||
| @ -206,11 +233,11 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { | |||||||
|     if(model->history_item == 0) { |     if(model->history_item == 0) { | ||||||
|         canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); |         canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str(canvas, 63, 46, "Scanning..."); |         canvas_draw_str(canvas, 63, 44, "Scanning..."); | ||||||
|         canvas_draw_line(canvas, 46, 51, 125, 51); |  | ||||||
|         canvas_set_font(canvas, FontSecondary); |         canvas_set_font(canvas, FontSecondary); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     subghz_view_rssi_draw(canvas, model); | ||||||
|     switch(model->bar_show) { |     switch(model->bar_show) { | ||||||
|     case SubGhzViewReceiverBarShowLock: |     case SubGhzViewReceiverBarShowLock: | ||||||
|         canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); |         canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); | ||||||
|  | |||||||
| @ -8,6 +8,8 @@ typedef struct SubGhzViewReceiver SubGhzViewReceiver; | |||||||
| 
 | 
 | ||||||
| typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context); | 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_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard); | ||||||
| 
 | 
 | ||||||
| void subghz_view_receiver_set_callback( | 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) { | void subghz_frequency_analyzer_draw_log_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { | ||||||
|     uint8_t column_height = 6; |     uint8_t column_height = 6; | ||||||
|     if(rssi) { |     if(rssi) { | ||||||
|         //rssi = rssi
 |  | ||||||
|         if(rssi > 54) rssi = 54; |         if(rssi > 54) rssi = 54; | ||||||
|         for(uint8_t i = 1; i < rssi; i++) { |         for(uint8_t i = 1; i < rssi; i++) { | ||||||
|             if(i % 5) { |             if(i % 5) { | ||||||
|  | |||||||
| @ -84,9 +84,10 @@ void subghz_view_transmitter_draw(Canvas* canvas, SubGhzViewTransmitterModel* mo | |||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
|     elements_multiline_text(canvas, 0, 8, furi_string_get_cstr(model->key_str)); |     elements_multiline_text_aligned( | ||||||
|     canvas_draw_str(canvas, 78, 8, furi_string_get_cstr(model->frequency_str)); |         canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->key_str)); | ||||||
|     canvas_draw_str(canvas, 113, 8, furi_string_get_cstr(model->preset_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"); |     if(model->show_button) subghz_view_transmitter_button_right(canvas, "Send"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "../u2f_app_i.h" | #include "../u2f_app_i.h" | ||||||
| #include "../views/u2f_view.h" | #include "../views/u2f_view.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| #include "furi_hal.h" | #include <furi_hal.h> | ||||||
| #include "../u2f.h" | #include "../u2f.h" | ||||||
| 
 | 
 | ||||||
| #define U2F_REQUEST_TIMEOUT 500 | #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_WIDTH 9 | ||||||
| #define KEY_HEIGHT 12 | #define KEY_HEIGHT 12 | ||||||
| #define KEY_PADDING 1 | #define KEY_PADDING 1 | ||||||
| #define ROW_COUNT 6 | #define ROW_COUNT 7 | ||||||
| #define COLUMN_COUNT 12 | #define COLUMN_COUNT 12 | ||||||
| 
 | 
 | ||||||
| // 0 width items are not drawn, but there value is used
 | // 0 width items are not drawn, but there value is used
 | ||||||
| const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { | 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 = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, | ||||||
|         {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, |         {.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); |     canvas_set_font(canvas, FontKeyboard); | ||||||
|     // Start shifting the all keys up if on the next row (Scrolling)
 |     // 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++) { |     for(uint8_t y = initY; y < ROW_COUNT; y++) { | ||||||
|         const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; |         const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; | ||||||
|         uint8_t x = 0; |         uint8_t x = 0; | ||||||
| @ -365,7 +384,10 @@ HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { | |||||||
|     with_view_model( |     with_view_model( | ||||||
|         hid_keyboard->view, |         hid_keyboard->view, | ||||||
|         HidKeyboardModel * model, |         HidKeyboardModel * model, | ||||||
|         { model->transport = bt_hid->transport; }, |         { | ||||||
|  |             model->transport = bt_hid->transport; | ||||||
|  |             model->y = 1; | ||||||
|  |         }, | ||||||
|         true); |         true); | ||||||
| 
 | 
 | ||||||
|     return hid_keyboard; |     return hid_keyboard; | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ | |||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <lib/flipper_format/flipper_format.h> | #include <lib/flipper_format/flipper_format.h> | ||||||
| 
 | 
 | ||||||
|  | #include <math.h> | ||||||
| #include <m-array.h> | #include <m-array.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "MusicPlayerWorker" | #define TAG "MusicPlayerWorker" | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ App( | |||||||
|     appid="nfc_magic", |     appid="nfc_magic", | ||||||
|     name="Nfc Magic", |     name="Nfc Magic", | ||||||
|     apptype=FlipperAppType.EXTERNAL, |     apptype=FlipperAppType.EXTERNAL, | ||||||
|  |     targets=["f7"], | ||||||
|     entry_point="nfc_magic_app", |     entry_point="nfc_magic_app", | ||||||
|     requires=[ |     requires=[ | ||||||
|         "storage", |         "storage", | ||||||
|  | |||||||
| @ -136,9 +136,9 @@ void nfc_magic_free(NfcMagic* nfc_magic) { | |||||||
|     free(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_start_10, | ||||||
|     &message_blink_set_color_blue, |     &message_blink_set_color_cyan, | ||||||
|     &message_do_not_reset, |     &message_do_not_reset, | ||||||
|     NULL, |     NULL, | ||||||
| }; | }; | ||||||
| @ -149,7 +149,7 @@ static const NotificationSequence nfc_magic_sequence_blink_stop = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void nfc_magic_blink_start(NfcMagic* nfc_magic) { | 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) { | void nfc_magic_blink_stop(NfcMagic* nfc_magic) { | ||||||
|  | |||||||
 Aleksandr Kutuzov
						Aleksandr Kutuzov