Merge branch 'release-candidate' into release
This commit is contained in:
		
						commit
						fd29e190b7
					
				
							
								
								
									
										19
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -10,7 +10,8 @@ on: | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f6 | ||||
|   TARGETS: f6 f7 | ||||
|   DEFAULT_TARGET: f6 | ||||
| 
 | ||||
| jobs: | ||||
|   build: | ||||
| @ -69,7 +70,7 @@ jobs: | ||||
|           echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}" | ||||
|           echo "::set-output name=suffix::${SUFFIX}" | ||||
|           echo "::set-output name=short-hash::${SHA}" | ||||
|           echo "::set-output name=latest-target::${TARGETS[${#TARGETS[@]}-1]}" | ||||
|           echo "::set-output name=default-target::${DEFAULT_TARGET}" | ||||
| 
 | ||||
|       - name: 'Build bootloader in docker' | ||||
|         uses: ./.github/actions/docker | ||||
| @ -169,12 +170,7 @@ jobs: | ||||
|         run: | | ||||
|           test -d core2_firmware && rm -rf core2_firmware || true | ||||
|           mkdir core2_firmware | ||||
|           cp \ | ||||
|             lib/STM32CubeWB/package.xml \ | ||||
|             lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_FUS_fw.bin \ | ||||
|             lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_FUS_fw_for_fus_0_5_3.bin \ | ||||
|             lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/stm32wb5x_BLE_Stack_full_fw.bin \ | ||||
|             core2_firmware | ||||
|           ./scripts/assets.py copro lib/STM32CubeWB core2_firmware STM32WB5x | ||||
|           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz core2_firmware | ||||
| 
 | ||||
|       - name: 'Bundle scripts' | ||||
| @ -185,6 +181,7 @@ jobs: | ||||
|       - name: 'Bundle resources' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|           ./scripts/assets.py manifest assets/resources | ||||
|           tar czpf artifacts/flipper-z-any-resources-${{steps.names.outputs.suffix}}.tgz -C assets resources | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
| @ -205,7 +202,7 @@ jobs: | ||||
|         with: | ||||
|           args: -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} | ||||
| 
 | ||||
|       - name: Find Previous Comment | ||||
|       - name: 'Find Previous Comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} | ||||
|         uses: peter-evans/find-comment@v1 | ||||
|         id: fc | ||||
| @ -214,12 +211,12 @@ jobs: | ||||
|           comment-author: 'github-actions[bot]' | ||||
|           body-includes: 'to flash the' | ||||
| 
 | ||||
|       - name: Create or update comment | ||||
|       - name: 'Create or update comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} | ||||
|         uses: peter-evans/create-or-update-comment@v1 | ||||
|         with: | ||||
|           comment-id: ${{ steps.fc.outputs.comment-id }} | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           body: | | ||||
|             [Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.latest-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB. | ||||
|             [Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB. | ||||
|           edit-mode: replace | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							| @ -10,7 +10,7 @@ on: | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f6 | ||||
|   TARGETS: f6 f7 | ||||
| 
 | ||||
| jobs: | ||||
|   lint_c_cpp: | ||||
|  | ||||
							
								
								
									
										12
									
								
								ReadMe.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								ReadMe.md
									
									
									
									
									
								
							| @ -14,7 +14,7 @@ Our goal is to create nice and clean code with good documentation, to make it a | ||||
| 
 | ||||
| Flipper Zero's firmware consists of three components: | ||||
| 
 | ||||
| - Core2 firmware set - proprietary componenets by ST: FUS + radio stack. | ||||
| - Core2 firmware set - proprietary components by ST: FUS + radio stack. | ||||
| - Core1 Bootloader - controls basic hardware initialization and loads firmware | ||||
| - Core1 Firmware - HAL + OS + Drivers + Applications | ||||
| 
 | ||||
| @ -67,13 +67,13 @@ One liner: `./flash_core1_main.sh` | ||||
|    ``` | ||||
| 3. Prepare the container: | ||||
|    ```sh | ||||
|    docker compose up -d | ||||
|    docker-compose up -d | ||||
|    ``` | ||||
| 
 | ||||
| ## Compile bootloader | ||||
| 
 | ||||
| ```sh | ||||
| docker compose exec dev make -j$(nproc) -C bootloader TARGET=f6 | ||||
| docker-compose exec dev make -j$(nproc) -C bootloader TARGET=f6 | ||||
| ``` | ||||
| 
 | ||||
| Bootloader compilation results: | ||||
| @ -85,7 +85,7 @@ Bootloader compilation results: | ||||
| ## Compile firmware | ||||
| 
 | ||||
| ```sh | ||||
| docker compose exec dev make -j$(nproc) -C firmware TARGET=f6 | ||||
| docker-compose exec dev make -j$(nproc) -C firmware TARGET=f6 | ||||
| ``` | ||||
| 
 | ||||
| Firmware compilation results: | ||||
| @ -102,14 +102,14 @@ That's exactly how we generate our `full` builds. | ||||
| 
 | ||||
| 1. Concatenate HEX files: | ||||
|    ```sh | ||||
|    docker compose exec dev srec_cat \ | ||||
|    docker-compose exec dev srec_cat \ | ||||
|     bootloader/.obj/f6/bootloader.hex -Intel \ | ||||
|     firmware/.obj/f6/firmware.hex -Intel \ | ||||
|     -o firmware/.obj/f6/full.hex -Intel | ||||
|    ``` | ||||
| 2. Convert HEX to DFU: | ||||
|    ```sh | ||||
|    docker compose exec dev hex2dfu \ | ||||
|    docker-compose exec dev hex2dfu \ | ||||
|     -i firmware/.obj/f6/full.hex \ | ||||
|     -o firmware/.obj/f6/full.dfu \ | ||||
|     -l "Flipper Zero F6" | ||||
|  | ||||
| @ -1,831 +1,69 @@ | ||||
| #include "archive_i.h" | ||||
| 
 | ||||
| static bool archive_get_filenames(ArchiveApp* archive); | ||||
| 
 | ||||
| static void update_offset(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             size_t array_size = files_array_size(model->files); | ||||
|             uint16_t bounds = array_size > 3 ? 2 : array_size; | ||||
| 
 | ||||
|             if(array_size > 3 && model->idx >= array_size - 1) { | ||||
|                 model->list_offset = model->idx - 3; | ||||
|             } else if(model->list_offset < model->idx - bounds) { | ||||
|                 model->list_offset = CLAMP(model->list_offset + 1, array_size - bounds, 0); | ||||
|             } else if(model->list_offset > model->idx - bounds) { | ||||
|                 model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void archive_update_last_idx(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             archive->browser.last_idx[archive->browser.depth] = | ||||
|                 CLAMP(model->idx, files_array_size(model->files) - 1, 0); | ||||
|             model->idx = 0; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void archive_switch_dir(ArchiveApp* archive, const char* path) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(path); | ||||
|     string_set(archive->browser.path, path); | ||||
|     archive_get_filenames(archive); | ||||
|     update_offset(archive); | ||||
| } | ||||
| 
 | ||||
| static void archive_switch_tab(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             model->tab_idx = archive->browser.tab_id; | ||||
|             model->idx = 0; | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     archive->browser.depth = 0; | ||||
|     archive_switch_dir(archive, tab_default_paths[archive->browser.tab_id]); | ||||
| } | ||||
| 
 | ||||
| static void archive_leave_dir(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     char* last_char_ptr = strrchr(string_get_cstr(archive->browser.path), '/'); | ||||
| 
 | ||||
|     if(last_char_ptr) { | ||||
|         size_t pos = last_char_ptr - string_get_cstr(archive->browser.path); | ||||
|         string_left(archive->browser.path, pos); | ||||
|     } | ||||
| 
 | ||||
|     archive->browser.depth = CLAMP(archive->browser.depth - 1, MAX_DEPTH, 0); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             model->idx = archive->browser.last_idx[archive->browser.depth]; | ||||
|             model->list_offset = | ||||
|                 model->idx - | ||||
|                 (files_array_size(model->files) > 3 ? 3 : files_array_size(model->files)); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     archive_switch_dir(archive, string_get_cstr(archive->browser.path)); | ||||
|     update_offset(archive); | ||||
| } | ||||
| 
 | ||||
| static void archive_enter_dir(ArchiveApp* archive, string_t name) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     archive_update_last_idx(archive); | ||||
|     archive->browser.depth = CLAMP(archive->browser.depth + 1, MAX_DEPTH, 0); | ||||
| 
 | ||||
|     string_cat(archive->browser.path, "/"); | ||||
|     string_cat(archive->browser.path, archive->browser.name); | ||||
| 
 | ||||
|     archive_switch_dir(archive, string_get_cstr(archive->browser.path)); | ||||
| } | ||||
| 
 | ||||
| static bool filter_by_extension(ArchiveApp* archive, FileInfo* file_info, const char* name) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(file_info); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     const char* filter_ext_ptr = get_tab_ext(archive->browser.tab_id); | ||||
| 
 | ||||
|     if(strcmp(filter_ext_ptr, "*") == 0) { | ||||
|         result = true; | ||||
|     } else if(strstr(name, filter_ext_ptr) != NULL) { | ||||
|         result = true; | ||||
|     } else if(file_info->flags & FSF_DIRECTORY) { | ||||
|         result = true; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { | ||||
|     furi_assert(file); | ||||
|     furi_assert(file_info); | ||||
| 
 | ||||
|     for(size_t i = 0; i < SIZEOF_ARRAY(known_ext); i++) { | ||||
|         if(string_search_str(file->name, known_ext[i], 0) != STRING_FAILURE) { | ||||
|             file->type = i; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(file_info->flags & FSF_DIRECTORY) { | ||||
|         file->type = ArchiveFileTypeFolder; | ||||
|     } else { | ||||
|         file->type = ArchiveFileTypeUnknown; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void archive_file_append(ArchiveApp* archive, const char* path, string_t string) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(path); | ||||
|     furi_assert(string); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(false); | ||||
| 
 | ||||
|     if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { | ||||
|         FURI_LOG_E("Archive", "Append open error"); | ||||
|     } | ||||
| 
 | ||||
|     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { | ||||
|         FURI_LOG_E("Archive", "Append write error"); | ||||
|     } | ||||
| 
 | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_free(file_worker); | ||||
| } | ||||
| 
 | ||||
| static void archive_view_add_item(ArchiveApp* archive, FileInfo* file_info, const char* name) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(file_info); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     ArchiveFile_t item; | ||||
| 
 | ||||
|     if(filter_by_extension(archive, file_info, name)) { | ||||
|         ArchiveFile_t_init(&item); | ||||
|         string_init_set_str(item.name, name); | ||||
|         set_file_type(&item, file_info); | ||||
| 
 | ||||
|         with_view_model( | ||||
|             archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                 files_array_push_back(model->files, item); | ||||
|                 return true; | ||||
|             }); | ||||
| 
 | ||||
|         ArchiveFile_t_clear(&item); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool archive_is_favorite(ArchiveApp* archive, ArchiveFile_t* selected) { | ||||
|     furi_assert(selected); | ||||
|     string_t path; | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
|     bool found = false; | ||||
| 
 | ||||
|     string_init_printf( | ||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name)); | ||||
| 
 | ||||
|     bool load_result = | ||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); | ||||
| 
 | ||||
|     if(load_result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_search(buffer, path)) { | ||||
|                 found = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
|     file_worker_close(archive->file_worker); | ||||
| 
 | ||||
|     return found; | ||||
| } | ||||
| 
 | ||||
| static bool archive_favorites_read(ArchiveApp* archive) { | ||||
|     string_t buffer; | ||||
|     FileInfo file_info; | ||||
|     string_init(buffer); | ||||
| 
 | ||||
|     bool load_result = | ||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(load_result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             archive_view_add_item(archive, &file_info, string_get_cstr(buffer)); | ||||
|             string_clean(buffer); | ||||
|         } | ||||
|     } | ||||
|     string_clear(buffer); | ||||
|     file_worker_close(archive->file_worker); | ||||
| 
 | ||||
|     return load_result; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
|     archive_favorites_rename(ArchiveApp* archive, ArchiveFile_t* selected, const char* dst) { | ||||
|     furi_assert(selected); | ||||
|     string_t path; | ||||
|     string_t buffer; | ||||
|     string_t temp; | ||||
| 
 | ||||
|     string_init(buffer); | ||||
|     string_init(temp); | ||||
| 
 | ||||
|     string_init_printf( | ||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name)); | ||||
|     bool load_result = | ||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(load_result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             string_printf( | ||||
|                 temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||
|             archive_file_append(archive, ARCHIVE_FAV_TEMP_PATH, temp); | ||||
|             string_clean(temp); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(temp); | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
| 
 | ||||
|     file_worker_close(archive->file_worker); | ||||
|     file_worker_remove(archive->file_worker, ARCHIVE_FAV_PATH); | ||||
|     file_worker_rename(archive->file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||
| 
 | ||||
|     return load_result; | ||||
| } | ||||
| 
 | ||||
| static bool archive_favorites_delete(ArchiveApp* archive, ArchiveFile_t* selected) { | ||||
|     furi_assert(selected); | ||||
|     string_t path; | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
| 
 | ||||
|     string_init_printf( | ||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name)); | ||||
| 
 | ||||
|     bool load_result = | ||||
|         file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     if(load_result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(archive->file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if(string_search(buffer, path)) { | ||||
|                 string_t temp; | ||||
|                 string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); | ||||
|                 archive_file_append(archive, ARCHIVE_FAV_TEMP_PATH, temp); | ||||
|                 string_clear(temp); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
| 
 | ||||
|     file_worker_close(archive->file_worker); | ||||
|     file_worker_remove(archive->file_worker, ARCHIVE_FAV_PATH); | ||||
|     file_worker_rename(archive->file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||
| 
 | ||||
|     return load_result; | ||||
| } | ||||
| 
 | ||||
| static bool archive_read_dir(ArchiveApp* archive) { | ||||
|     FileInfo file_info; | ||||
|     File* directory = storage_file_alloc(archive->api); | ||||
|     char name[MAX_NAME_LEN]; | ||||
| 
 | ||||
|     if(!storage_dir_open(directory, string_get_cstr(archive->browser.path))) { | ||||
|         storage_dir_close(directory); | ||||
|         storage_file_free(directory); | ||||
|         return false; | ||||
|     } | ||||
|     while(1) { | ||||
|         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         uint16_t files_cnt; | ||||
|         with_view_model( | ||||
|             archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                 files_cnt = files_array_size(model->files); | ||||
| 
 | ||||
|                 return true; | ||||
|             }); | ||||
| 
 | ||||
|         if(files_cnt > MAX_FILES) { | ||||
|             break; | ||||
|         } else if(storage_file_get_error(directory) == FSE_OK) { | ||||
|             archive_view_add_item(archive, &file_info, name); | ||||
|         } else { | ||||
|             storage_dir_close(directory); | ||||
|             storage_file_free(directory); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     storage_dir_close(directory); | ||||
|     storage_file_free(directory); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool archive_get_filenames(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             files_array_clean(model->files); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     if(archive->browser.tab_id != ArchiveTabFavorites) { | ||||
|         archive_read_dir(archive); | ||||
|     } else { | ||||
|         archive_favorites_read(archive); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| static void archive_exit_callback(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     AppEvent event; | ||||
|     event.type = EventTypeExit; | ||||
|     furi_check(osMessageQueuePut(archive->event_queue, &event, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| static uint32_t archive_previous_callback(void* context) { | ||||
|     return ArchiveViewMain; | ||||
| } | ||||
| 
 | ||||
| /* file menu */ | ||||
| static void archive_add_to_favorites(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
|     string_t buffer_src; | ||||
| 
 | ||||
|     string_init_printf( | ||||
|         buffer_src, | ||||
|         "%s/%s\r\n", | ||||
|         string_get_cstr(archive->browser.path), | ||||
|         string_get_cstr(archive->browser.name)); | ||||
| 
 | ||||
|     archive_file_append(archive, ARCHIVE_FAV_PATH, buffer_src); | ||||
| 
 | ||||
|     string_clear(buffer_src); | ||||
| } | ||||
| 
 | ||||
| static void archive_text_input_callback(void* context) { | ||||
| bool archive_custom_event_callback(void* context, uint32_t event) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
| 
 | ||||
|     string_t buffer_src; | ||||
|     string_t buffer_dst; | ||||
| 
 | ||||
|     string_init_printf( | ||||
|         buffer_src, | ||||
|         "%s/%s", | ||||
|         string_get_cstr(archive->browser.path), | ||||
|         string_get_cstr(archive->browser.name)); | ||||
|     string_init_printf( | ||||
|         buffer_dst, | ||||
|         "%s/%s", | ||||
|         string_get_cstr(archive->browser.path), | ||||
|         archive->browser.text_input_buffer); | ||||
| 
 | ||||
|     string_set(archive->browser.name, archive->browser.text_input_buffer); | ||||
|     // append extension
 | ||||
| 
 | ||||
|     ArchiveFile_t* file; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             file = files_array_get( | ||||
|                 model->files, CLAMP(model->idx, files_array_size(model->files) - 1, 0)); | ||||
|             file->fav = archive_is_favorite(archive, file); | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     string_cat(buffer_dst, known_ext[file->type]); | ||||
|     storage_common_rename(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); | ||||
| 
 | ||||
|     if(file->fav) { | ||||
|         archive_favorites_rename(archive, file, string_get_cstr(buffer_dst)); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewMain); | ||||
|     archive_get_filenames(archive); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             model->idx = 0; | ||||
|             while(model->idx < files_array_size(model->files)) { | ||||
|                 ArchiveFile_t* current = files_array_get(model->files, model->idx); | ||||
|                 if(!string_search(current->name, archive->browser.text_input_buffer)) { | ||||
|                     break; | ||||
|                 } | ||||
|                 ++model->idx; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     update_offset(archive); | ||||
| 
 | ||||
|     string_clear(buffer_src); | ||||
|     string_clear(buffer_dst); | ||||
|     return scene_manager_handle_custom_event(archive->scene_manager, event); | ||||
| } | ||||
| 
 | ||||
| static void archive_enter_text_input(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
|     *archive->browser.text_input_buffer = '\0'; | ||||
| 
 | ||||
|     strlcpy( | ||||
|         archive->browser.text_input_buffer, string_get_cstr(archive->browser.name), MAX_NAME_LEN); | ||||
| 
 | ||||
|     archive_trim_file_ext(archive->browser.text_input_buffer); | ||||
| 
 | ||||
|     text_input_set_header_text(archive->text_input, "Rename:"); | ||||
| 
 | ||||
|     text_input_set_result_callback( | ||||
|         archive->text_input, | ||||
|         archive_text_input_callback, | ||||
|         archive, | ||||
|         archive->browser.text_input_buffer, | ||||
|         MAX_NAME_LEN, | ||||
|         false); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||
| } | ||||
| 
 | ||||
| static void archive_show_file_menu(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     archive->browser.menu = true; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             ArchiveFile_t* selected; | ||||
|             selected = files_array_get(model->files, model->idx); | ||||
|             model->menu = true; | ||||
|             model->menu_idx = 0; | ||||
|             selected->fav = is_known_app(selected->type) ? archive_is_favorite(archive, selected) : | ||||
|                                                            false; | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void archive_close_file_menu(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     archive->browser.menu = false; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             model->menu = false; | ||||
|             model->menu_idx = 0; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void archive_open_app(ArchiveApp* archive, const char* app_name, const char* args) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(app_name); | ||||
| 
 | ||||
|     loader_start(archive->loader, app_name, args); | ||||
| } | ||||
| 
 | ||||
| static void archive_delete_file(ArchiveApp* archive, ArchiveFile_t* file) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(file); | ||||
| 
 | ||||
|     string_t path; | ||||
|     string_init(path); | ||||
| 
 | ||||
|     string_printf( | ||||
|         path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(file->name)); | ||||
| 
 | ||||
|     if(archive_is_favorite(archive, file)) { // remove from favorites
 | ||||
|         archive_favorites_delete(archive, file); | ||||
|     } | ||||
| 
 | ||||
|     file_worker_remove(archive->file_worker, string_get_cstr(path)); | ||||
| 
 | ||||
|     string_clear(path); | ||||
|     archive_get_filenames(archive); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     update_offset(archive); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
|     archive_run_in_app(ArchiveApp* archive, ArchiveFile_t* selected, bool full_path_provided) { | ||||
|     string_t full_path; | ||||
| 
 | ||||
|     if(!full_path_provided) { | ||||
|         string_init_printf( | ||||
|             full_path, | ||||
|             "%s/%s", | ||||
|             string_get_cstr(archive->browser.path), | ||||
|             string_get_cstr(selected->name)); | ||||
|     } else { | ||||
|         string_init_set(full_path, selected->name); | ||||
|     } | ||||
| 
 | ||||
|     archive_open_app(archive, flipper_app_name[selected->type], string_get_cstr(full_path)); | ||||
|     string_clear(full_path); | ||||
| } | ||||
| 
 | ||||
| static void archive_file_menu_callback(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     ArchiveFile_t* selected; | ||||
|     uint8_t idx = 0; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             selected = files_array_get(model->files, model->idx); | ||||
|             idx = model->menu_idx; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     switch(idx) { | ||||
|     case 0: | ||||
|         if(is_known_app(selected->type)) { | ||||
|             archive_run_in_app(archive, selected, false); | ||||
|         } | ||||
|         break; | ||||
|     case 1: | ||||
|         if(is_known_app(selected->type)) { | ||||
|             if(!archive_is_favorite(archive, selected)) { | ||||
|                 string_set(archive->browser.name, selected->name); | ||||
|                 archive_add_to_favorites(archive); | ||||
|             } else { | ||||
|                 // delete from favorites
 | ||||
|                 archive_favorites_delete(archive, selected); | ||||
|             } | ||||
|             archive_close_file_menu(archive); | ||||
|         } | ||||
|         break; | ||||
|     case 2: | ||||
|         // open rename view
 | ||||
|         if(is_known_app(selected->type)) { | ||||
|             archive_enter_text_input(archive); | ||||
|         } | ||||
|         break; | ||||
|     case 3: | ||||
|         // confirmation?
 | ||||
|         archive_delete_file(archive, selected); | ||||
|         archive_close_file_menu(archive); | ||||
| 
 | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         archive_close_file_menu(archive); | ||||
|         break; | ||||
|     } | ||||
|     selected = NULL; | ||||
| } | ||||
| 
 | ||||
| static void menu_input_handler(ArchiveApp* archive, InputEvent* event) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||
|             with_view_model( | ||||
|                 archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                     if(event->key == InputKeyUp) { | ||||
|                         model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS; | ||||
|                     } else if(event->key == InputKeyDown) { | ||||
|                         model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS; | ||||
|                     } | ||||
|                     return true; | ||||
|                 }); | ||||
|         } | ||||
| 
 | ||||
|         if(event->key == InputKeyOk) { | ||||
|             archive_file_menu_callback(archive); | ||||
|         } else if(event->key == InputKeyBack) { | ||||
|             archive_close_file_menu(archive); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* main controls */ | ||||
| 
 | ||||
| static bool archive_view_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
| bool archive_back_event_callback(void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveApp* archive = context; | ||||
|     bool in_menu = archive->browser.menu; | ||||
| 
 | ||||
|     if(in_menu) { | ||||
|         menu_input_handler(archive, event); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         if(event->key == InputKeyLeft) { | ||||
|             if(archive->browser.tab_id > 0) { | ||||
|                 archive->browser.tab_id = CLAMP(archive->browser.tab_id - 1, ArchiveTabTotal, 0); | ||||
|                 archive_switch_tab(archive); | ||||
|                 return true; | ||||
|             } | ||||
|         } else if(event->key == InputKeyRight) { | ||||
|             if(archive->browser.tab_id < ArchiveTabTotal - 1) { | ||||
|                 archive->browser.tab_id = | ||||
|                     CLAMP(archive->browser.tab_id + 1, ArchiveTabTotal - 1, 0); | ||||
|                 archive_switch_tab(archive); | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|         } else if(event->key == InputKeyBack) { | ||||
|             if(archive->browser.depth == 0) { | ||||
|                 archive_exit_callback(archive); | ||||
|             } else { | ||||
|                 archive_leave_dir(archive); | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||
|         with_view_model( | ||||
|             archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                 uint16_t num_elements = (uint16_t)files_array_size(model->files); | ||||
|                 if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||
|                     if(event->key == InputKeyUp) { | ||||
|                         model->idx = ((model->idx - 1) + num_elements) % num_elements; | ||||
|                     } else if(event->key == InputKeyDown) { | ||||
|                         model->idx = (model->idx + 1) % num_elements; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 return true; | ||||
|             }); | ||||
|         update_offset(archive); | ||||
|     } | ||||
| 
 | ||||
|     if(event->key == InputKeyOk) { | ||||
|         ArchiveFile_t* selected; | ||||
| 
 | ||||
|         with_view_model( | ||||
|             archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                 selected = files_array_size(model->files) > 0 ? | ||||
|                                files_array_get(model->files, model->idx) : | ||||
|                                NULL; | ||||
|                 return true; | ||||
|             }); | ||||
| 
 | ||||
|         if(selected) { | ||||
|             string_set(archive->browser.name, selected->name); | ||||
| 
 | ||||
|             if(selected->type == ArchiveFileTypeFolder) { | ||||
|                 if(event->type == InputTypeShort) { | ||||
|                     archive_enter_dir(archive, archive->browser.name); | ||||
|                 } else if(event->type == InputTypeLong) { | ||||
|                     archive_show_file_menu(archive); | ||||
|                 } | ||||
|             } else { | ||||
|                 if(event->type == InputTypeShort) { | ||||
|                     if(archive->browser.tab_id == ArchiveTabFavorites) { | ||||
|                         if(is_known_app(selected->type)) { | ||||
|                             archive_run_in_app(archive, selected, true); | ||||
|                         } | ||||
|                     } else { | ||||
|                         archive_show_file_menu(archive); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     update_offset(archive); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void archive_free(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     file_worker_free(archive->file_worker); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewMain); | ||||
|     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||
|     view_dispatcher_free(archive->view_dispatcher); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             files_array_clear(model->files); | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     view_free(archive->view_archive_main); | ||||
| 
 | ||||
|     string_clear(archive->browser.name); | ||||
|     string_clear(archive->browser.path); | ||||
| 
 | ||||
|     text_input_free(archive->text_input); | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
|     archive->api = NULL; | ||||
|     furi_record_close("gui"); | ||||
|     archive->gui = NULL; | ||||
|     furi_record_close("loader"); | ||||
|     archive->loader = NULL; | ||||
|     furi_thread_free(archive->app_thread); | ||||
|     furi_check(osMessageQueueDelete(archive->event_queue) == osOK); | ||||
| 
 | ||||
|     free(archive); | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     return scene_manager_handle_back_event(archive->scene_manager); | ||||
| } | ||||
| 
 | ||||
| ArchiveApp* archive_alloc() { | ||||
|     ArchiveApp* archive = furi_alloc(sizeof(ArchiveApp)); | ||||
| 
 | ||||
|     archive->event_queue = osMessageQueueNew(8, sizeof(AppEvent), NULL); | ||||
|     archive->app_thread = furi_thread_alloc(); | ||||
|     archive->gui = furi_record_open("gui"); | ||||
|     archive->loader = furi_record_open("loader"); | ||||
|     archive->api = furi_record_open("storage"); | ||||
|     archive->text_input = text_input_alloc(); | ||||
|     archive->view_archive_main = view_alloc(); | ||||
|     archive->file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     furi_check(archive->event_queue); | ||||
| 
 | ||||
|     view_allocate_model( | ||||
|         archive->view_archive_main, ViewModelTypeLocking, sizeof(ArchiveViewModel)); | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|             files_array_init(model->files); | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     view_set_context(archive->view_archive_main, archive); | ||||
|     view_set_draw_callback(archive->view_archive_main, archive_view_render); | ||||
|     view_set_input_callback(archive->view_archive_main, archive_view_input); | ||||
|     view_set_previous_callback( | ||||
|         text_input_get_view(archive->text_input), archive_previous_callback); | ||||
| 
 | ||||
|     // View Dispatcher
 | ||||
|     archive->view_dispatcher = view_dispatcher_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         archive->view_dispatcher, ArchiveViewMain, archive->view_archive_main); | ||||
|     view_dispatcher_add_view( | ||||
|         archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); | ||||
|     archive->scene_manager = scene_manager_alloc(&archive_scene_handlers, archive); | ||||
| 
 | ||||
|     view_dispatcher_enable_queue(archive->view_dispatcher); | ||||
|     view_dispatcher_attach_to_gui( | ||||
|         archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveTabFavorites); | ||||
|     view_dispatcher_set_event_callback_context(archive->view_dispatcher, archive); | ||||
|     view_dispatcher_set_custom_event_callback( | ||||
|         archive->view_dispatcher, archive_custom_event_callback); | ||||
|     view_dispatcher_set_navigation_event_callback( | ||||
|         archive->view_dispatcher, archive_back_event_callback); | ||||
| 
 | ||||
|     archive->main_view = main_view_alloc(); | ||||
| 
 | ||||
|     view_dispatcher_add_view( | ||||
|         archive->view_dispatcher, ArchiveViewBrowser, archive_main_get_view(archive->main_view)); | ||||
| 
 | ||||
|     view_dispatcher_add_view( | ||||
|         archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); | ||||
| 
 | ||||
|     return archive; | ||||
| } | ||||
| 
 | ||||
| void archive_free(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewBrowser); | ||||
|     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||
|     view_dispatcher_free(archive->view_dispatcher); | ||||
|     scene_manager_free(archive->scene_manager); | ||||
|     main_view_free(archive->main_view); | ||||
| 
 | ||||
|     text_input_free(archive->text_input); | ||||
| 
 | ||||
|     furi_record_close("gui"); | ||||
|     archive->gui = NULL; | ||||
| 
 | ||||
|     free(archive); | ||||
| } | ||||
| 
 | ||||
| int32_t archive_app(void* p) { | ||||
|     ArchiveApp* archive = archive_alloc(); | ||||
| 
 | ||||
|     // default tab
 | ||||
|     archive_switch_tab(archive); | ||||
| 
 | ||||
|     AppEvent event; | ||||
|     while(1) { | ||||
|         furi_check(osMessageQueueGet(archive->event_queue, &event, NULL, osWaitForever) == osOK); | ||||
|         if(event.type == EventTypeExit) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); | ||||
|     view_dispatcher_run(archive->view_dispatcher); | ||||
|     archive_free(archive); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @ -5,44 +5,27 @@ | ||||
| #include <furi.h> | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <loader/loader.h> | ||||
| 
 | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| #include <storage/storage.h> | ||||
| #include "archive_views.h" | ||||
| #include "applications.h" | ||||
| #include "file-worker.h" | ||||
| 
 | ||||
| #define MAX_DEPTH 32 | ||||
| #define MAX_FILES 100 //temp
 | ||||
| #include "views/archive_main_view.h" | ||||
| #include "scenes/archive_scene.h" | ||||
| 
 | ||||
| #define MAX_FILE_SIZE 128 | ||||
| #define ARCHIVE_FAV_PATH "/any/favorites.txt" | ||||
| #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | ||||
| 
 | ||||
| typedef enum { | ||||
|     ArchiveViewMain, | ||||
|     ArchiveViewBrowser, | ||||
|     ArchiveViewTextInput, | ||||
|     ArchiveViewTotal, | ||||
| } ArchiveViewEnum; | ||||
| 
 | ||||
| static const char* flipper_app_name[] = { | ||||
|     [ArchiveFileTypeIButton] = "iButton", | ||||
|     [ArchiveFileTypeNFC] = "NFC", | ||||
|     [ArchiveFileTypeSubGhz] = "Sub-GHz", | ||||
|     [ArchiveFileTypeLFRFID] = "125 kHz RFID", | ||||
|     [ArchiveFileTypeIrda] = "Infrared", | ||||
| }; | ||||
| 
 | ||||
| static const char* known_ext[] = { | ||||
|     [ArchiveFileTypeIButton] = ".ibtn", | ||||
|     [ArchiveFileTypeNFC] = ".nfc", | ||||
|     [ArchiveFileTypeSubGhz] = ".sub", | ||||
|     [ArchiveFileTypeLFRFID] = ".rfid", | ||||
|     [ArchiveFileTypeIrda] = ".ir", | ||||
| }; | ||||
| 
 | ||||
| static const char* tab_default_paths[] = { | ||||
|     [ArchiveTabFavorites] = "/any/favorites", | ||||
|     [ArchiveTabIButton] = "/any/ibutton", | ||||
| @ -53,23 +36,6 @@ static const char* tab_default_paths[] = { | ||||
|     [ArchiveTabBrowser] = "/any", | ||||
| }; | ||||
| 
 | ||||
| static inline const char* get_tab_ext(ArchiveTabEnum tab) { | ||||
|     switch(tab) { | ||||
|     case ArchiveTabIButton: | ||||
|         return known_ext[ArchiveFileTypeIButton]; | ||||
|     case ArchiveTabNFC: | ||||
|         return known_ext[ArchiveFileTypeNFC]; | ||||
|     case ArchiveTabSubGhz: | ||||
|         return known_ext[ArchiveFileTypeSubGhz]; | ||||
|     case ArchiveTabLFRFID: | ||||
|         return known_ext[ArchiveFileTypeLFRFID]; | ||||
|     case ArchiveTabIrda: | ||||
|         return known_ext[ArchiveFileTypeIrda]; | ||||
|     default: | ||||
|         return "*"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static inline const char* get_default_path(ArchiveFileTypeEnum type) { | ||||
|     switch(type) { | ||||
|     case ArchiveFileTypeIButton: | ||||
| @ -104,35 +70,11 @@ typedef struct { | ||||
|     EventType type; | ||||
| } AppEvent; | ||||
| 
 | ||||
| typedef enum { | ||||
|     FavoritesCheck, | ||||
|     FavoritesRead, | ||||
|     FavoritesDelete, | ||||
|     FavoritesRename, | ||||
| } FavActionsEnum; | ||||
| 
 | ||||
| typedef struct { | ||||
|     ArchiveTabEnum tab_id; | ||||
|     string_t name; | ||||
|     string_t path; | ||||
|     char text_input_buffer[MAX_NAME_LEN]; | ||||
| 
 | ||||
|     uint8_t depth; | ||||
|     uint16_t last_idx[MAX_DEPTH]; | ||||
| 
 | ||||
|     bool menu; | ||||
| } ArchiveBrowser; | ||||
| 
 | ||||
| struct ArchiveApp { | ||||
|     osMessageQueueId_t event_queue; | ||||
|     FuriThread* app_thread; | ||||
|     Loader* loader; | ||||
|     Gui* gui; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     View* view_archive_main; | ||||
|     SceneManager* scene_manager; | ||||
|     ArchiveMainView* main_view; | ||||
|     TextInput* text_input; | ||||
| 
 | ||||
|     Storage* api; | ||||
|     FileWorker* file_worker; | ||||
|     ArchiveBrowser browser; | ||||
|     char text_store[MAX_NAME_LEN]; | ||||
| }; | ||||
|  | ||||
| @ -1,168 +0,0 @@ | ||||
| #include "archive_views.h" | ||||
| 
 | ||||
| static const char* ArchiveTabNames[] = { | ||||
|     [ArchiveTabFavorites] = "Favorites", | ||||
|     [ArchiveTabIButton] = "iButton", | ||||
|     [ArchiveTabNFC] = "NFC", | ||||
|     [ArchiveTabSubGhz] = "Sub-GHz", | ||||
|     [ArchiveTabLFRFID] = "RFID LF", | ||||
|     [ArchiveTabIrda] = "Infrared", | ||||
|     [ArchiveTabBrowser] = "Browser"}; | ||||
| 
 | ||||
| static const Icon* ArchiveItemIcons[] = { | ||||
|     [ArchiveFileTypeIButton] = &I_ibutt_10px, | ||||
|     [ArchiveFileTypeNFC] = &I_Nfc_10px, | ||||
|     [ArchiveFileTypeSubGhz] = &I_sub1_10px, | ||||
|     [ArchiveFileTypeLFRFID] = &I_125_10px, | ||||
|     [ArchiveFileTypeIrda] = &I_ir_10px, | ||||
|     [ArchiveFileTypeFolder] = &I_dir_10px, | ||||
|     [ArchiveFileTypeUnknown] = &I_unknown_10px, | ||||
| }; | ||||
| 
 | ||||
| static void render_item_menu(Canvas* canvas, ArchiveViewModel* model) { | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_box(canvas, 71, 17, 57, 46); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     elements_slightly_rounded_frame(canvas, 70, 16, 58, 48); | ||||
| 
 | ||||
|     string_t menu[MENU_ITEMS]; | ||||
| 
 | ||||
|     string_init_set_str(menu[0], "Run in app"); | ||||
|     string_init_set_str(menu[1], "Pin"); | ||||
|     string_init_set_str(menu[2], "Rename"); | ||||
|     string_init_set_str(menu[3], "Delete"); | ||||
| 
 | ||||
|     ArchiveFile_t* selected = files_array_get(model->files, model->idx); | ||||
| 
 | ||||
|     if(!is_known_app(selected->type)) { | ||||
|         string_set_str(menu[0], "---"); | ||||
|         string_set_str(menu[1], "---"); | ||||
|         string_set_str(menu[2], "---"); | ||||
|     } else if(model->tab_idx == 0 || selected->fav) { | ||||
|         string_set_str(menu[1], "Unpin"); | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < MENU_ITEMS; i++) { | ||||
|         canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i])); | ||||
|         string_clear(menu[i]); | ||||
|     } | ||||
| 
 | ||||
|     canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); | ||||
| } | ||||
| 
 | ||||
| void archive_trim_file_ext(char* name) { | ||||
|     size_t str_len = strlen(name); | ||||
|     char* end = name + str_len; | ||||
|     while(end > name && *end != '.' && *end != '\\' && *end != '/') { | ||||
|         --end; | ||||
|     } | ||||
|     if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { | ||||
|         *end = '\0'; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_draw_box(canvas, 0, 15 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_dot(canvas, 0, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 1, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 1); | ||||
| 
 | ||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 11); | ||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); | ||||
| } | ||||
| 
 | ||||
| static void draw_list(Canvas* canvas, ArchiveViewModel* model) { | ||||
|     furi_assert(model); | ||||
| 
 | ||||
|     size_t array_size = files_array_size(model->files); | ||||
|     bool scrollbar = array_size > 4; | ||||
| 
 | ||||
|     for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { | ||||
|         string_t str_buff; | ||||
|         char cstr_buff[MAX_NAME_LEN]; | ||||
| 
 | ||||
|         size_t idx = CLAMP(i + model->list_offset, array_size, 0); | ||||
|         ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); | ||||
| 
 | ||||
|         string_init_set(str_buff, file->name); | ||||
|         string_right(str_buff, string_search_rchar(str_buff, '/') + 1); | ||||
|         strlcpy(cstr_buff, string_get_cstr(str_buff), string_size(str_buff) + 1); | ||||
| 
 | ||||
|         if(is_known_app(file->type)) archive_trim_file_ext(cstr_buff); | ||||
| 
 | ||||
|         string_clean(str_buff); | ||||
|         string_set_str(str_buff, cstr_buff); | ||||
| 
 | ||||
|         elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); | ||||
| 
 | ||||
|         if(model->idx == idx) { | ||||
|             archive_draw_frame(canvas, i, scrollbar); | ||||
|         } else { | ||||
|             canvas_set_color(canvas, ColorBlack); | ||||
|         } | ||||
| 
 | ||||
|         canvas_draw_icon(canvas, 2, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); | ||||
|         canvas_draw_str(canvas, 15, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); | ||||
|         string_clear(str_buff); | ||||
|     } | ||||
| 
 | ||||
|     if(scrollbar) { | ||||
|         elements_scrollbar_pos(canvas, 126, 15, 49, model->idx, array_size); | ||||
|     } | ||||
| 
 | ||||
|     if(model->menu) { | ||||
|         render_item_menu(canvas, model); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void archive_render_status_bar(Canvas* canvas, ArchiveViewModel* model) { | ||||
|     furi_assert(model); | ||||
| 
 | ||||
|     const char* tab_name = ArchiveTabNames[model->tab_idx]; | ||||
| 
 | ||||
|     canvas_draw_icon(canvas, 0, 0, &I_Background_128x11); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_box(canvas, 0, 0, 50, 13); | ||||
|     canvas_draw_box(canvas, 107, 0, 20, 13); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_draw_frame(canvas, 1, 0, 50, 12); | ||||
|     canvas_draw_line(canvas, 0, 1, 0, 11); | ||||
|     canvas_draw_line(canvas, 1, 12, 49, 12); | ||||
|     canvas_draw_str_aligned(canvas, 26, 9, AlignCenter, AlignBottom, tab_name); | ||||
| 
 | ||||
|     canvas_draw_frame(canvas, 108, 0, 20, 12); | ||||
|     canvas_draw_line(canvas, 107, 1, 107, 11); | ||||
|     canvas_draw_line(canvas, 108, 12, 126, 12); | ||||
| 
 | ||||
|     if(model->tab_idx > 0) { | ||||
|         canvas_draw_icon(canvas, 112, 2, &I_ButtonLeft_4x7); | ||||
|     } | ||||
|     if(model->tab_idx < SIZEOF_ARRAY(ArchiveTabNames) - 1) { | ||||
|         canvas_draw_icon(canvas, 120, 2, &I_ButtonRight_4x7); | ||||
|     } | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_dot(canvas, 50, 0); | ||||
|     canvas_draw_dot(canvas, 127, 0); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| } | ||||
| 
 | ||||
| void archive_view_render(Canvas* canvas, void* model) { | ||||
|     ArchiveViewModel* m = model; | ||||
| 
 | ||||
|     archive_render_status_bar(canvas, model); | ||||
| 
 | ||||
|     if(files_array_size(m->files) > 0) { | ||||
|         draw_list(canvas, m); | ||||
|     } else { | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										171
									
								
								applications/archive/helpers/archive_favorites.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								applications/archive/helpers/archive_favorites.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | ||||
| #include "archive_favorites.h" | ||||
| #include "archive_files.h" | ||||
| #include "../views/archive_main_view.h" | ||||
| 
 | ||||
| bool archive_favorites_read(void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveMainView* archive_view = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t buffer; | ||||
|     FileInfo file_info; | ||||
|     string_init(buffer); | ||||
| 
 | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); | ||||
| 
 | ||||
|     if(result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             archive_view_add_item(archive_view, &file_info, string_get_cstr(buffer)); | ||||
|             string_clean(buffer); | ||||
|         } | ||||
|     } | ||||
|     string_clear(buffer); | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_free(file_worker); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool archive_favorites_delete(const char* file_path, const char* name) { | ||||
|     furi_assert(file_path); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t path; | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
| 
 | ||||
|     string_init_printf(path, "%s/%s", file_path, name); | ||||
| 
 | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     if(result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if(string_search(buffer, path)) { | ||||
|                 string_t temp; | ||||
|                 string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); | ||||
|                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); | ||||
|                 string_clear(temp); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
| 
 | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||
|     file_worker_rename(file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||
| 
 | ||||
|     file_worker_free(file_worker); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool archive_is_favorite(const char* file_path, const char* name) { | ||||
|     furi_assert(file_path); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t path; | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
|     bool found = false; | ||||
| 
 | ||||
|     string_init_printf(path, "%s/%s", file_path, name); | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); | ||||
| 
 | ||||
|     if(result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_search(buffer, path)) { | ||||
|                 found = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_free(file_worker); | ||||
| 
 | ||||
|     return found; | ||||
| } | ||||
| 
 | ||||
| bool archive_favorites_rename(const char* file_path, const char* src, const char* dst) { | ||||
|     furi_assert(file_path); | ||||
|     furi_assert(src); | ||||
|     furi_assert(dst); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t path; | ||||
|     string_t buffer; | ||||
|     string_t temp; | ||||
| 
 | ||||
|     string_init(buffer); | ||||
|     string_init(temp); | ||||
|     string_init(path); | ||||
| 
 | ||||
|     string_printf(path, "%s/%s", file_path, src); | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(result) { | ||||
|         while(1) { | ||||
|             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
|             string_printf( | ||||
|                 temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||
|             archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); | ||||
|             string_clean(temp); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(temp); | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
| 
 | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||
|     file_worker_rename(file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||
| 
 | ||||
|     file_worker_free(file_worker); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void archive_add_to_favorites(const char* file_path, const char* name) { | ||||
|     furi_assert(file_path); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     string_t buffer_src; | ||||
| 
 | ||||
|     string_init_printf(buffer_src, "%s/%s\r\n", file_path, name); | ||||
|     archive_file_append(ARCHIVE_FAV_PATH, buffer_src); | ||||
|     string_clear(buffer_src); | ||||
| } | ||||
							
								
								
									
										11
									
								
								applications/archive/helpers/archive_favorites.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								applications/archive/helpers/archive_favorites.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| #pragma once | ||||
| #include "file-worker.h" | ||||
| 
 | ||||
| #define ARCHIVE_FAV_PATH "/any/favorites.txt" | ||||
| #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | ||||
| 
 | ||||
| bool archive_favorites_read(void* context); | ||||
| bool archive_favorites_delete(const char* file_path, const char* name); | ||||
| bool archive_is_favorite(const char* file_path, const char* name); | ||||
| bool archive_favorites_rename(const char* file_path, const char* src, const char* dst); | ||||
| void archive_add_to_favorites(const char* file_path, const char* name); | ||||
							
								
								
									
										143
									
								
								applications/archive/helpers/archive_files.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								applications/archive/helpers/archive_files.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | ||||
| #include "archive_files.h" | ||||
| #include "archive_favorites.h" | ||||
| #include "../archive_i.h" | ||||
| 
 | ||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | ||||
|     furi_assert(file_info); | ||||
|     furi_assert(tab_ext); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(strcmp(tab_ext, "*") == 0) { | ||||
|         result = true; | ||||
|     } else if(strstr(name, tab_ext) != NULL) { | ||||
|         result = true; | ||||
|     } else if(file_info->flags & FSF_DIRECTORY) { | ||||
|         result = true; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void archive_trim_file_ext(char* name) { | ||||
|     size_t str_len = strlen(name); | ||||
|     char* end = name + str_len; | ||||
|     while(end > name && *end != '.' && *end != '\\' && *end != '/') { | ||||
|         --end; | ||||
|     } | ||||
|     if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { | ||||
|         *end = '\0'; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { | ||||
|     furi_assert(file); | ||||
|     furi_assert(file_info); | ||||
| 
 | ||||
|     for(size_t i = 0; i < SIZEOF_ARRAY(known_ext); i++) { | ||||
|         if(string_search_str(file->name, known_ext[i], 0) != STRING_FAILURE) { | ||||
|             file->type = i; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(file_info->flags & FSF_DIRECTORY) { | ||||
|         file->type = ArchiveFileTypeFolder; | ||||
|     } else { | ||||
|         file->type = ArchiveFileTypeUnknown; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool archive_get_filenames(void* context, uint8_t tab_id, const char* path) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveMainView* main_view = context; | ||||
|     archive_file_array_clean(main_view); | ||||
| 
 | ||||
|     if(tab_id != ArchiveTabFavorites) { | ||||
|         archive_read_dir(main_view, path); | ||||
|     } else { | ||||
|         archive_favorites_read(main_view); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool archive_read_dir(void* context, const char* path) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveMainView* main_view = context; | ||||
|     FileInfo file_info; | ||||
|     Storage* fs_api = furi_record_open("storage"); | ||||
|     File* directory = storage_file_alloc(fs_api); | ||||
|     char name[MAX_NAME_LEN]; | ||||
| 
 | ||||
|     if(!storage_dir_open(directory, path)) { | ||||
|         storage_dir_close(directory); | ||||
|         storage_file_free(directory); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     while(1) { | ||||
|         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         uint16_t files_cnt = archive_file_array_size(main_view); | ||||
| 
 | ||||
|         if(files_cnt > MAX_FILES) { | ||||
|             break; | ||||
|         } else if(storage_file_get_error(directory) == FSE_OK) { | ||||
|             archive_view_add_item(main_view, &file_info, name); | ||||
|         } else { | ||||
|             storage_dir_close(directory); | ||||
|             storage_file_free(directory); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     storage_dir_close(directory); | ||||
|     storage_file_free(directory); | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void archive_file_append(const char* path, string_t string) { | ||||
|     furi_assert(path); | ||||
|     furi_assert(string); | ||||
| 
 | ||||
|     FileWorker* file_worker = file_worker_alloc(false); | ||||
| 
 | ||||
|     if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { | ||||
|         FURI_LOG_E("Archive", "Append open error"); | ||||
|     } | ||||
| 
 | ||||
|     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { | ||||
|         FURI_LOG_E("Archive", "Append write error"); | ||||
|     } | ||||
| 
 | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_free(file_worker); | ||||
| } | ||||
| 
 | ||||
| void archive_delete_file(void* context, string_t path, string_t name) { | ||||
|     furi_assert(context); | ||||
|     furi_assert(path); | ||||
|     furi_assert(name); | ||||
|     ArchiveMainView* main_view = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(false); | ||||
| 
 | ||||
|     string_t full_path; | ||||
|     string_init(full_path); | ||||
|     string_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); | ||||
|     file_worker_remove(file_worker, string_get_cstr(full_path)); | ||||
|     file_worker_free(file_worker); | ||||
|     string_clear(full_path); | ||||
| 
 | ||||
|     if(archive_is_favorite(string_get_cstr(path), string_get_cstr(name))) { | ||||
|         archive_favorites_delete(string_get_cstr(path), string_get_cstr(name)); | ||||
|     } | ||||
| 
 | ||||
|     archive_file_array_remove_selected(main_view); | ||||
| } | ||||
| @ -1,15 +1,7 @@ | ||||
| #pragma once | ||||
| #include "file-worker.h" | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #define MAX_LEN_PX 100 | ||||
| #define MAX_NAME_LEN 255 | ||||
| #define FRAME_HEIGHT 12 | ||||
| #define MENU_ITEMS 4 | ||||
| #define MAX_FILES 100 //temp
 | ||||
| 
 | ||||
| typedef enum { | ||||
|     ArchiveFileTypeIButton, | ||||
| @ -22,17 +14,6 @@ typedef enum { | ||||
|     AppIdTotal, | ||||
| } ArchiveFileTypeEnum; | ||||
| 
 | ||||
| typedef enum { | ||||
|     ArchiveTabFavorites, | ||||
|     ArchiveTabLFRFID, | ||||
|     ArchiveTabSubGhz, | ||||
|     ArchiveTabNFC, | ||||
|     ArchiveTabIButton, | ||||
|     ArchiveTabIrda, | ||||
|     ArchiveTabBrowser, | ||||
|     ArchiveTabTotal, | ||||
| } ArchiveTabEnum; | ||||
| 
 | ||||
| typedef struct { | ||||
|     string_t name; | ||||
|     ArchiveFileTypeEnum type; | ||||
| @ -66,18 +47,10 @@ ARRAY_DEF( | ||||
|      INIT_SET(API_6(ArchiveFile_t_init_set)), | ||||
|      CLEAR(API_2(ArchiveFile_t_clear)))) | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t tab_idx; | ||||
|     uint8_t menu_idx; | ||||
|     uint16_t idx; | ||||
|     uint16_t list_offset; | ||||
|     files_array_t files; | ||||
|     bool menu; | ||||
| } ArchiveViewModel; | ||||
| 
 | ||||
| void archive_view_render(Canvas* canvas, void* model); | ||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); | ||||
| void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | ||||
| void archive_trim_file_ext(char* name); | ||||
| 
 | ||||
| static inline bool is_known_app(ArchiveFileTypeEnum type) { | ||||
|     return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); | ||||
| } | ||||
| bool archive_get_filenames(void* context, uint8_t tab_id, const char* path); | ||||
| bool archive_read_dir(void* context, const char* path); | ||||
| void archive_file_append(const char* path, string_t string); | ||||
| void archive_delete_file(void* context, string_t path, string_t name); | ||||
							
								
								
									
										30
									
								
								applications/archive/scenes/archive_scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/archive/scenes/archive_scene.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| #include "archive_scene.h" | ||||
| 
 | ||||
| // Generate scene on_enter handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||
| void (*const archive_on_enter_handlers[])(void*) = { | ||||
| #include "archive_scene_config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_event handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||
| bool (*const archive_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||
| #include "archive_scene_config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_exit handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||
| void (*const archive_on_exit_handlers[])(void* context) = { | ||||
| #include "archive_scene_config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Initialize scene handlers configuration structure
 | ||||
| const SceneManagerHandlers archive_scene_handlers = { | ||||
|     .on_enter_handlers = archive_on_enter_handlers, | ||||
|     .on_event_handlers = archive_on_event_handlers, | ||||
|     .on_exit_handlers = archive_on_exit_handlers, | ||||
|     .scene_num = ArchiveAppSceneNum, | ||||
| }; | ||||
							
								
								
									
										29
									
								
								applications/archive/scenes/archive_scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/archive/scenes/archive_scene.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| // Generate scene id and total number
 | ||||
| #define ADD_SCENE(prefix, name, id) ArchiveAppScene##id, | ||||
| typedef enum { | ||||
| #include "archive_scene_config.h" | ||||
|     ArchiveAppSceneNum, | ||||
| } ArchiveAppScene; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| extern const SceneManagerHandlers archive_scene_handlers; | ||||
| 
 | ||||
| // Generate scene on_enter handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||
| #include "archive_scene_config.h" | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_event handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) \ | ||||
|     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||
| #include "archive_scene_config.h" | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_exit handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||
| #include "archive_scene_config.h" | ||||
| #undef ADD_SCENE | ||||
							
								
								
									
										42
									
								
								applications/archive/scenes/archive_scene_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								applications/archive/scenes/archive_scene_browser.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| #include "../archive_i.h" | ||||
| #include "../views/archive_main_view.h" | ||||
| 
 | ||||
| void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     view_dispatcher_send_custom_event(archive->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| const void archive_scene_browser_on_enter(void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     ArchiveMainView* main_view = archive->main_view; | ||||
| 
 | ||||
|     archive_browser_set_callback(main_view, archive_scene_browser_callback, archive); | ||||
|     archive_browser_update(main_view); | ||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); | ||||
| } | ||||
| 
 | ||||
| const bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     bool consumed; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case ArchiveBrowserEventRename: | ||||
|             scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case ArchiveBrowserEventExit: | ||||
|             view_dispatcher_stop(archive->view_dispatcher); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| const void archive_scene_browser_on_exit(void* context) { | ||||
|     // ArchiveApp* archive = (ArchiveApp*)context;
 | ||||
| } | ||||
							
								
								
									
										2
									
								
								applications/archive/scenes/archive_scene_config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								applications/archive/scenes/archive_scene_config.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| ADD_SCENE(archive, browser, Browser) | ||||
| ADD_SCENE(archive, rename, Rename) | ||||
							
								
								
									
										80
									
								
								applications/archive/scenes/archive_scene_rename.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								applications/archive/scenes/archive_scene_rename.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,80 @@ | ||||
| #include "../archive_i.h" | ||||
| #include "../helpers/archive_favorites.h" | ||||
| #include "../helpers/archive_files.h" | ||||
| 
 | ||||
| #define SCENE_RENAME_CUSTOM_EVENT (0UL) | ||||
| 
 | ||||
| void archive_scene_rename_text_input_callback(void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     view_dispatcher_send_custom_event(archive->view_dispatcher, SCENE_RENAME_CUSTOM_EVENT); | ||||
| } | ||||
| 
 | ||||
| const void archive_scene_rename_on_enter(void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
| 
 | ||||
|     TextInput* text_input = archive->text_input; | ||||
|     ArchiveFile_t* current = archive_get_current_file(archive->main_view); | ||||
|     strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); | ||||
| 
 | ||||
|     archive_trim_file_ext(archive->text_store); | ||||
| 
 | ||||
|     text_input_set_header_text(text_input, "Rename:"); | ||||
| 
 | ||||
|     text_input_set_result_callback( | ||||
|         text_input, | ||||
|         archive_scene_rename_text_input_callback, | ||||
|         archive, | ||||
|         archive->text_store, | ||||
|         MAX_NAME_LEN, | ||||
|         false); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||
| } | ||||
| 
 | ||||
| const bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SCENE_RENAME_CUSTOM_EVENT) { | ||||
|             Storage* fs_api = furi_record_open("storage"); | ||||
| 
 | ||||
|             string_t buffer_src; | ||||
|             string_t buffer_dst; | ||||
| 
 | ||||
|             const char* path = archive_get_path(archive->main_view); | ||||
|             const char* name = archive_get_name(archive->main_view); | ||||
| 
 | ||||
|             string_init_printf(buffer_src, "%s/%s", path, name); | ||||
|             string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); | ||||
| 
 | ||||
|             archive_set_name(archive->main_view, archive->text_store); | ||||
| 
 | ||||
|             // append extension
 | ||||
|             ArchiveFile_t* file = archive_get_current_file(archive->main_view); | ||||
| 
 | ||||
|             string_cat(buffer_dst, known_ext[file->type]); | ||||
|             storage_common_rename( | ||||
|                 fs_api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); | ||||
|             furi_record_close("storage"); | ||||
| 
 | ||||
|             if(file->fav) { | ||||
|                 archive_favorites_rename(path, name, string_get_cstr(buffer_dst)); | ||||
|             } | ||||
| 
 | ||||
|             string_clear(buffer_src); | ||||
|             string_clear(buffer_dst); | ||||
| 
 | ||||
|             scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| const void archive_scene_rename_on_exit(void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     // Clear view
 | ||||
|     text_input_set_header_text(archive->text_input, NULL); | ||||
|     text_input_set_result_callback(archive->text_input, NULL, NULL, NULL, 0, false); | ||||
| } | ||||
							
								
								
									
										641
									
								
								applications/archive/views/archive_main_view.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										641
									
								
								applications/archive/views/archive_main_view.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,641 @@ | ||||
| #include <furi.h> | ||||
| #include "../archive_i.h" | ||||
| #include "archive_main_view.h" | ||||
| 
 | ||||
| static const char* flipper_app_name[] = { | ||||
|     [ArchiveFileTypeIButton] = "iButton", | ||||
|     [ArchiveFileTypeNFC] = "NFC", | ||||
|     [ArchiveFileTypeSubGhz] = "Sub-GHz", | ||||
|     [ArchiveFileTypeLFRFID] = "125 kHz RFID", | ||||
|     [ArchiveFileTypeIrda] = "Infrared", | ||||
| }; | ||||
| 
 | ||||
| static const char* ArchiveTabNames[] = { | ||||
|     [ArchiveTabFavorites] = "Favorites", | ||||
|     [ArchiveTabIButton] = "iButton", | ||||
|     [ArchiveTabNFC] = "NFC", | ||||
|     [ArchiveTabSubGhz] = "Sub-GHz", | ||||
|     [ArchiveTabLFRFID] = "RFID LF", | ||||
|     [ArchiveTabIrda] = "Infrared", | ||||
|     [ArchiveTabBrowser] = "Browser"}; | ||||
| 
 | ||||
| static const Icon* ArchiveItemIcons[] = { | ||||
|     [ArchiveFileTypeIButton] = &I_ibutt_10px, | ||||
|     [ArchiveFileTypeNFC] = &I_Nfc_10px, | ||||
|     [ArchiveFileTypeSubGhz] = &I_sub1_10px, | ||||
|     [ArchiveFileTypeLFRFID] = &I_125_10px, | ||||
|     [ArchiveFileTypeIrda] = &I_ir_10px, | ||||
|     [ArchiveFileTypeFolder] = &I_dir_10px, | ||||
|     [ArchiveFileTypeUnknown] = &I_unknown_10px, | ||||
| }; | ||||
| 
 | ||||
| void archive_browser_set_callback( | ||||
|     ArchiveMainView* main_view, | ||||
|     ArchiveMainViewCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(main_view); | ||||
|     furi_assert(callback); | ||||
|     main_view->callback = callback; | ||||
|     main_view->context = context; | ||||
| } | ||||
| 
 | ||||
| void update_offset(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             size_t array_size = files_array_size(model->files); | ||||
|             uint16_t bounds = array_size > 3 ? 2 : array_size; | ||||
| 
 | ||||
|             if(array_size > 3 && model->idx >= array_size - 1) { | ||||
|                 model->list_offset = model->idx - 3; | ||||
|             } else if(model->list_offset < model->idx - bounds) { | ||||
|                 model->list_offset = CLAMP(model->idx - 2, array_size - bounds, 0); | ||||
|             } else if(model->list_offset > model->idx - bounds) { | ||||
|                 model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| size_t archive_file_array_size(ArchiveMainView* main_view) { | ||||
|     uint16_t size = 0; | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             size = files_array_size(model->files); | ||||
|             return true; | ||||
|         }); | ||||
|     return size; | ||||
| } | ||||
| 
 | ||||
| void archive_file_array_remove_selected(ArchiveMainView* main_view) { | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             files_array_remove_v(model->files, model->idx, model->idx + 1); | ||||
|             model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     update_offset(main_view); | ||||
| } | ||||
| 
 | ||||
| void archive_file_array_clean(ArchiveMainView* main_view) { | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             files_array_clean(model->files); | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| ArchiveFile_t* archive_get_current_file(ArchiveMainView* main_view) { | ||||
|     ArchiveFile_t* selected; | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             selected = files_array_size(model->files) > 0 ? | ||||
|                            files_array_get(model->files, model->idx) : | ||||
|                            NULL; | ||||
|             return true; | ||||
|         }); | ||||
|     return selected; | ||||
| } | ||||
| 
 | ||||
| ArchiveTabEnum archive_get_tab(ArchiveMainView* main_view) { | ||||
|     ArchiveTabEnum tab_id; | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             tab_id = model->tab_idx; | ||||
|             return true; | ||||
|         }); | ||||
|     return tab_id; | ||||
| } | ||||
| 
 | ||||
| void archive_set_tab(ArchiveMainView* main_view, ArchiveTabEnum tab) { | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             model->tab_idx = tab; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| uint8_t archive_get_depth(ArchiveMainView* main_view) { | ||||
|     uint8_t depth; | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             depth = model->depth; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     return depth; | ||||
| } | ||||
| 
 | ||||
| const char* archive_get_path(ArchiveMainView* main_view) { | ||||
|     return string_get_cstr(main_view->path); | ||||
| } | ||||
| const char* archive_get_name(ArchiveMainView* main_view) { | ||||
|     ArchiveFile_t* selected = archive_get_current_file(main_view); | ||||
|     return string_get_cstr(selected->name); | ||||
| } | ||||
| 
 | ||||
| void archive_set_name(ArchiveMainView* main_view, const char* name) { | ||||
|     furi_assert(main_view); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     string_set(main_view->name, name); | ||||
| } | ||||
| 
 | ||||
| void archive_browser_update(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
| 
 | ||||
|     archive_get_filenames(main_view, archive_get_tab(main_view), string_get_cstr(main_view->path)); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             uint16_t idx = 0; | ||||
|             while(idx < files_array_size(model->files)) { | ||||
|                 ArchiveFile_t* current = files_array_get(model->files, idx); | ||||
|                 if(!string_search(current->name, string_get_cstr(main_view->name))) { | ||||
|                     model->idx = idx; | ||||
|                     break; | ||||
|                 } | ||||
|                 ++idx; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     update_offset(main_view); | ||||
| } | ||||
| 
 | ||||
| void archive_view_add_item(ArchiveMainView* main_view, FileInfo* file_info, const char* name) { | ||||
|     furi_assert(main_view); | ||||
|     furi_assert(file_info); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     ArchiveFile_t item; | ||||
| 
 | ||||
|     if(filter_by_extension(file_info, get_tab_ext(archive_get_tab(main_view)), name)) { | ||||
|         ArchiveFile_t_init(&item); | ||||
|         string_init_set_str(item.name, name); | ||||
|         set_file_type(&item, file_info); | ||||
| 
 | ||||
|         with_view_model( | ||||
|             main_view->view, (ArchiveMainViewModel * model) { | ||||
|                 files_array_push_back(model->files, item); | ||||
|                 return true; | ||||
|             }); | ||||
| 
 | ||||
|         ArchiveFile_t_clear(&item); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void render_item_menu(Canvas* canvas, ArchiveMainViewModel* model) { | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_box(canvas, 71, 17, 57, 46); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     elements_slightly_rounded_frame(canvas, 70, 16, 58, 48); | ||||
| 
 | ||||
|     string_t menu[MENU_ITEMS]; | ||||
| 
 | ||||
|     string_init_set_str(menu[0], "Run in app"); | ||||
|     string_init_set_str(menu[1], "Pin"); | ||||
|     string_init_set_str(menu[2], "Rename"); | ||||
|     string_init_set_str(menu[3], "Delete"); | ||||
| 
 | ||||
|     ArchiveFile_t* selected = files_array_get(model->files, model->idx); | ||||
| 
 | ||||
|     if(!is_known_app(selected->type)) { | ||||
|         string_set_str(menu[0], "---"); | ||||
|         string_set_str(menu[1], "---"); | ||||
|         string_set_str(menu[2], "---"); | ||||
|     } else if(selected->fav) { | ||||
|         string_set_str(menu[1], "Unpin"); | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < MENU_ITEMS; i++) { | ||||
|         canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i])); | ||||
|         string_clear(menu[i]); | ||||
|     } | ||||
| 
 | ||||
|     canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); | ||||
| } | ||||
| 
 | ||||
| static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_draw_box(canvas, 0, 15 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_dot(canvas, 0, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 1, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 1); | ||||
| 
 | ||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 11); | ||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT); | ||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); | ||||
| } | ||||
| 
 | ||||
| static void draw_list(Canvas* canvas, ArchiveMainViewModel* model) { | ||||
|     furi_assert(model); | ||||
| 
 | ||||
|     size_t array_size = files_array_size(model->files); | ||||
|     bool scrollbar = array_size > 4; | ||||
| 
 | ||||
|     for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { | ||||
|         string_t str_buff; | ||||
|         char cstr_buff[MAX_NAME_LEN]; | ||||
| 
 | ||||
|         size_t idx = CLAMP(i + model->list_offset, array_size, 0); | ||||
|         ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); | ||||
| 
 | ||||
|         string_init_set(str_buff, file->name); | ||||
|         string_right(str_buff, string_search_rchar(str_buff, '/') + 1); | ||||
|         strlcpy(cstr_buff, string_get_cstr(str_buff), string_size(str_buff) + 1); | ||||
| 
 | ||||
|         if(is_known_app(file->type)) archive_trim_file_ext(cstr_buff); | ||||
| 
 | ||||
|         string_clean(str_buff); | ||||
|         string_set_str(str_buff, cstr_buff); | ||||
| 
 | ||||
|         elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); | ||||
| 
 | ||||
|         if(model->idx == idx) { | ||||
|             archive_draw_frame(canvas, i, scrollbar); | ||||
|         } else { | ||||
|             canvas_set_color(canvas, ColorBlack); | ||||
|         } | ||||
| 
 | ||||
|         canvas_draw_icon(canvas, 2, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); | ||||
|         canvas_draw_str(canvas, 15, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); | ||||
|         string_clear(str_buff); | ||||
|     } | ||||
| 
 | ||||
|     if(scrollbar) { | ||||
|         elements_scrollbar_pos(canvas, 126, 15, 49, model->idx, array_size); | ||||
|     } | ||||
| 
 | ||||
|     if(model->action == BrowserActionItemMenu) { | ||||
|         render_item_menu(canvas, model); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void archive_render_status_bar(Canvas* canvas, ArchiveMainViewModel* model) { | ||||
|     furi_assert(model); | ||||
| 
 | ||||
|     const char* tab_name = ArchiveTabNames[model->tab_idx]; | ||||
| 
 | ||||
|     canvas_draw_icon(canvas, 0, 0, &I_Background_128x11); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_box(canvas, 0, 0, 50, 13); | ||||
|     canvas_draw_box(canvas, 107, 0, 20, 13); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_draw_frame(canvas, 1, 0, 50, 12); | ||||
|     canvas_draw_line(canvas, 0, 1, 0, 11); | ||||
|     canvas_draw_line(canvas, 1, 12, 49, 12); | ||||
|     canvas_draw_str_aligned(canvas, 26, 9, AlignCenter, AlignBottom, tab_name); | ||||
| 
 | ||||
|     canvas_draw_frame(canvas, 108, 0, 20, 12); | ||||
|     canvas_draw_line(canvas, 107, 1, 107, 11); | ||||
|     canvas_draw_line(canvas, 108, 12, 126, 12); | ||||
| 
 | ||||
|     if(model->tab_idx > 0) { | ||||
|         canvas_draw_icon(canvas, 112, 2, &I_ButtonLeft_4x7); | ||||
|     } | ||||
|     if(model->tab_idx < SIZEOF_ARRAY(ArchiveTabNames) - 1) { | ||||
|         canvas_draw_icon(canvas, 120, 2, &I_ButtonRight_4x7); | ||||
|     } | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorWhite); | ||||
|     canvas_draw_dot(canvas, 50, 0); | ||||
|     canvas_draw_dot(canvas, 127, 0); | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| } | ||||
| 
 | ||||
| void archive_view_render(Canvas* canvas, void* model) { | ||||
|     ArchiveMainViewModel* m = model; | ||||
| 
 | ||||
|     archive_render_status_bar(canvas, model); | ||||
| 
 | ||||
|     if(files_array_size(m->files) > 0) { | ||||
|         draw_list(canvas, m); | ||||
|     } else { | ||||
|         canvas_draw_str_aligned( | ||||
|             canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| View* archive_main_get_view(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
|     return main_view->view; | ||||
| } | ||||
| 
 | ||||
| static void archive_show_file_menu(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             ArchiveFile_t* selected; | ||||
|             selected = files_array_get(model->files, model->idx); | ||||
|             model->action = BrowserActionItemMenu; | ||||
|             model->menu_idx = 0; | ||||
|             selected->fav = is_known_app(selected->type) ? archive_is_favorite( | ||||
|                                                                string_get_cstr(main_view->path), | ||||
|                                                                string_get_cstr(selected->name)) : | ||||
|                                                            false; | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void archive_close_file_menu(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             model->action = BrowserActionBrowse; | ||||
|             model->menu_idx = 0; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void archive_run_in_app( | ||||
|     ArchiveMainView* main_view, | ||||
|     ArchiveFile_t* selected, | ||||
|     bool full_path_provided) { | ||||
|     Loader* loader = furi_record_open("loader"); | ||||
| 
 | ||||
|     string_t full_path; | ||||
| 
 | ||||
|     if(!full_path_provided) { | ||||
|         string_init_printf( | ||||
|             full_path, "%s/%s", string_get_cstr(main_view->path), string_get_cstr(selected->name)); | ||||
|     } else { | ||||
|         string_init_set(full_path, selected->name); | ||||
|     } | ||||
|     loader_start(loader, flipper_app_name[selected->type], string_get_cstr(full_path)); | ||||
| 
 | ||||
|     string_clear(full_path); | ||||
|     furi_record_close("loader"); | ||||
| } | ||||
| 
 | ||||
| static void archive_file_menu_callback(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
| 
 | ||||
|     ArchiveFile_t* selected = archive_get_current_file(main_view); | ||||
|     const char* path = archive_get_path(main_view); | ||||
|     const char* name = archive_get_name(main_view); | ||||
| 
 | ||||
|     uint8_t idx; | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             idx = model->menu_idx; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     switch(idx) { | ||||
|     case 0: | ||||
|         if(is_known_app(selected->type)) { | ||||
|             archive_run_in_app(main_view, selected, false); | ||||
|         } | ||||
|         break; | ||||
|     case 1: | ||||
|         if(is_known_app(selected->type)) { | ||||
|             if(!archive_is_favorite(path, name)) { | ||||
|                 archive_set_name(main_view, string_get_cstr(selected->name)); | ||||
|                 archive_add_to_favorites(path, name); | ||||
|             } else { | ||||
|                 // delete from favorites
 | ||||
|                 archive_favorites_delete(path, name); | ||||
|             } | ||||
|             archive_close_file_menu(main_view); | ||||
|         } | ||||
|         break; | ||||
|     case 2: | ||||
|         // open rename view
 | ||||
|         if(is_known_app(selected->type)) { | ||||
|             main_view->callback(ArchiveBrowserEventRename, main_view->context); | ||||
|         } | ||||
|         break; | ||||
|     case 3: | ||||
|         // confirmation?
 | ||||
|         archive_delete_file(main_view, main_view->path, selected->name); | ||||
|         archive_close_file_menu(main_view); | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         archive_close_file_menu(main_view); | ||||
|         break; | ||||
|     } | ||||
|     selected = NULL; | ||||
| } | ||||
| 
 | ||||
| static void archive_switch_dir(ArchiveMainView* main_view, const char* path) { | ||||
|     furi_assert(main_view); | ||||
|     furi_assert(path); | ||||
| 
 | ||||
|     string_set(main_view->path, path); | ||||
|     archive_get_filenames(main_view, archive_get_tab(main_view), string_get_cstr(main_view->path)); | ||||
|     update_offset(main_view); | ||||
| } | ||||
| 
 | ||||
| void archive_switch_tab(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             model->idx = 0; | ||||
|             model->depth = 0; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     archive_switch_dir(main_view, tab_default_paths[archive_get_tab(main_view)]); | ||||
| } | ||||
| 
 | ||||
| static void archive_enter_dir(ArchiveMainView* main_view, string_t name) { | ||||
|     furi_assert(main_view); | ||||
|     furi_assert(name); | ||||
| 
 | ||||
|     // update last index
 | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             model->last_idx[model->depth] = | ||||
|                 CLAMP(model->idx, files_array_size(model->files) - 1, 0); | ||||
|             model->idx = 0; | ||||
|             model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     string_cat(main_view->path, "/"); | ||||
|     string_cat(main_view->path, main_view->name); | ||||
| 
 | ||||
|     archive_switch_dir(main_view, string_get_cstr(main_view->path)); | ||||
| } | ||||
| 
 | ||||
| static void archive_leave_dir(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
| 
 | ||||
|     char* last_char_ptr = strrchr(string_get_cstr(main_view->path), '/'); | ||||
| 
 | ||||
|     if(last_char_ptr) { | ||||
|         size_t pos = last_char_ptr - string_get_cstr(main_view->path); | ||||
|         string_left(main_view->path, pos); | ||||
|     } | ||||
| 
 | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             model->depth = CLAMP(model->depth - 1, MAX_DEPTH, 0); | ||||
|             model->idx = model->last_idx[model->depth]; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     archive_switch_dir(main_view, string_get_cstr(main_view->path)); | ||||
| } | ||||
| 
 | ||||
| bool archive_view_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveMainView* main_view = context; | ||||
| 
 | ||||
|     BrowserActionEnum action; | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             action = model->action; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     switch(action) { | ||||
|     case BrowserActionItemMenu: | ||||
| 
 | ||||
|         if(event->type == InputTypeShort) { | ||||
|             if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||
|                 with_view_model( | ||||
|                     main_view->view, (ArchiveMainViewModel * model) { | ||||
|                         if(event->key == InputKeyUp) { | ||||
|                             model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS; | ||||
|                         } else if(event->key == InputKeyDown) { | ||||
|                             model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS; | ||||
|                         } | ||||
|                         return true; | ||||
|                     }); | ||||
|             } | ||||
| 
 | ||||
|             if(event->key == InputKeyOk) { | ||||
|                 archive_file_menu_callback(main_view); | ||||
|             } else if(event->key == InputKeyBack) { | ||||
|                 archive_close_file_menu(main_view); | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     case BrowserActionBrowse: | ||||
| 
 | ||||
|         if(event->type == InputTypeShort) { | ||||
|             if(event->key == InputKeyLeft) { | ||||
|                 ArchiveTabEnum tab = archive_get_tab(main_view); | ||||
|                 if(tab) { | ||||
|                     archive_set_tab(main_view, CLAMP(tab - 1, ArchiveTabTotal, 0)); | ||||
|                     archive_switch_tab(main_view); | ||||
|                     return true; | ||||
|                 } | ||||
|             } else if(event->key == InputKeyRight) { | ||||
|                 ArchiveTabEnum tab = archive_get_tab(main_view); | ||||
| 
 | ||||
|                 if(tab < ArchiveTabTotal - 1) { | ||||
|                     archive_set_tab(main_view, CLAMP(tab + 1, ArchiveTabTotal - 1, 0)); | ||||
|                     archive_switch_tab(main_view); | ||||
|                     return true; | ||||
|                 } | ||||
| 
 | ||||
|             } else if(event->key == InputKeyBack) { | ||||
|                 if(!archive_get_depth(main_view)) { | ||||
|                     main_view->callback(ArchiveBrowserEventExit, main_view->context); | ||||
|                 } else { | ||||
|                     archive_leave_dir(main_view); | ||||
|                 } | ||||
| 
 | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||
|             with_view_model( | ||||
|                 main_view->view, (ArchiveMainViewModel * model) { | ||||
|                     uint16_t num_elements = (uint16_t)files_array_size(model->files); | ||||
|                     if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||
|                         if(event->key == InputKeyUp) { | ||||
|                             model->idx = ((model->idx - 1) + num_elements) % num_elements; | ||||
|                         } else if(event->key == InputKeyDown) { | ||||
|                             model->idx = (model->idx + 1) % num_elements; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     return true; | ||||
|                 }); | ||||
|             update_offset(main_view); | ||||
|         } | ||||
| 
 | ||||
|         if(event->key == InputKeyOk) { | ||||
|             ArchiveFile_t* selected = archive_get_current_file(main_view); | ||||
| 
 | ||||
|             if(selected) { | ||||
|                 archive_set_name(main_view, string_get_cstr(selected->name)); | ||||
|                 if(selected->type == ArchiveFileTypeFolder) { | ||||
|                     if(event->type == InputTypeShort) { | ||||
|                         archive_enter_dir(main_view, main_view->name); | ||||
|                     } else if(event->type == InputTypeLong) { | ||||
|                         archive_show_file_menu(main_view); | ||||
|                     } | ||||
|                 } else { | ||||
|                     if(event->type == InputTypeShort) { | ||||
|                         if(archive_get_tab(main_view) == ArchiveTabFavorites) { | ||||
|                             if(is_known_app(selected->type)) { | ||||
|                                 archive_run_in_app(main_view, selected, true); | ||||
|                             } | ||||
|                         } else { | ||||
|                             archive_show_file_menu(main_view); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| ArchiveMainView* main_view_alloc() { | ||||
|     ArchiveMainView* main_view = furi_alloc(sizeof(ArchiveMainView)); | ||||
|     main_view->view = view_alloc(); | ||||
|     view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(ArchiveMainViewModel)); | ||||
|     view_set_context(main_view->view, main_view); | ||||
|     view_set_draw_callback(main_view->view, (ViewDrawCallback)archive_view_render); | ||||
|     view_set_input_callback(main_view->view, archive_view_input); | ||||
| 
 | ||||
|     string_init(main_view->name); | ||||
|     string_init(main_view->path); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             files_array_init(model->files); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     return main_view; | ||||
| } | ||||
| 
 | ||||
| void main_view_free(ArchiveMainView* main_view) { | ||||
|     furi_assert(main_view); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         main_view->view, (ArchiveMainViewModel * model) { | ||||
|             files_array_clear(model->files); | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     string_clear(main_view->name); | ||||
|     string_clear(main_view->path); | ||||
| 
 | ||||
|     view_free(main_view->view); | ||||
|     free(main_view); | ||||
| } | ||||
							
								
								
									
										117
									
								
								applications/archive/views/archive_main_view.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								applications/archive/views/archive_main_view.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include <storage/storage.h> | ||||
| #include "../helpers/archive_files.h" | ||||
| #include "../helpers/archive_favorites.h" | ||||
| 
 | ||||
| #define MAX_LEN_PX 110 | ||||
| #define MAX_NAME_LEN 255 | ||||
| #define FRAME_HEIGHT 12 | ||||
| #define MENU_ITEMS 4 | ||||
| #define MAX_DEPTH 32 | ||||
| 
 | ||||
| typedef enum { | ||||
|     ArchiveTabFavorites, | ||||
|     ArchiveTabLFRFID, | ||||
|     ArchiveTabSubGhz, | ||||
|     ArchiveTabNFC, | ||||
|     ArchiveTabIButton, | ||||
|     ArchiveTabIrda, | ||||
|     ArchiveTabBrowser, | ||||
|     ArchiveTabTotal, | ||||
| } ArchiveTabEnum; | ||||
| 
 | ||||
| static const char* known_ext[] = { | ||||
|     [ArchiveFileTypeIButton] = ".ibtn", | ||||
|     [ArchiveFileTypeNFC] = ".nfc", | ||||
|     [ArchiveFileTypeSubGhz] = ".sub", | ||||
|     [ArchiveFileTypeLFRFID] = ".rfid", | ||||
|     [ArchiveFileTypeIrda] = ".ir", | ||||
| }; | ||||
| 
 | ||||
| static inline const char* get_tab_ext(ArchiveTabEnum tab) { | ||||
|     switch(tab) { | ||||
|     case ArchiveTabIButton: | ||||
|         return known_ext[ArchiveFileTypeIButton]; | ||||
|     case ArchiveTabNFC: | ||||
|         return known_ext[ArchiveFileTypeNFC]; | ||||
|     case ArchiveTabSubGhz: | ||||
|         return known_ext[ArchiveFileTypeSubGhz]; | ||||
|     case ArchiveTabLFRFID: | ||||
|         return known_ext[ArchiveFileTypeLFRFID]; | ||||
|     case ArchiveTabIrda: | ||||
|         return known_ext[ArchiveFileTypeIrda]; | ||||
|     default: | ||||
|         return "*"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| typedef enum { | ||||
|     ArchiveBrowserEventRename, | ||||
|     ArchiveBrowserEventExit, | ||||
|     ArchiveBrowserEventLeaveDir, | ||||
| } ArchiveBrowserEvent; | ||||
| 
 | ||||
| typedef struct ArchiveMainView ArchiveMainView; | ||||
| 
 | ||||
| typedef void (*ArchiveMainViewCallback)(ArchiveBrowserEvent event, void* context); | ||||
| 
 | ||||
| typedef enum { | ||||
|     BrowserActionBrowse, | ||||
|     BrowserActionItemMenu, | ||||
|     BrowserActionTotal, | ||||
| } BrowserActionEnum; | ||||
| 
 | ||||
| struct ArchiveMainView { | ||||
|     View* view; | ||||
|     ArchiveMainViewCallback callback; | ||||
|     void* context; | ||||
| 
 | ||||
|     string_t name; | ||||
|     string_t path; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     ArchiveTabEnum tab_idx; | ||||
|     BrowserActionEnum action; | ||||
|     files_array_t files; | ||||
| 
 | ||||
|     uint8_t depth; | ||||
|     uint8_t menu_idx; | ||||
| 
 | ||||
|     uint16_t idx; | ||||
|     uint16_t last_idx[MAX_DEPTH]; | ||||
|     uint16_t list_offset; | ||||
| 
 | ||||
| } ArchiveMainViewModel; | ||||
| 
 | ||||
| void archive_browser_set_callback( | ||||
|     ArchiveMainView* main_view, | ||||
|     ArchiveMainViewCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| View* archive_main_get_view(ArchiveMainView* main_view); | ||||
| 
 | ||||
| ArchiveMainView* main_view_alloc(); | ||||
| void main_view_free(ArchiveMainView* main_view); | ||||
| 
 | ||||
| void archive_file_array_remove_selected(ArchiveMainView* main_view); | ||||
| void archive_file_array_clean(ArchiveMainView* main_view); | ||||
| 
 | ||||
| void archive_view_add_item(ArchiveMainView* main_view, FileInfo* file_info, const char* name); | ||||
| void archive_browser_update(ArchiveMainView* main_view); | ||||
| 
 | ||||
| size_t archive_file_array_size(ArchiveMainView* main_view); | ||||
| ArchiveFile_t* archive_get_current_file(ArchiveMainView* main_view); | ||||
| const char* archive_get_path(ArchiveMainView* main_view); | ||||
| const char* archive_get_name(ArchiveMainView* main_view); | ||||
| void archive_set_name(ArchiveMainView* main_view, const char* name); | ||||
| 
 | ||||
| static inline bool is_known_app(ArchiveFileTypeEnum type) { | ||||
|     return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); | ||||
| } | ||||
							
								
								
									
										25
									
								
								applications/bt/bt_service/bt.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										25
									
								
								applications/bt/bt_service/bt.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1,4 +1,5 @@ | ||||
| #include "bt_i.h" | ||||
| #include "battery_service.h" | ||||
| 
 | ||||
| #define BT_SERVICE_TAG "BT" | ||||
| 
 | ||||
| @ -21,6 +22,17 @@ static ViewPort* bt_statusbar_view_port_alloc() { | ||||
|     return statusbar_view_port; | ||||
| } | ||||
| 
 | ||||
| static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) { | ||||
|     furi_assert(bt); | ||||
|     string_t pin_str; | ||||
|     string_init_printf(pin_str, "%06d", pin); | ||||
|     dialog_message_set_text( | ||||
|         bt->dialog_message, string_get_cstr(pin_str), 64, 32, AlignCenter, AlignCenter); | ||||
|     dialog_message_set_buttons(bt->dialog_message, "Back", NULL, NULL); | ||||
|     dialog_message_show(bt->dialogs, bt->dialog_message); | ||||
|     string_clear(pin_str); | ||||
| } | ||||
| 
 | ||||
| Bt* bt_alloc() { | ||||
|     Bt* bt = furi_alloc(sizeof(Bt)); | ||||
|     // Load settings
 | ||||
| @ -40,13 +52,16 @@ Bt* bt_alloc() { | ||||
|     bt->gui = furi_record_open("gui"); | ||||
|     gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft); | ||||
| 
 | ||||
|     // Dialogs
 | ||||
|     bt->dialogs = furi_record_open("dialogs"); | ||||
|     bt->dialog_message = dialog_message_alloc(); | ||||
| 
 | ||||
|     return bt; | ||||
| } | ||||
| 
 | ||||
| int32_t bt_srv() { | ||||
|     Bt* bt = bt_alloc(); | ||||
|     furi_record_create("bt", bt); | ||||
|     furi_hal_bt_init(); | ||||
| 
 | ||||
|     if(!furi_hal_bt_wait_startup()) { | ||||
|         FURI_LOG_E(BT_SERVICE_TAG, "Core2 startup failed"); | ||||
| @ -68,6 +83,14 @@ int32_t bt_srv() { | ||||
|         if(message.type == BtMessageTypeUpdateStatusbar) { | ||||
|             // Update statusbar
 | ||||
|             view_port_enabled_set(bt->statusbar_view_port, furi_hal_bt_is_alive()); | ||||
|         } else if(message.type == BtMessageTypeUpdateBatteryLevel) { | ||||
|             // Update battery level
 | ||||
|             if(furi_hal_bt_is_alive()) { | ||||
|                 battery_svc_update_level(message.data.battery_level); | ||||
|             } | ||||
|         } else if(message.type == BtMessageTypePinCodeShow) { | ||||
|             // Display PIN code
 | ||||
|             bt_pin_code_show_event_handler(bt, message.data.pin_code); | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
|  | ||||
| @ -1,3 +1,18 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct Bt Bt; | ||||
| 
 | ||||
| void bt_update_battery_level(Bt* bt, uint8_t battery_level); | ||||
| 
 | ||||
| bool bt_pin_code_show(Bt* bt, uint32_t pin_code); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										15
									
								
								applications/bt/bt_service/bt_api.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										15
									
								
								applications/bt/bt_service/bt_api.c
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,15 @@ | ||||
| #include "bt.h" | ||||
| #include "bt_i.h" | ||||
| 
 | ||||
| void bt_update_battery_level(Bt* bt, uint8_t battery_level) { | ||||
|     furi_assert(bt); | ||||
|     BtMessage message = { | ||||
|         .type = BtMessageTypeUpdateBatteryLevel, .data.battery_level = battery_level}; | ||||
|     furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| bool bt_pin_code_show(Bt* bt, uint32_t pin_code) { | ||||
|     furi_assert(bt); | ||||
|     BtMessage message = {.type = BtMessageTypePinCodeShow, .data.pin_code = pin_code}; | ||||
|     return osMessageQueuePut(bt->message_queue, &message, 0, 0) == osOK; | ||||
| } | ||||
| @ -9,15 +9,24 @@ | ||||
| #include <gui/view_port.h> | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #include <applications/dialogs/dialogs.h> | ||||
| 
 | ||||
| #include "../bt_settings.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     BtMessageTypeUpdateStatusbar, | ||||
|     BtMessageTypeUpdateBatteryLevel, | ||||
|     BtMessageTypePinCodeShow, | ||||
| } BtMessageType; | ||||
| 
 | ||||
| typedef union { | ||||
|     uint32_t pin_code; | ||||
|     uint8_t battery_level; | ||||
| } BtMessageData; | ||||
| 
 | ||||
| typedef struct { | ||||
|     BtMessageType type; | ||||
|     void* param; | ||||
|     BtMessageData data; | ||||
| } BtMessage; | ||||
| 
 | ||||
| struct Bt { | ||||
| @ -26,4 +35,6 @@ struct Bt { | ||||
|     osTimerId_t update_status_timer; | ||||
|     Gui* gui; | ||||
|     ViewPort* statusbar_view_port; | ||||
|     DialogsApp* dialogs; | ||||
|     DialogMessage* dialog_message; | ||||
| }; | ||||
|  | ||||
| @ -68,10 +68,10 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { | ||||
|                 canvas_draw_str(canvas, 73, item_text_y, "<"); | ||||
|             } | ||||
| 
 | ||||
|             canvas_draw_str(canvas, 84, item_text_y, string_get_cstr(item->current_value_text)); | ||||
|             canvas_draw_str(canvas, 80, item_text_y, string_get_cstr(item->current_value_text)); | ||||
| 
 | ||||
|             if(item->current_value_index < (item->values_count - 1)) { | ||||
|                 canvas_draw_str(canvas, 113, item_text_y, ">"); | ||||
|                 canvas_draw_str(canvas, 115, item_text_y, ">"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -235,6 +235,21 @@ void variable_item_list_free(VariableItemList* variable_item_list) { | ||||
|     free(variable_item_list); | ||||
| } | ||||
| 
 | ||||
| void variable_item_list_clean(VariableItemList* variable_item_list) { | ||||
|     furi_assert(variable_item_list); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         variable_item_list->view, (VariableItemListModel * model) { | ||||
|             VariableItemArray_it_t it; | ||||
|             for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); | ||||
|                 VariableItemArray_next(it)) { | ||||
|                 string_clean(VariableItemArray_ref(it)->current_value_text); | ||||
|             } | ||||
|             VariableItemArray_clean(model->items); | ||||
|             return false; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| View* variable_item_list_get_view(VariableItemList* variable_item_list) { | ||||
|     furi_assert(variable_item_list); | ||||
|     return variable_item_list->view; | ||||
|  | ||||
| @ -18,6 +18,9 @@ VariableItemList* variable_item_list_alloc(); | ||||
|  * @param variable_item_list VariableItemList instance | ||||
|  */ | ||||
| void variable_item_list_free(VariableItemList* variable_item_list); | ||||
| 
 | ||||
| void variable_item_list_clean(VariableItemList* variable_item_list); | ||||
| 
 | ||||
| View* variable_item_list_get_view(VariableItemList* variable_item_list); | ||||
| 
 | ||||
| /** Add item to VariableItemList
 | ||||
|  | ||||
| @ -118,6 +118,20 @@ static void widget_add_element(Widget* widget, WidgetElement* element) { | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void widget_add_string_multi_element( | ||||
|     Widget* widget, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     Font font, | ||||
|     const char* text) { | ||||
|     furi_assert(widget); | ||||
|     WidgetElement* string_multi_element = | ||||
|         widget_element_string_multi_create(x, y, horizontal, vertical, font, text); | ||||
|     widget_add_element(widget, string_multi_element); | ||||
| } | ||||
| 
 | ||||
| void widget_add_string_element( | ||||
|     Widget* widget, | ||||
|     uint8_t x, | ||||
|  | ||||
| @ -26,6 +26,23 @@ void widget_clear(Widget* widget); | ||||
|  */ | ||||
| View* widget_get_view(Widget* widget); | ||||
| 
 | ||||
| /** Add Multi String Element
 | ||||
|  * @param widget Widget instance | ||||
|  * @param x - x coordinate | ||||
|  * @param y - y coordinate | ||||
|  * @param horizontal - Align instance | ||||
|  * @param vertical - Align instance | ||||
|  * @param font Font instance | ||||
|  */ | ||||
| void widget_add_string_multi_element( | ||||
|     Widget* widget, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     Font font, | ||||
|     const char* text); | ||||
| 
 | ||||
| /** Add String Element
 | ||||
|  * @param widget Widget instance | ||||
|  * @param x - x coordinate | ||||
|  | ||||
| @ -30,7 +30,18 @@ static bool gui_button_input(InputEvent* event, WidgetElement* element) { | ||||
|     GuiButtonModel* model = element->model; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if((event->type == InputTypeShort) && model->callback) { | ||||
|     if(model->callback == NULL) return consumed; | ||||
| 
 | ||||
|     if(event->key == InputKeyOk && event->type == InputTypePress && | ||||
|        model->button_type == GuiButtonTypeCenter) { | ||||
|         model->callback(GuiButtonTypeCenterPress, model->context); | ||||
|         consumed = true; | ||||
|     } else if( | ||||
|         event->key == InputKeyOk && event->type == InputTypeRelease && | ||||
|         model->button_type == GuiButtonTypeCenter) { | ||||
|         model->callback(GuiButtonTypeCenterRelease, model->context); | ||||
|         consumed = true; | ||||
|     } else if(event->type == InputTypeShort) { | ||||
|         if((model->button_type == GuiButtonTypeLeft) && (event->key == InputKeyLeft)) { | ||||
|             model->callback(model->button_type, model->context); | ||||
|             consumed = true; | ||||
|  | ||||
| @ -6,6 +6,8 @@ typedef enum { | ||||
|     GuiButtonTypeLeft, | ||||
|     GuiButtonTypeCenter, | ||||
|     GuiButtonTypeRight, | ||||
|     GuiButtonTypeCenterPress, | ||||
|     GuiButtonTypeCenterRelease, | ||||
| } GuiButtonType; | ||||
| 
 | ||||
| typedef void (*ButtonCallback)(GuiButtonType result, void* context); | ||||
| @ -28,6 +30,15 @@ struct WidgetElement { | ||||
|     Widget* parent; | ||||
| }; | ||||
| 
 | ||||
| /* Create multi string element */ | ||||
| WidgetElement* widget_element_string_multi_create( | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     Font font, | ||||
|     const char* text); | ||||
| 
 | ||||
| /* Create string element */ | ||||
| WidgetElement* widget_element_string_create( | ||||
|     uint8_t x, | ||||
|  | ||||
| @ -0,0 +1,67 @@ | ||||
| #include "widget_element_i.h" | ||||
| #include <m-string.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t x; | ||||
|     uint8_t y; | ||||
|     Align horizontal; | ||||
|     Align vertical; | ||||
|     Font font; | ||||
|     string_t text; | ||||
| } GuiStringMultiModel; | ||||
| 
 | ||||
| static void gui_string_multi_draw(Canvas* canvas, WidgetElement* element) { | ||||
|     furi_assert(canvas); | ||||
|     furi_assert(element); | ||||
|     GuiStringMultiModel* model = element->model; | ||||
| 
 | ||||
|     if(string_size(model->text)) { | ||||
|         canvas_set_font(canvas, model->font); | ||||
|         elements_multiline_text_aligned( | ||||
|             canvas, | ||||
|             model->x, | ||||
|             model->y, | ||||
|             model->horizontal, | ||||
|             model->vertical, | ||||
|             string_get_cstr(model->text)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void gui_string_multi_free(WidgetElement* gui_string) { | ||||
|     furi_assert(gui_string); | ||||
| 
 | ||||
|     GuiStringMultiModel* model = gui_string->model; | ||||
|     string_clear(model->text); | ||||
|     free(gui_string->model); | ||||
|     free(gui_string); | ||||
| } | ||||
| 
 | ||||
| WidgetElement* widget_element_string_multi_create( | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical, | ||||
|     Font font, | ||||
|     const char* text) { | ||||
|     furi_assert(text); | ||||
| 
 | ||||
|     // Allocate and init model
 | ||||
|     GuiStringMultiModel* model = furi_alloc(sizeof(GuiStringMultiModel)); | ||||
|     model->x = x; | ||||
|     model->y = y; | ||||
|     model->horizontal = horizontal; | ||||
|     model->vertical = vertical; | ||||
|     model->font = font; | ||||
|     string_init_set_str(model->text, text); | ||||
| 
 | ||||
|     // Allocate and init Element
 | ||||
|     WidgetElement* gui_string = furi_alloc(sizeof(WidgetElement)); | ||||
|     gui_string->parent = NULL; | ||||
|     gui_string->input = NULL; | ||||
|     gui_string->draw = gui_string_multi_draw; | ||||
|     gui_string->free = gui_string_multi_free; | ||||
|     gui_string->model = model; | ||||
| 
 | ||||
|     return gui_string; | ||||
| } | ||||
| @ -25,9 +25,9 @@ static void signal_received_callback(void* context, IrdaWorkerSignal* received_s | ||||
|             sizeof(buf), | ||||
|             "%s, A:0x%0*lX, C:0x%0*lX%s\r\n", | ||||
|             irda_get_protocol_name(message->protocol), | ||||
|             irda_get_protocol_address_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), | ||||
|             message->address, | ||||
|             irda_get_protocol_command_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), | ||||
|             message->command, | ||||
|             message->repeat ? " R" : ""); | ||||
|         cli_write(cli, (uint8_t*)buf, buf_cnt); | ||||
| @ -98,6 +98,20 @@ static bool parse_message(const char* str, IrdaMessage* message) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     uint32_t address_length = irda_get_protocol_address_length(protocol); | ||||
|     uint32_t address_mask = (1LU << address_length) - 1; | ||||
|     if(address != (address & address_mask)) { | ||||
|         printf("Address out of range (mask 0x%08lX): 0x%lX\r\n", address_mask, address); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     uint32_t command_length = irda_get_protocol_command_length(protocol); | ||||
|     uint32_t command_mask = (1LU << command_length) - 1; | ||||
|     if(command != (command & command_mask)) { | ||||
|         printf("Command out of range (mask 0x%08lX): 0x%lX\r\n", command_mask, command); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     message->protocol = protocol; | ||||
|     message->address = address; | ||||
|     message->command = command; | ||||
|  | ||||
| @ -45,9 +45,9 @@ size_t IrdaAppFileParser::stringify_message( | ||||
|         "%.31s %.31s A:%0*lX C:%0*lX\n", | ||||
|         name, | ||||
|         irda_get_protocol_name(protocol), | ||||
|         irda_get_protocol_address_length(protocol), | ||||
|         ROUND_UP_TO(irda_get_protocol_address_length(protocol), 4), | ||||
|         message.address, | ||||
|         irda_get_protocol_command_length(protocol), | ||||
|         ROUND_UP_TO(irda_get_protocol_command_length(protocol), 4), | ||||
|         message.command); | ||||
| 
 | ||||
|     furi_assert(written < buf_size); | ||||
| @ -162,8 +162,8 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     int address_length = irda_get_protocol_address_length(protocol); | ||||
|     uint32_t address_mask = (1LU << (4 * address_length)) - 1; | ||||
|     uint32_t address_length = irda_get_protocol_address_length(protocol); | ||||
|     uint32_t address_mask = (1LU << address_length) - 1; | ||||
|     if(address != (address & address_mask)) { | ||||
|         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); | ||||
|         FURI_LOG_E( | ||||
| @ -176,8 +176,8 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     int command_length = irda_get_protocol_command_length(protocol); | ||||
|     uint32_t command_mask = (1LU << (4 * command_length)) - 1; | ||||
|     uint32_t command_length = irda_get_protocol_command_length(protocol); | ||||
|     uint32_t command_mask = (1LU << command_length) - 1; | ||||
|     if(command != (command & command_mask)) { | ||||
|         size_t end_of_str = MIN(str.find_last_not_of(" \t\r\n") + 1, (size_t)30); | ||||
|         FURI_LOG_E( | ||||
|  | ||||
| @ -29,9 +29,9 @@ void IrdaAppSceneEditDelete::on_enter(IrdaApp* app) { | ||||
|                 "%s\n%s\nA=0x%0*lX C=0x%0*lX", | ||||
|                 remote_manager->get_button_name(app->get_current_button()).c_str(), | ||||
|                 irda_get_protocol_name(message->protocol), | ||||
|                 irda_get_protocol_address_length(message->protocol), | ||||
|                 ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), | ||||
|                 message->address, | ||||
|                 irda_get_protocol_command_length(message->protocol), | ||||
|                 ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), | ||||
|                 message->command); | ||||
|         } else { | ||||
|             app->set_text_store( | ||||
|  | ||||
| @ -13,7 +13,7 @@ void IrdaAppSceneLearnEnterName::on_enter(IrdaApp* app) { | ||||
|             0, | ||||
|             "%.4s_%0*lX", | ||||
|             irda_get_protocol_name(message->protocol), | ||||
|             irda_get_protocol_command_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), | ||||
|             message->command); | ||||
|     } else { | ||||
|         auto raw_signal = signal.get_raw_signal(); | ||||
|  | ||||
| @ -27,9 +27,9 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { | ||||
|         app->set_text_store( | ||||
|             1, | ||||
|             "A: 0x%0*lX\nC: 0x%0*lX\n", | ||||
|             irda_get_protocol_address_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), | ||||
|             message->address, | ||||
|             irda_get_protocol_command_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), | ||||
|             message->command); | ||||
|         dialog_ex_set_header(dialog_ex, app->get_text_store(0), 95, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text(dialog_ex, app->get_text_store(1), 75, 23, AlignLeft, AlignTop); | ||||
|  | ||||
| @ -64,18 +64,18 @@ static void signal_received_callback(void* context, IrdaWorkerSignal* received_s | ||||
|             sizeof(irda_monitor->display_text), | ||||
|             "%s\nA:0x%0*lX\nC:0x%0*lX\n%s\n", | ||||
|             irda_get_protocol_name(message->protocol), | ||||
|             irda_get_protocol_address_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), | ||||
|             message->address, | ||||
|             irda_get_protocol_command_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), | ||||
|             message->command, | ||||
|             message->repeat ? " R" : ""); | ||||
|         view_port_update(irda_monitor->view_port); | ||||
|         printf( | ||||
|             "== %s, A:0x%0*lX, C:0x%0*lX%s ==\r\n", | ||||
|             irda_get_protocol_name(message->protocol), | ||||
|             irda_get_protocol_address_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_address_length(message->protocol), 4), | ||||
|             message->address, | ||||
|             irda_get_protocol_command_length(message->protocol), | ||||
|             ROUND_UP_TO(irda_get_protocol_command_length(message->protocol), 4), | ||||
|             message->command, | ||||
|             message->repeat ? " R" : ""); | ||||
|     } else { | ||||
|  | ||||
| @ -18,6 +18,7 @@ | ||||
| #include <stm32wbxx.h> | ||||
| 
 | ||||
| #include <notification/notification-messages.h> | ||||
| #include <applications/bt/bt_service/bt.h> | ||||
| 
 | ||||
| #define POWER_OFF_TIMEOUT 30 | ||||
| 
 | ||||
| @ -39,6 +40,7 @@ struct Power { | ||||
| 
 | ||||
|     ValueMutex* menu_vm; | ||||
|     Cli* cli; | ||||
|     Bt* bt; | ||||
|     MenuItem* menu; | ||||
| 
 | ||||
|     PowerState state; | ||||
| @ -108,6 +110,8 @@ Power* power_alloc() { | ||||
|     power->cli = furi_record_open("cli"); | ||||
|     power_cli_init(power->cli, power); | ||||
| 
 | ||||
|     power->bt = furi_record_open("bt"); | ||||
| 
 | ||||
|     power->menu = menu_item_alloc_menu("Power", icon_animation_alloc(&A_Power_14)); | ||||
|     menu_item_subitem_add( | ||||
|         power->menu, menu_item_alloc_function("Off", NULL, power_menu_off_callback, power)); | ||||
| @ -206,13 +210,15 @@ int32_t power_srv(void* p) { | ||||
|         power->menu_vm, (Menu * menu) { menu_item_add(menu, power->menu); }); | ||||
| 
 | ||||
|     furi_record_create("power", power); | ||||
| 
 | ||||
|     uint8_t battery_level = 0; | ||||
|     uint8_t battery_level_prev = 0; | ||||
|     while(1) { | ||||
|         bool battery_low = false; | ||||
| 
 | ||||
|         with_view_model( | ||||
|             power->info_view, (PowerInfoModel * model) { | ||||
|                 model->charge = furi_hal_power_get_pct(); | ||||
|                 battery_level = model->charge; | ||||
|                 model->health = furi_hal_power_get_bat_health_pct(); | ||||
|                 model->capacity_remaining = furi_hal_power_get_battery_remaining_capacity(); | ||||
|                 model->capacity_full = furi_hal_power_get_battery_full_capacity(); | ||||
| @ -258,6 +264,11 @@ int32_t power_srv(void* p) { | ||||
| 
 | ||||
|         power_charging_indication_handler(power, notifications); | ||||
| 
 | ||||
|         if(battery_level_prev != battery_level) { | ||||
|             battery_level_prev = battery_level; | ||||
|             bt_update_battery_level(power->bt, battery_level); | ||||
|         } | ||||
| 
 | ||||
|         view_port_update(power->battery_view_port); | ||||
| 
 | ||||
|         osDelay(1024); | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| ADD_SCENE(subghz, start, Start) | ||||
| ADD_SCENE(subghz, receiver, Receiver) | ||||
| ADD_SCENE(subghz, receiver_config, ReceiverConfig) | ||||
| ADD_SCENE(subghz, receiver_info, ReceiverInfo) | ||||
| ADD_SCENE(subghz, save_name, SaveName) | ||||
| ADD_SCENE(subghz, save_success, SaveSuccess) | ||||
| ADD_SCENE(subghz, saved, Saved) | ||||
| ADD_SCENE(subghz, transmitter, Transmitter) | ||||
| ADD_SCENE(subghz, no_man, NoMan) | ||||
| ADD_SCENE(subghz, show_error, ShowError) | ||||
| ADD_SCENE(subghz, test, Test) | ||||
| ADD_SCENE(subghz, test_static, TestStatic) | ||||
| ADD_SCENE(subghz, test_carrier, TestCarrier) | ||||
|  | ||||
| @ -1,21 +1,102 @@ | ||||
| #include "../subghz_i.h" | ||||
| #include "../views/subghz_receiver.h" | ||||
| 
 | ||||
| static void subghz_scene_receiver_update_statusbar(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     char frequency_str[20]; | ||||
|     char preset_str[10]; | ||||
|     string_t history_stat_str; | ||||
|     string_init(history_stat_str); | ||||
|     if(!subghz_history_get_text_space_left(subghz->txrx->history, history_stat_str)) { | ||||
|         snprintf( | ||||
|             frequency_str, | ||||
|             sizeof(frequency_str), | ||||
|             "%03ld.%02ld", | ||||
|             subghz->txrx->frequency / 1000000 % 1000, | ||||
|             subghz->txrx->frequency / 10000 % 100); | ||||
|         if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || | ||||
|            subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { | ||||
|             snprintf(preset_str, sizeof(preset_str), "AM"); | ||||
|         } else if(subghz->txrx->preset == FuriHalSubGhzPreset2FSKAsync) { | ||||
|             snprintf(preset_str, sizeof(preset_str), "FM"); | ||||
|         } else { | ||||
|             furi_check(0); | ||||
|         } | ||||
|         subghz_receiver_add_data_statusbar( | ||||
|             subghz->subghz_receiver, frequency_str, preset_str, string_get_cstr(history_stat_str)); | ||||
|     } else { | ||||
|         subghz_receiver_add_data_statusbar( | ||||
|             subghz->subghz_receiver, string_get_cstr(history_stat_str), "", ""); | ||||
|         subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|     } | ||||
|     string_clear(history_stat_str); | ||||
| } | ||||
| 
 | ||||
| void subghz_scene_receiver_callback(SubghzReceverEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
|     view_dispatcher_send_custom_event(subghz->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| void subghz_scene_add_to_history_callback(SubGhzProtocolCommon* parser, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
|     string_t str_buff; | ||||
|     string_init(str_buff); | ||||
| 
 | ||||
|     if(subghz_history_add_to_history( | ||||
|            subghz->txrx->history, parser, subghz->txrx->frequency, subghz->txrx->preset)) { | ||||
|         subghz_protocol_reset(subghz->txrx->protocol); | ||||
|         string_clean(str_buff); | ||||
|         subghz_history_get_text_item_menu( | ||||
|             subghz->txrx->history, str_buff, subghz_history_get_item(subghz->txrx->history) - 1); | ||||
|         subghz_receiver_add_item_to_menu( | ||||
|             subghz->subghz_receiver, | ||||
|             string_get_cstr(str_buff), | ||||
|             subghz_history_get_type_protocol( | ||||
|                 subghz->txrx->history, subghz_history_get_item(subghz->txrx->history) - 1)); | ||||
|         subghz_scene_receiver_update_statusbar(subghz); | ||||
|     } | ||||
|     string_clear(str_buff); | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_receiver_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     SubghzReceiver* subghz_receiver = subghz->subghz_receiver; | ||||
| 
 | ||||
|     subghz_receiver_set_callback(subghz_receiver, subghz_scene_receiver_callback, subghz); | ||||
|     string_t str_buff; | ||||
|     string_init(str_buff); | ||||
| 
 | ||||
|     //Load history to receiver
 | ||||
|     subghz_receiver_exit(subghz->subghz_receiver); | ||||
|     for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) { | ||||
|         string_clean(str_buff); | ||||
|         subghz_history_get_text_item_menu(subghz->txrx->history, str_buff, i); | ||||
|         subghz_receiver_add_item_to_menu( | ||||
|             subghz->subghz_receiver, | ||||
|             string_get_cstr(str_buff), | ||||
|             subghz_history_get_type_protocol(subghz->txrx->history, i)); | ||||
|     } | ||||
|     string_clear(str_buff); | ||||
|     subghz_scene_receiver_update_statusbar(subghz); | ||||
|     subghz_receiver_set_callback(subghz->subghz_receiver, subghz_scene_receiver_callback, subghz); | ||||
|     subghz_protocol_enable_dump( | ||||
|         subghz->txrx->protocol, subghz_scene_add_to_history_callback, subghz); | ||||
| 
 | ||||
|     subghz_receiver_set_protocol(subghz_receiver, subghz->protocol_result, subghz->protocol); | ||||
|     subghz_receiver_set_worker(subghz_receiver, subghz->worker); | ||||
|     subghz->state_notifications = NOTIFICATION_RX_STATE; | ||||
|     if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||
|         subghz_rx_end(subghz->txrx->worker); | ||||
|         //subghz_sleep();
 | ||||
|         subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|     }; | ||||
|     if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) { | ||||
|         subghz_begin(subghz->txrx->preset); | ||||
|         subghz_rx(subghz->txrx->worker, subghz->txrx->frequency); | ||||
|         subghz->txrx->txrx_state = SubGhzTxRxStateRx; | ||||
|     } | ||||
|     if(subghz->txrx->idx_menu_chosen != 0) { | ||||
|         subghz_receiver_set_idx_menu(subghz->subghz_receiver, subghz->txrx->idx_menu_chosen); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewReceiver); | ||||
| } | ||||
| 
 | ||||
| @ -24,51 +105,43 @@ const bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case SubghzReceverEventSave: | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             subghz->frequency = subghz_receiver_get_frequency(subghz->subghz_receiver); | ||||
|             subghz->preset = subghz_receiver_get_preset(subghz->subghz_receiver); | ||||
|             subghz->protocol_result = subghz_receiver_get_protocol(subghz->subghz_receiver); | ||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||
|             return true; | ||||
|             break; | ||||
|         case SubghzReceverEventBack: | ||||
|             scene_manager_previous_scene(subghz->scene_manager); | ||||
|             // Stop CC1101 Rx
 | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||
|                 subghz_rx_end(subghz->txrx->worker); | ||||
|                 subghz_sleep(); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|             }; | ||||
|             subghz_history_clean(subghz->txrx->history); | ||||
|             subghz->txrx->hopper_state = SubGhzHopperStateOFF; | ||||
|             subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; | ||||
|             subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; | ||||
|             subghz->txrx->idx_menu_chosen = 0; | ||||
|             subghz_protocol_enable_dump(subghz->txrx->protocol, NULL, subghz); | ||||
|             scene_manager_search_and_switch_to_previous_scene( | ||||
|                 subghz->scene_manager, SubGhzSceneStart); | ||||
|             return true; | ||||
|             break; | ||||
|         case SubghzReceverEventSendStart: | ||||
|             subghz->state_notifications = NOTIFICATION_TX_STATE; | ||||
|             subghz->frequency = subghz_receiver_get_frequency(subghz->subghz_receiver); | ||||
|             subghz->preset = subghz_receiver_get_preset(subghz->subghz_receiver); | ||||
|             subghz->protocol_result = subghz_receiver_get_protocol(subghz->subghz_receiver); | ||||
|             subghz_transmitter_tx_start(subghz); | ||||
|             return true; | ||||
|             break; | ||||
|         case SubghzReceverEventSendStop: | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             subghz_transmitter_tx_stop(subghz); | ||||
|             return true; | ||||
|             break; | ||||
|         case SubghzReceverEventMain: | ||||
|             subghz->state_notifications = NOTIFICATION_RX_STATE; | ||||
|         case SubghzReceverEventOK: | ||||
|             subghz->txrx->idx_menu_chosen = subghz_receiver_get_idx_menu(subghz->subghz_receiver); | ||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); | ||||
|             return true; | ||||
|             break; | ||||
|         case SubghzReceverEventConfig: | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             return true; | ||||
|             break; | ||||
|         case SubghzReceverEventSendHistoryFull: | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); | ||||
|             return true; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|         if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { | ||||
|             subghz_hopper_update(subghz->txrx); | ||||
|             subghz_scene_receiver_update_statusbar(subghz); | ||||
|         } | ||||
| 
 | ||||
|         switch(subghz->state_notifications) { | ||||
|         case NOTIFICATION_TX_STATE: | ||||
|             notification_message(subghz->notifications, &sequence_blink_red_10); | ||||
|             break; | ||||
|         case NOTIFICATION_RX_STATE: | ||||
|             notification_message(subghz->notifications, &sequence_blink_blue_10); | ||||
|             break; | ||||
|  | ||||
							
								
								
									
										156
									
								
								applications/subghz/scenes/subghz_scene_receiver_config.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								applications/subghz/scenes/subghz_scene_receiver_config.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,156 @@ | ||||
| #include "../subghz_i.h" | ||||
| 
 | ||||
| #define PRESET_COUNT 3 | ||||
| const char* const preset_text[PRESET_COUNT] = { | ||||
|     "AM270", | ||||
|     "AM650", | ||||
|     "FM", | ||||
| }; | ||||
| const uint32_t preset_value[PRESET_COUNT] = { | ||||
|     FuriHalSubGhzPresetOok270Async, /** OOK, bandwidth 270kHz, asynchronous */ | ||||
|     FuriHalSubGhzPresetOok650Async, /** OOK, bandwidth 650kHz, asynchronous */ | ||||
|     FuriHalSubGhzPreset2FSKAsync, /** FM, asynchronous */ | ||||
| }; | ||||
| 
 | ||||
| #define HOPPING_COUNT 2 | ||||
| const char* const hopping_text[HOPPING_COUNT] = { | ||||
|     "OFF", | ||||
|     "ON", | ||||
| }; | ||||
| const uint32_t hopping_value[HOPPING_COUNT] = { | ||||
|     SubGhzHopperStateOFF, | ||||
|     SubGhzHopperStateRunnig, | ||||
| }; | ||||
| 
 | ||||
| uint8_t subghz_scene_receiver_config_uint32_value_index( | ||||
|     const uint32_t value, | ||||
|     const uint32_t values[], | ||||
|     uint8_t values_count) { | ||||
|     int64_t last_value = INT64_MIN; | ||||
|     uint8_t index = 0; | ||||
|     for(uint8_t i = 0; i < values_count; i++) { | ||||
|         if((value >= last_value) && (value <= values[i])) { | ||||
|             index = i; | ||||
|             break; | ||||
|         } | ||||
|         last_value = values[i]; | ||||
|     } | ||||
|     return index; | ||||
| } | ||||
| 
 | ||||
| uint8_t subghz_scene_receiver_config_hopper_value_index( | ||||
|     const uint32_t value, | ||||
|     const uint32_t values[], | ||||
|     uint8_t values_count, | ||||
|     void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     if(value == values[0]) { | ||||
|         return 0; | ||||
|     } else { | ||||
|         variable_item_set_current_value_text( | ||||
|             (VariableItem*)scene_manager_get_scene_state( | ||||
|                 subghz->scene_manager, SubGhzSceneReceiverConfig), | ||||
|             " -----"); | ||||
|         return 1; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void subghz_scene_receiver_config_set_frequency(VariableItem* item) { | ||||
|     SubGhz* subghz = variable_item_get_context(item); | ||||
|     uint8_t index = variable_item_get_current_value_index(item); | ||||
| 
 | ||||
|     if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) { | ||||
|         variable_item_set_current_value_text(item, subghz_frequencies_text[index]); | ||||
|         subghz->txrx->frequency = subghz_frequencies[index]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void subghz_scene_receiver_config_set_preset(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, preset_text[index]); | ||||
|     subghz->txrx->preset = preset_value[index]; | ||||
| } | ||||
| 
 | ||||
| static void subghz_scene_receiver_config_set_hopping_runing(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, hopping_text[index]); | ||||
|     if(hopping_value[index] == SubGhzHopperStateOFF) { | ||||
|         variable_item_set_current_value_text( | ||||
|             (VariableItem*)scene_manager_get_scene_state( | ||||
|                 subghz->scene_manager, SubGhzSceneReceiverConfig), | ||||
|             subghz_frequencies_text[subghz_frequencies_433_92]); | ||||
|         subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; | ||||
|     } else { | ||||
|         variable_item_set_current_value_text( | ||||
|             (VariableItem*)scene_manager_get_scene_state( | ||||
|                 subghz->scene_manager, SubGhzSceneReceiverConfig), | ||||
|             " -----"); | ||||
|     } | ||||
| 
 | ||||
|     subghz->txrx->hopper_state = hopping_value[index]; | ||||
| } | ||||
| 
 | ||||
| void subghz_scene_receiver_config_callback(SubghzReceverEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
|     view_dispatcher_send_custom_event(subghz->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_receiver_config_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     VariableItem* item; | ||||
|     uint8_t value_index; | ||||
| 
 | ||||
|     item = variable_item_list_add( | ||||
|         subghz->variable_item_list, | ||||
|         "Frequency:", | ||||
|         subghz_frequencies_count, | ||||
|         subghz_scene_receiver_config_set_frequency, | ||||
|         subghz); | ||||
|     value_index = subghz_scene_receiver_config_uint32_value_index( | ||||
|         subghz->txrx->frequency, subghz_frequencies, subghz_frequencies_count); | ||||
|     scene_manager_set_scene_state( | ||||
|         subghz->scene_manager, SubGhzSceneReceiverConfig, (uint32_t)item); | ||||
|     variable_item_set_current_value_index(item, value_index); | ||||
|     variable_item_set_current_value_text(item, subghz_frequencies_text[value_index]); | ||||
| 
 | ||||
|     item = variable_item_list_add( | ||||
|         subghz->variable_item_list, | ||||
|         "Hopping:", | ||||
|         HOPPING_COUNT, | ||||
|         subghz_scene_receiver_config_set_hopping_runing, | ||||
|         subghz); | ||||
|     value_index = subghz_scene_receiver_config_hopper_value_index( | ||||
|         subghz->txrx->hopper_state, hopping_value, HOPPING_COUNT, subghz); | ||||
|     variable_item_set_current_value_index(item, value_index); | ||||
|     variable_item_set_current_value_text(item, hopping_text[value_index]); | ||||
| 
 | ||||
|     item = variable_item_list_add( | ||||
|         subghz->variable_item_list, | ||||
|         "Modulation:", | ||||
|         PRESET_COUNT, | ||||
|         subghz_scene_receiver_config_set_preset, | ||||
|         subghz); | ||||
|     value_index = subghz_scene_receiver_config_uint32_value_index( | ||||
|         subghz->txrx->preset, preset_value, PRESET_COUNT); | ||||
|     variable_item_set_current_value_index(item, value_index); | ||||
|     variable_item_set_current_value_text(item, preset_text[value_index]); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewVariableItemList); | ||||
| } | ||||
| 
 | ||||
| const bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { | ||||
|     //SubGhz* subghz = context;
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_receiver_config_on_exit(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     variable_item_list_clean(subghz->variable_item_list); | ||||
| } | ||||
							
								
								
									
										163
									
								
								applications/subghz/scenes/subghz_scene_receiver_info.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								applications/subghz/scenes/subghz_scene_receiver_info.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,163 @@ | ||||
| #include "../subghz_i.h" | ||||
| 
 | ||||
| void subghz_scene_receiver_info_callback(GuiButtonType result, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
|     view_dispatcher_send_custom_event(subghz->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| static bool subghz_scene_receiver_info_update_parser(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     subghz->txrx->protocol_result = subghz_protocol_get_by_name( | ||||
|         subghz->txrx->protocol, | ||||
|         subghz_history_get_name(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); | ||||
| 
 | ||||
|     if(subghz->txrx->protocol_result->to_load_protocol != NULL) { | ||||
|         subghz->txrx->protocol_result->to_load_protocol( | ||||
|             subghz->txrx->protocol_result, | ||||
|             subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); | ||||
|         subghz->txrx->frequency = | ||||
|             subghz_history_get_frequency(subghz->txrx->history, subghz->txrx->idx_menu_chosen); | ||||
|         subghz->txrx->preset = | ||||
|             subghz_history_get_preset(subghz->txrx->history, subghz->txrx->idx_menu_chosen); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_receiver_info_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     if(subghz_scene_receiver_info_update_parser(subghz)) { | ||||
|         char buffer_str[16]; | ||||
|         snprintf( | ||||
|             buffer_str, | ||||
|             sizeof(buffer_str), | ||||
|             "%03ld.%02ld", | ||||
|             subghz->txrx->frequency / 1000000 % 1000, | ||||
|             subghz->txrx->frequency / 10000 % 100); | ||||
|         widget_add_string_element( | ||||
|             subghz->widget, 78, 0, AlignLeft, AlignTop, FontSecondary, buffer_str); | ||||
|         if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || | ||||
|            subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { | ||||
|             snprintf(buffer_str, sizeof(buffer_str), "AM"); | ||||
|         } else if(subghz->txrx->preset == FuriHalSubGhzPreset2FSKAsync) { | ||||
|             snprintf(buffer_str, sizeof(buffer_str), "FM"); | ||||
|         } else { | ||||
|             furi_check(0); | ||||
|         } | ||||
|         widget_add_string_element( | ||||
|             subghz->widget, 113, 0, AlignLeft, AlignTop, FontSecondary, buffer_str); | ||||
|         string_t text; | ||||
|         string_init(text); | ||||
|         subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, text); | ||||
|         widget_add_string_multi_element( | ||||
|             subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, string_get_cstr(text)); | ||||
|         string_clear(text); | ||||
| 
 | ||||
|         if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_string && | ||||
|            strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) { | ||||
|             widget_add_button_element( | ||||
|                 subghz->widget, | ||||
|                 GuiButtonTypeRight, | ||||
|                 "Save", | ||||
|                 subghz_scene_receiver_info_callback, | ||||
|                 subghz); | ||||
|             widget_add_button_element( | ||||
|                 subghz->widget, | ||||
|                 GuiButtonTypeCenter, | ||||
|                 "Send", | ||||
|                 subghz_scene_receiver_info_callback, | ||||
|                 subghz); | ||||
|         } | ||||
| 
 | ||||
|     } else { | ||||
|         widget_add_icon_element(subghz->widget, 32, 12, &I_DolphinFirstStart7_61x51); | ||||
|         widget_add_string_element( | ||||
|             subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse."); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewWidget); | ||||
| } | ||||
| 
 | ||||
| const bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { | ||||
|     SubGhz* subghz = context; | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == GuiButtonTypeCenterPress) { | ||||
|             //CC1101 Stop RX -> Start TX
 | ||||
|             subghz->state_notifications = NOTIFICATION_TX_STATE; | ||||
|             if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { | ||||
|                 subghz->txrx->hopper_state = SubGhzHopperStatePause; | ||||
|             } | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||
|                 subghz_rx_end(subghz->txrx->worker); | ||||
|                 //subghz_sleep();
 | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|             } | ||||
|             if(!subghz_scene_receiver_info_update_parser(subghz)) { | ||||
|                 return false; | ||||
|             } | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) { | ||||
|                 subghz_tx_start(subghz); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateTx; | ||||
|             } | ||||
|             return true; | ||||
|         } else if(event.event == GuiButtonTypeCenterRelease) { | ||||
|             //CC1101 Stop Tx -> Start RX
 | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|                 subghz_tx_stop(subghz); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|             } | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) { | ||||
|                 subghz_begin(subghz->txrx->preset); | ||||
|                 subghz_rx(subghz->txrx->worker, subghz->txrx->frequency); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateRx; | ||||
|             } | ||||
|             if(subghz->txrx->hopper_state == SubGhzHopperStatePause) { | ||||
|                 subghz->txrx->hopper_state = SubGhzHopperStateRunnig; | ||||
|             } | ||||
|             subghz->state_notifications = NOTIFICATION_RX_STATE; | ||||
|             return true; | ||||
|         } else if(event.event == GuiButtonTypeRight) { | ||||
|             //CC1101 Stop RX -> Save
 | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { | ||||
|                 subghz->txrx->hopper_state = SubGhzHopperStateOFF; | ||||
|             } | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||
|                 subghz_rx_end(subghz->txrx->worker); | ||||
|                 subghz_sleep(); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|             } | ||||
|             if(!subghz_scene_receiver_info_update_parser(subghz)) { | ||||
|                 return false; | ||||
|             } | ||||
|             if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->to_save_string && | ||||
|                strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) { | ||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|         if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { | ||||
|             subghz_hopper_update(subghz->txrx); | ||||
|         } | ||||
|         switch(subghz->state_notifications) { | ||||
|         case NOTIFICATION_TX_STATE: | ||||
|             notification_message(subghz->notifications, &sequence_blink_red_10); | ||||
|             break; | ||||
|         case NOTIFICATION_RX_STATE: | ||||
|             notification_message(subghz->notifications, &sequence_blink_blue_10); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_receiver_info_on_exit(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     widget_clear(subghz->widget); | ||||
| } | ||||
| @ -35,11 +35,13 @@ const bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent even | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SCENE_SAVE_NAME_CUSTOM_EVENT) { | ||||
|             if(subghz_save_protocol_to_file(subghz, subghz->text_store)) { | ||||
|             if(strcmp(subghz->text_store, "") && | ||||
|                subghz_save_protocol_to_file(subghz, subghz->text_store)) { | ||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); | ||||
|                 return true; | ||||
|             } else { | ||||
|                 //Error save
 | ||||
|                 string_set(subghz->error_str, "No name file"); | ||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -8,15 +8,18 @@ enum SubmenuIndex { | ||||
|     SubmenuIndexCAME12bit, | ||||
|     SubmenuIndexCAME24bit, | ||||
|     SubmenuIndexNeroSketch, | ||||
|     SubmenuIndexNeroRadio, | ||||
|     SubmenuIndexGateTX, | ||||
|     SubmenuIndexDoorHan, | ||||
| }; | ||||
| 
 | ||||
| bool subghz_scene_set_type_submenu_to_find_protocol(void* context, const char* protocol_name) { | ||||
|     SubGhz* subghz = context; | ||||
|     subghz->protocol_result = subghz_protocol_get_by_name(subghz->protocol, protocol_name); | ||||
|     if(subghz->protocol_result == NULL) { | ||||
|         //show error
 | ||||
|     subghz->txrx->protocol_result = | ||||
|         subghz_protocol_get_by_name(subghz->txrx->protocol, protocol_name); | ||||
|     if(subghz->txrx->protocol_result == NULL) { | ||||
|         string_set(subghz->error_str, "Protocol not found"); | ||||
|         scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| @ -62,6 +65,8 @@ const void subghz_scene_set_type_on_enter(void* context) { | ||||
|         subghz); | ||||
|     // submenu_add_item(
 | ||||
|     //     subghz->submenu, "Nero Sketch", SubmenuIndexNeroSketch, subghz_scene_set_type_submenu_callback, subghz);
 | ||||
|     // submenu_add_item(
 | ||||
|     //     subghz->submenu, "Nero Radio", SubmenuIndexNeroRadio, subghz_scene_set_type_submenu_callback, subghz);
 | ||||
|     submenu_add_item( | ||||
|         subghz->submenu, | ||||
|         "Gate TX_433", | ||||
| @ -90,81 +95,86 @@ const bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event | ||||
|         switch(event.event) { | ||||
|         case SubmenuIndexPricenton: | ||||
|             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Princeton")) { | ||||
|                 subghz->protocol_result->code_last_count_bit = 24; | ||||
|                 subghz->txrx->protocol_result->code_last_count_bit = 24; | ||||
|                 key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|                 subghz->protocol_result->code_last_found = key; | ||||
|                 subghz->txrx->protocol_result->code_last_found = key; | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexNiceFlo12bit: | ||||
|             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Nice FLO")) { | ||||
|                 subghz->protocol_result->code_last_count_bit = 12; | ||||
|                 subghz->txrx->protocol_result->code_last_count_bit = 12; | ||||
|                 key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
 | ||||
|                 subghz->protocol_result->code_last_found = key; | ||||
|                 subghz->txrx->protocol_result->code_last_found = key; | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexNiceFlo24bit: | ||||
|             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "Nice FLO")) { | ||||
|                 subghz->protocol_result->code_last_count_bit = 24; | ||||
|                 subghz->txrx->protocol_result->code_last_count_bit = 24; | ||||
|                 key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|                 subghz->protocol_result->code_last_found = key; | ||||
|                 subghz->txrx->protocol_result->code_last_found = key; | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexCAME12bit: | ||||
|             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "CAME")) { | ||||
|                 subghz->protocol_result->code_last_count_bit = 12; | ||||
|                 subghz->txrx->protocol_result->code_last_count_bit = 12; | ||||
|                 key = (key & 0x0000FFF0) | 0x1; //btn 0x1, 0x2, 0x4
 | ||||
|                 subghz->protocol_result->code_last_found = key; | ||||
|                 subghz->txrx->protocol_result->code_last_found = key; | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexCAME24bit: | ||||
|             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "CAME")) { | ||||
|                 subghz->protocol_result->code_last_count_bit = 24; | ||||
|                 subghz->txrx->protocol_result->code_last_count_bit = 24; | ||||
|                 key = (key & 0x00FFFFF0) | 0x4; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|                 subghz->protocol_result->code_last_found = key; | ||||
|                 subghz->txrx->protocol_result->code_last_found = key; | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         // case SubmenuIndexNeroSketch:
 | ||||
|         //     /* code */
 | ||||
|         //     break;
 | ||||
|         // case SubmenuIndexNeroRadio:
 | ||||
|         //     /* code */
 | ||||
|         //     break;
 | ||||
|         case SubmenuIndexGateTX: | ||||
|             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "GateTX")) { | ||||
|                 subghz->protocol_result->code_last_count_bit = 24; | ||||
|                 subghz->txrx->protocol_result->code_last_count_bit = 24; | ||||
|                 key = (key & 0x00F0FFFF) | 0xF << 16; //btn 0xF, 0xC, 0xA, 0x6
 | ||||
|                 subghz->protocol_result->code_last_found = subghz_protocol_common_reverse_key( | ||||
|                     key, subghz->protocol_result->code_last_count_bit); | ||||
|                 subghz->txrx->protocol_result->code_last_found = | ||||
|                     subghz_protocol_common_reverse_key( | ||||
|                         key, subghz->txrx->protocol_result->code_last_count_bit); | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
|         case SubmenuIndexDoorHan: | ||||
|             if(subghz_scene_set_type_submenu_to_find_protocol(subghz, "KeeLoq")) { | ||||
|                 subghz->protocol_result->code_last_count_bit = 64; | ||||
|                 subghz->protocol_result->serial = key & 0x0FFFFFFF; | ||||
|                 subghz->protocol_result->btn = 0x2; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|                 subghz->protocol_result->cnt = 0x0003; | ||||
|                 subghz->txrx->protocol_result->code_last_count_bit = 64; | ||||
|                 subghz->txrx->protocol_result->serial = key & 0x0FFFFFFF; | ||||
|                 subghz->txrx->protocol_result->btn = 0x2; //btn 0x1, 0x2, 0x4, 0x8
 | ||||
|                 subghz->txrx->protocol_result->cnt = 0x0003; | ||||
|                 if(subghz_protocol_keeloq_set_manufacture_name( | ||||
|                        subghz->protocol_result, "DoorHan")) { | ||||
|                     subghz->protocol_result->code_last_found = | ||||
|                         subghz_protocol_keeloq_gen_key(subghz->protocol_result); | ||||
|                        subghz->txrx->protocol_result, "DoorHan")) { | ||||
|                     subghz->txrx->protocol_result->code_last_found = | ||||
|                         subghz_protocol_keeloq_gen_key(subghz->txrx->protocol_result); | ||||
|                     generated_protocol = true; | ||||
|                 } else { | ||||
|                     generated_protocol = false; | ||||
|                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneNoMan); | ||||
|                     string_set(subghz->error_str, "No manufactory key"); | ||||
|                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             return false; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(generated_protocol) { | ||||
|             subghz->frequency = subghz_frequencies[subghz_frequencies_433_92]; | ||||
|             subghz->preset = FuriHalSubGhzPresetOok650Async; | ||||
|             subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; | ||||
|             subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; | ||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| @ -2,26 +2,26 @@ | ||||
| 
 | ||||
| #define SCENE_NO_MAN_CUSTOM_EVENT (11UL) | ||||
| 
 | ||||
| void subghz_scene_no_man_popup_callback(void* context) { | ||||
| void subghz_scene_show_error_popup_callback(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     view_dispatcher_send_custom_event(subghz->view_dispatcher, SCENE_NO_MAN_CUSTOM_EVENT); | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_no_man_on_enter(void* context) { | ||||
| const void subghz_scene_show_error_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = subghz->popup; | ||||
|     popup_set_icon(popup, 32, 12, &I_DolphinFirstStart7_61x51); | ||||
|     popup_set_header(popup, "No manufactory key", 13, 8, AlignLeft, AlignBottom); | ||||
|     popup_set_header(popup, string_get_cstr(subghz->error_str), 64, 8, AlignCenter, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, subghz); | ||||
|     popup_set_callback(popup, subghz_scene_no_man_popup_callback); | ||||
|     popup_set_callback(popup, subghz_scene_show_error_popup_callback); | ||||
|     popup_enable_timeout(popup); | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewPopup); | ||||
| } | ||||
| 
 | ||||
| const bool subghz_scene_no_man_on_event(void* context, SceneManagerEvent event) { | ||||
| const bool subghz_scene_show_error_on_event(void* context, SceneManagerEvent event) { | ||||
|     SubGhz* subghz = context; | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SCENE_NO_MAN_CUSTOM_EVENT) { | ||||
| @ -33,7 +33,7 @@ const bool subghz_scene_no_man_on_event(void* context, SceneManagerEvent event) | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_no_man_on_exit(void* context) { | ||||
| const void subghz_scene_show_error_on_exit(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     // Clear view
 | ||||
| @ -45,4 +45,5 @@ const void subghz_scene_no_man_on_exit(void* context) { | ||||
|     popup_set_context(popup, NULL); | ||||
|     popup_set_timeout(popup, 0); | ||||
|     popup_disable_timeout(popup); | ||||
|     string_clean(subghz->error_str); | ||||
| } | ||||
| @ -1,5 +1,6 @@ | ||||
| #include "../subghz_i.h" | ||||
| #include "../views/subghz_transmitter.h" | ||||
| #include <lib/subghz/protocols/subghz_protocol_keeloq.h> | ||||
| 
 | ||||
| void subghz_scene_transmitter_callback(SubghzTransmitterEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
| @ -7,42 +8,90 @@ void subghz_scene_transmitter_callback(SubghzTransmitterEvent event, void* conte | ||||
|     view_dispatcher_send_custom_event(subghz->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| static void subghz_scene_transmitter_update_data_show(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     if(subghz->txrx->protocol_result && subghz->txrx->protocol_result->get_upload_protocol) { | ||||
|         string_t key_str; | ||||
|         string_init(key_str); | ||||
|         char frequency_str[10]; | ||||
|         char preset_str[6]; | ||||
|         uint8_t show_button = 0; | ||||
|         subghz->txrx->protocol_result->to_string(subghz->txrx->protocol_result, key_str); | ||||
| 
 | ||||
|         if((!strcmp(subghz->txrx->protocol_result->name, "KeeLoq")) && | ||||
|            (!strcmp( | ||||
|                subghz_protocol_keeloq_get_manufacture_name(subghz->txrx->protocol_result), | ||||
|                "Unknown"))) { | ||||
|             show_button = 0; | ||||
|         } else { | ||||
|             show_button = 1; | ||||
|         } | ||||
|         snprintf( | ||||
|             frequency_str, | ||||
|             sizeof(frequency_str), | ||||
|             "%03ld.%02ld", | ||||
|             subghz->txrx->frequency / 1000000 % 1000, | ||||
|             subghz->txrx->frequency / 10000 % 100); | ||||
|         if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || | ||||
|            subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { | ||||
|             snprintf(preset_str, sizeof(preset_str), "AM"); | ||||
|         } else if(subghz->txrx->preset == FuriHalSubGhzPreset2FSKAsync) { | ||||
|             snprintf(preset_str, sizeof(preset_str), "FM"); | ||||
|         } else { | ||||
|             furi_check(0); | ||||
|         } | ||||
| 
 | ||||
|         subghz_transmitter_add_data_to_show( | ||||
|             subghz->subghz_transmitter, | ||||
|             string_get_cstr(key_str), | ||||
|             frequency_str, | ||||
|             preset_str, | ||||
|             show_button); | ||||
|         string_clear(key_str); | ||||
|     } else { | ||||
|         string_set(subghz->error_str, "Protocol not found"); | ||||
|         scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const void subghz_scene_transmitter_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     SubghzTransmitter* subghz_transmitter = subghz->subghz_transmitter; | ||||
| 
 | ||||
|     subghz_transmitter_set_callback(subghz_transmitter, subghz_scene_transmitter_callback, subghz); | ||||
|     subghz_transmitter_set_protocol(subghz_transmitter, subghz->protocol_result); | ||||
|     subghz_transmitter_set_frequency_preset(subghz_transmitter, subghz->frequency, subghz->preset); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter); | ||||
| 
 | ||||
|     subghz_transmitter_set_callback( | ||||
|         subghz->subghz_transmitter, subghz_scene_transmitter_callback, subghz); | ||||
|     subghz_scene_transmitter_update_data_show(subghz); | ||||
|     subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewTransmitter); | ||||
| } | ||||
| 
 | ||||
| const bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubghzTransmitterEventSendStart) { | ||||
|             subghz->state_notifications = NOTIFICATION_TX_STATE; | ||||
|             subghz_transmitter_tx_start(subghz); | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||
|                 subghz_rx_end(subghz->txrx->worker); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|             } | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateIdle) { | ||||
|                 subghz_tx_start(subghz); | ||||
|                 subghz_scene_transmitter_update_data_show(subghz); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateTx; | ||||
|             } | ||||
|             return true; | ||||
|         } else if(event.event == SubghzTransmitterEventSendStop) { | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             subghz_transmitter_tx_stop(subghz); | ||||
|             subghz_sleep(); | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|                 subghz_tx_stop(subghz); | ||||
|                 subghz_sleep(); | ||||
|                 subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|             } | ||||
|             return true; | ||||
|         } else if(event.event == SubghzTransmitterEventBack) { | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             scene_manager_search_and_switch_to_previous_scene( | ||||
|                 subghz->scene_manager, SubGhzSceneStart); | ||||
|             return true; | ||||
|         } else if(event.event == SubghzTransmitterEventNoMan) { | ||||
|             subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
|             scene_manager_search_and_switch_to_previous_scene( | ||||
|                 subghz->scene_manager, SubGhzSceneNoMan); | ||||
|             return true; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|         if(subghz->state_notifications == NOTIFICATION_TX_STATE) { | ||||
| @ -55,7 +104,6 @@ const bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent ev | ||||
| 
 | ||||
| const void subghz_scene_transmitter_on_exit(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     SubghzTransmitter* subghz_transmitter = subghz->subghz_transmitter; | ||||
|     subghz_transmitter_set_callback(subghz_transmitter, NULL, subghz); | ||||
| 
 | ||||
|     subghz->state_notifications = NOTIFICATION_IDLE_STATE; | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,22 @@ | ||||
| #include "subghz_i.h" | ||||
| 
 | ||||
| const char* const subghz_frequencies_text[] = { | ||||
|     "300.00", | ||||
|     "315.00", | ||||
|     "348.00", | ||||
|     "387.00", | ||||
|     "433.08", | ||||
|     "433.92", | ||||
|     "434.78", | ||||
|     "438.90", | ||||
|     "464.00", | ||||
|     "779.00", | ||||
|     "868.35", | ||||
|     "915.00", | ||||
|     "925.00", | ||||
|     "928.00", | ||||
| }; | ||||
| 
 | ||||
| const uint32_t subghz_frequencies[] = { | ||||
|     /* 300 - 348 */ | ||||
|     300000000, | ||||
| @ -20,7 +37,15 @@ const uint32_t subghz_frequencies[] = { | ||||
|     928000000, | ||||
| }; | ||||
| 
 | ||||
| const uint32_t subghz_hopper_frequencies[] = { | ||||
|     315000000, | ||||
|     433920000, | ||||
|     868350000, | ||||
| }; | ||||
| 
 | ||||
| const uint32_t subghz_frequencies_count = sizeof(subghz_frequencies) / sizeof(uint32_t); | ||||
| const uint32_t subghz_hopper_frequencies_count = | ||||
|     sizeof(subghz_hopper_frequencies) / sizeof(uint32_t); | ||||
| const uint32_t subghz_frequencies_433_92 = 5; | ||||
| 
 | ||||
| bool subghz_custom_event_callback(void* context, uint32_t event) { | ||||
| @ -77,11 +102,6 @@ SubGhz* subghz_alloc() { | ||||
|         SubGhzViewReceiver, | ||||
|         subghz_receiver_get_view(subghz->subghz_receiver)); | ||||
| 
 | ||||
|     // Dialog
 | ||||
|     subghz->dialog_ex = dialog_ex_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         subghz->view_dispatcher, SubGhzViewDialogEx, dialog_ex_get_view(subghz->dialog_ex)); | ||||
| 
 | ||||
|     // Popup
 | ||||
|     subghz->popup = popup_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
| @ -92,6 +112,11 @@ SubGhz* subghz_alloc() { | ||||
|     view_dispatcher_add_view( | ||||
|         subghz->view_dispatcher, SubGhzViewTextInput, text_input_get_view(subghz->text_input)); | ||||
| 
 | ||||
|     // Custom Widget
 | ||||
|     subghz->widget = widget_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         subghz->view_dispatcher, SubGhzViewWidget, widget_get_view(subghz->widget)); | ||||
| 
 | ||||
|     // Transmitter
 | ||||
|     subghz->subghz_transmitter = subghz_transmitter_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
| @ -99,6 +124,13 @@ SubGhz* subghz_alloc() { | ||||
|         SubGhzViewTransmitter, | ||||
|         subghz_transmitter_get_view(subghz->subghz_transmitter)); | ||||
| 
 | ||||
|     // Variable Item List
 | ||||
|     subghz->variable_item_list = variable_item_list_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         subghz->view_dispatcher, | ||||
|         SubGhzViewVariableItemList, | ||||
|         variable_item_list_get_view(subghz->variable_item_list)); | ||||
| 
 | ||||
|     // Carrier Test Module
 | ||||
|     subghz->subghz_test_carrier = subghz_test_carrier_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
| @ -120,17 +152,26 @@ SubGhz* subghz_alloc() { | ||||
|         SubGhzViewStatic, | ||||
|         subghz_test_static_get_view(subghz->subghz_test_static)); | ||||
| 
 | ||||
|     //init Worker & Protocol
 | ||||
|     subghz->worker = subghz_worker_alloc(); | ||||
|     subghz->protocol = subghz_protocol_alloc(); | ||||
|     //init Worker & Protocol & History
 | ||||
|     subghz->txrx = furi_alloc(sizeof(SubGhzTxRx)); | ||||
|     subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92]; | ||||
|     subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; | ||||
|     subghz->txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|     subghz->txrx->hopper_state = SubGhzHopperStateOFF; | ||||
|     subghz->txrx->history = subghz_history_alloc(); | ||||
|     subghz->txrx->worker = subghz_worker_alloc(); | ||||
|     subghz->txrx->protocol = subghz_protocol_alloc(); | ||||
|     subghz_worker_set_overrun_callback( | ||||
|         subghz->worker, (SubGhzWorkerOverrunCallback)subghz_protocol_reset); | ||||
|         subghz->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_protocol_reset); | ||||
|     subghz_worker_set_pair_callback( | ||||
|         subghz->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse); | ||||
|     subghz_worker_set_context(subghz->worker, subghz->protocol); | ||||
|         subghz->txrx->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse); | ||||
|     subghz_worker_set_context(subghz->txrx->worker, subghz->txrx->protocol); | ||||
| 
 | ||||
|     subghz_protocol_load_keeloq_file(subghz->protocol, "/ext/subghz/keeloq_mfcodes"); | ||||
|     subghz_protocol_load_nice_flor_s_file(subghz->protocol, "/ext/subghz/nice_floor_s_rx"); | ||||
|     //Init Error_str
 | ||||
|     string_init(subghz->error_str); | ||||
| 
 | ||||
|     subghz_protocol_load_keeloq_file(subghz->txrx->protocol, "/ext/subghz/keeloq_mfcodes"); | ||||
|     subghz_protocol_load_nice_flor_s_file(subghz->txrx->protocol, "/ext/subghz/nice_floor_s_rx"); | ||||
| 
 | ||||
|     //subghz_protocol_enable_dump_text(subghz->protocol, subghz_text_callback, subghz);
 | ||||
| 
 | ||||
| @ -160,18 +201,22 @@ void subghz_free(SubGhz* subghz) { | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTextInput); | ||||
|     text_input_free(subghz->text_input); | ||||
| 
 | ||||
|     // Receiver
 | ||||
|     // Custom Widget
 | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewWidget); | ||||
|     widget_free(subghz->widget); | ||||
| 
 | ||||
|     // Transmitter
 | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewTransmitter); | ||||
|     subghz_transmitter_free(subghz->subghz_transmitter); | ||||
| 
 | ||||
|     // Variable Item List
 | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewVariableItemList); | ||||
|     variable_item_list_free(subghz->variable_item_list); | ||||
| 
 | ||||
|     // Submenu
 | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewMenu); | ||||
|     submenu_free(subghz->submenu); | ||||
| 
 | ||||
|     // DialogEx
 | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewDialogEx); | ||||
|     dialog_ex_free(subghz->dialog_ex); | ||||
| 
 | ||||
|     // Popup
 | ||||
|     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewPopup); | ||||
|     popup_free(subghz->popup); | ||||
| @ -186,9 +231,14 @@ void subghz_free(SubGhz* subghz) { | ||||
|     furi_record_close("gui"); | ||||
|     subghz->gui = NULL; | ||||
| 
 | ||||
|     //Worker & Protocol
 | ||||
|     subghz_protocol_free(subghz->protocol); | ||||
|     subghz_worker_free(subghz->worker); | ||||
|     //Worker & Protocol & History
 | ||||
|     subghz_protocol_free(subghz->txrx->protocol); | ||||
|     subghz_worker_free(subghz->txrx->worker); | ||||
|     subghz_history_free(subghz->txrx->history); | ||||
|     free(subghz->txrx); | ||||
| 
 | ||||
|     //Error string
 | ||||
|     string_clear(subghz->error_str); | ||||
| 
 | ||||
|     // Notifications
 | ||||
|     furi_record_close("notification"); | ||||
|  | ||||
| @ -128,21 +128,27 @@ void subghz_history_get_text_item_menu(SubGhzHistory* instance, string_t output, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void subghz_history_add_to_history(SubGhzHistory* instance, void* context) { | ||||
| bool subghz_history_add_to_history( | ||||
|     SubGhzHistory* instance, | ||||
|     void* context, | ||||
|     uint32_t frequency, | ||||
|     FuriHalSubGhzPreset preset) { | ||||
|     furi_assert(instance); | ||||
|     furi_assert(context); | ||||
|     SubGhzProtocolCommon* protocol = context; | ||||
| 
 | ||||
|     if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return; | ||||
|     if(instance->last_index_write >= SUBGHZ_HISTORY_MAX) return false; | ||||
|     if((instance->code_last_found == (protocol->code_last_found & 0xFFFF0FFFFFFFFFFF)) && | ||||
|        ((millis() - instance->last_update_timestamp) < 500)) { | ||||
|         instance->last_update_timestamp = millis(); | ||||
|         return; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     instance->code_last_found = protocol->code_last_found & 0xFFFF0FFFFFFFFFFF; | ||||
|     instance->last_update_timestamp = millis(); | ||||
| 
 | ||||
|     instance->history[instance->last_index_write].real_frequency = frequency; | ||||
|     instance->history[instance->last_index_write].preset = preset; | ||||
|     instance->history[instance->last_index_write].te = 0; | ||||
|     instance->history[instance->last_index_write].manufacture_name = NULL; | ||||
|     instance->history[instance->last_index_write].name = protocol->name; | ||||
| @ -161,4 +167,5 @@ void subghz_history_add_to_history(SubGhzHistory* instance, void* context) { | ||||
|     instance->history[instance->last_index_write].type_protocol = protocol->type_protocol; | ||||
| 
 | ||||
|     instance->last_index_write++; | ||||
|     return true; | ||||
| } | ||||
| @ -1,3 +1,4 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <lib/subghz/protocols/subghz_protocol_common.h> | ||||
| @ -94,8 +95,15 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, string_t output | ||||
|  *  | ||||
|  * @param instance  - SubGhzHistory instance | ||||
|  * @param context    - SubGhzProtocolCommon context | ||||
|  * @param frequency - frequency Hz | ||||
|  * @param preset    - FuriHalSubGhzPreset preset | ||||
|  * @return bool; | ||||
|  */ | ||||
| void subghz_history_add_to_history(SubGhzHistory* instance, void* context); | ||||
| bool subghz_history_add_to_history( | ||||
|     SubGhzHistory* instance, | ||||
|     void* context, | ||||
|     uint32_t frequency, | ||||
|     FuriHalSubGhzPreset preset); | ||||
| 
 | ||||
| /** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data
 | ||||
|  *  | ||||
| @ -104,3 +112,5 @@ void subghz_history_add_to_history(SubGhzHistory* instance, void* context); | ||||
|  * @return SubGhzProtocolCommonLoad* | ||||
|  */ | ||||
| SubGhzProtocolCommonLoad* subghz_history_get_raw_data(SubGhzHistory* instance, uint16_t idx); | ||||
| 
 | ||||
| void subghz_hopper_update(void* context); | ||||
|  | ||||
| @ -19,6 +19,9 @@ void subghz_begin(FuriHalSubGhzPreset preset) { | ||||
| 
 | ||||
| uint32_t subghz_rx(void* context, uint32_t frequency) { | ||||
|     furi_assert(context); | ||||
|     if(!furi_hal_subghz_is_frequency_valid(frequency)) { | ||||
|         furi_check(0); | ||||
|     } | ||||
|     SubGhzWorker* worker = context; | ||||
| 
 | ||||
|     furi_hal_subghz_idle(); | ||||
| @ -33,6 +36,9 @@ uint32_t subghz_rx(void* context, uint32_t frequency) { | ||||
| } | ||||
| 
 | ||||
| uint32_t subghz_tx(uint32_t frequency) { | ||||
|     if(!furi_hal_subghz_is_frequency_valid(frequency)) { | ||||
|         furi_check(0); | ||||
|     } | ||||
|     furi_hal_subghz_idle(); | ||||
|     uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); | ||||
|     hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); | ||||
| @ -53,6 +59,7 @@ void subghz_rx_end(void* context) { | ||||
|         subghz_worker_stop(worker); | ||||
|         furi_hal_subghz_stop_async_rx(); | ||||
|     } | ||||
|     furi_hal_subghz_idle(); | ||||
| } | ||||
| 
 | ||||
| void subghz_sleep(void) { | ||||
| @ -66,41 +73,46 @@ void subghz_frequency_preset_to_str(void* context, string_t output) { | ||||
|         output, | ||||
|         "Frequency: %d\n" | ||||
|         "Preset: %d\n", | ||||
|         (int)subghz->frequency, | ||||
|         (int)subghz->preset); | ||||
|         (int)subghz->txrx->frequency, | ||||
|         (int)subghz->txrx->preset); | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_tx_start(void* context) { | ||||
| void subghz_tx_start(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
|     subghz->encoder = subghz_protocol_encoder_common_alloc(); | ||||
|     subghz->encoder->repeat = 200; //max repeat with the button held down
 | ||||
|     subghz->txrx->encoder = subghz_protocol_encoder_common_alloc(); | ||||
|     subghz->txrx->encoder->repeat = 200; //max repeat with the button held down
 | ||||
|     //get upload
 | ||||
|     if(subghz->protocol_result->get_upload_protocol) { | ||||
|         if(subghz->protocol_result->get_upload_protocol(subghz->protocol_result, subghz->encoder)) { | ||||
|             if(subghz->preset) { | ||||
|                 subghz_begin(subghz->preset); | ||||
|     if(subghz->txrx->protocol_result->get_upload_protocol) { | ||||
|         if(subghz->txrx->protocol_result->get_upload_protocol( | ||||
|                subghz->txrx->protocol_result, subghz->txrx->encoder)) { | ||||
|             if(subghz->txrx->preset) { | ||||
|                 subghz_begin(subghz->txrx->preset); | ||||
|             } else { | ||||
|                 subghz_begin(FuriHalSubGhzPresetOok650Async); | ||||
|                 subghz_begin(FuriHalSubGhzPresetOok270Async); | ||||
|             } | ||||
|             if(subghz->frequency) { | ||||
|                 subghz_tx(subghz->frequency); | ||||
|             if(subghz->txrx->frequency) { | ||||
|                 subghz_tx(subghz->txrx->frequency); | ||||
|             } else { | ||||
|                 subghz_tx(433920000); | ||||
|             } | ||||
| 
 | ||||
|             //Start TX
 | ||||
|             furi_hal_subghz_start_async_tx(subghz_protocol_encoder_common_yield, subghz->encoder); | ||||
|             furi_hal_subghz_start_async_tx( | ||||
|                 subghz_protocol_encoder_common_yield, subghz->txrx->encoder); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_tx_stop(void* context) { | ||||
| void subghz_tx_stop(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
|     //Stop TX
 | ||||
|     furi_hal_subghz_stop_async_tx(); | ||||
|     subghz_protocol_encoder_common_free(subghz->encoder); | ||||
|     subghz_protocol_encoder_common_free(subghz->txrx->encoder); | ||||
|     furi_hal_subghz_idle(); | ||||
|     //if protocol dynamic then we save the last upload
 | ||||
|     if(subghz->protocol_result->type_protocol == TYPE_PROTOCOL_DYNAMIC) { | ||||
|     if(subghz->txrx->protocol_result->type_protocol == TYPE_PROTOCOL_DYNAMIC) { | ||||
|         subghz_save_protocol_to_file(subghz, subghz->text_store); | ||||
|     } | ||||
|     notification_message(subghz->notifications, &sequence_reset_red); | ||||
| @ -133,7 +145,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { | ||||
|         if(res != 1) { | ||||
|             break; | ||||
|         } | ||||
|         subghz->frequency = (uint32_t)data; | ||||
|         subghz->txrx->frequency = (uint32_t)data; | ||||
| 
 | ||||
|         // Read and parse preset from 2st line
 | ||||
|         if(!file_worker_read_until(file_worker, temp_str, '\n')) { | ||||
| @ -143,7 +155,7 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { | ||||
|         if(res != 1) { | ||||
|             break; | ||||
|         } | ||||
|         subghz->preset = (FuriHalSubGhzPreset)data; | ||||
|         subghz->txrx->preset = (FuriHalSubGhzPreset)data; | ||||
| 
 | ||||
|         // Read and parse name protocol from 2st line
 | ||||
|         if(!file_worker_read_until(file_worker, temp_str, '\n')) { | ||||
| @ -151,13 +163,13 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { | ||||
|         } | ||||
|         // strlen("Protocol: ") = 10
 | ||||
|         string_right(temp_str, 10); | ||||
|         subghz->protocol_result = | ||||
|             subghz_protocol_get_by_name(subghz->protocol, string_get_cstr(temp_str)); | ||||
|         if(subghz->protocol_result == NULL) { | ||||
|         subghz->txrx->protocol_result = | ||||
|             subghz_protocol_get_by_name(subghz->txrx->protocol, string_get_cstr(temp_str)); | ||||
|         if(subghz->txrx->protocol_result == NULL) { | ||||
|             break; | ||||
|         } | ||||
|         if(!subghz->protocol_result->to_load_protocol_from_file( | ||||
|                file_worker, subghz->protocol_result)) { | ||||
|         if(!subghz->txrx->protocol_result->to_load_protocol_from_file( | ||||
|                file_worker, subghz->txrx->protocol_result)) { | ||||
|             break; | ||||
|         } | ||||
|         loaded = true; | ||||
| @ -175,8 +187,9 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { | ||||
| } | ||||
| 
 | ||||
| bool subghz_save_protocol_to_file(void* context, const char* dev_name) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
|     furi_assert(subghz->protocol_result); | ||||
|     furi_assert(subghz->txrx->protocol_result); | ||||
|     FileWorker* file_worker = file_worker_alloc(false); | ||||
|     string_t dev_file_name; | ||||
|     string_init(dev_file_name); | ||||
| @ -210,7 +223,7 @@ bool subghz_save_protocol_to_file(void* context, const char* dev_name) { | ||||
|             break; | ||||
|         } | ||||
|         //Get string save
 | ||||
|         subghz->protocol_result->to_save_string(subghz->protocol_result, temp_str); | ||||
|         subghz->txrx->protocol_result->to_save_string(subghz->txrx->protocol_result, temp_str); | ||||
|         // Prepare and write data to file
 | ||||
|         if(!file_worker_write(file_worker, string_get_cstr(temp_str), string_size(temp_str))) { | ||||
|             break; | ||||
| @ -276,7 +289,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { | ||||
|         if(sscanf_res != 1) { | ||||
|             break; | ||||
|         } | ||||
|         subghz->frequency = (uint32_t)data; | ||||
|         subghz->txrx->frequency = (uint32_t)data; | ||||
| 
 | ||||
|         // Read and parse preset from 2st line
 | ||||
|         if(!file_worker_read_until(file_worker, temp_str, '\n')) { | ||||
| @ -286,7 +299,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { | ||||
|         if(sscanf_res != 1) { | ||||
|             break; | ||||
|         } | ||||
|         subghz->preset = (FuriHalSubGhzPreset)data; | ||||
|         subghz->txrx->preset = (FuriHalSubGhzPreset)data; | ||||
| 
 | ||||
|         // Read and parse name protocol from 3st line
 | ||||
|         if(!file_worker_read_until(file_worker, temp_str, '\n')) { | ||||
| @ -294,13 +307,13 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { | ||||
|         } | ||||
|         // strlen("Protocol: ") = 10
 | ||||
|         string_right(temp_str, 10); | ||||
|         subghz->protocol_result = | ||||
|             subghz_protocol_get_by_name(subghz->protocol, string_get_cstr(temp_str)); | ||||
|         if(subghz->protocol_result == NULL) { | ||||
|         subghz->txrx->protocol_result = | ||||
|             subghz_protocol_get_by_name(subghz->txrx->protocol, string_get_cstr(temp_str)); | ||||
|         if(subghz->txrx->protocol_result == NULL) { | ||||
|             break; | ||||
|         } | ||||
|         if(!subghz->protocol_result->to_load_protocol_from_file( | ||||
|                file_worker, subghz->protocol_result)) { | ||||
|         if(!subghz->txrx->protocol_result->to_load_protocol_from_file( | ||||
|                file_worker, subghz->txrx->protocol_result)) { | ||||
|             break; | ||||
|         } | ||||
|         res = true; | ||||
| @ -328,3 +341,57 @@ uint32_t subghz_random_serial(void) { | ||||
|     } | ||||
|     return (uint32_t)rand(); | ||||
| } | ||||
| 
 | ||||
| void subghz_hopper_update(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhzTxRx* txrx = context; | ||||
| 
 | ||||
|     switch(txrx->hopper_state) { | ||||
|     case SubGhzHopperStateOFF: | ||||
|         return; | ||||
|         break; | ||||
|     case SubGhzHopperStatePause: | ||||
|         return; | ||||
|         break; | ||||
|     case SubGhzHopperStateRSSITimeOut: | ||||
|         if(txrx->hopper_timeout != 0) { | ||||
|             txrx->hopper_timeout--; | ||||
|             return; | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
|     float rssi = -127.0f; | ||||
|     if(txrx->hopper_state != SubGhzHopperStateRSSITimeOut) { | ||||
|         // See RSSI Calculation timings in CC1101 17.3 RSSI
 | ||||
|         rssi = furi_hal_subghz_get_rssi(); | ||||
| 
 | ||||
|         // Stay if RSSI is high enough
 | ||||
|         if(rssi > -90.0f) { | ||||
|             txrx->hopper_timeout = 10; | ||||
|             txrx->hopper_state = SubGhzHopperStateRSSITimeOut; | ||||
|             return; | ||||
|         } | ||||
|     } else { | ||||
|         txrx->hopper_state = SubGhzHopperStateRunnig; | ||||
|     } | ||||
| 
 | ||||
|     // Select next frequency
 | ||||
|     if(txrx->hopper_idx_frequency < subghz_hopper_frequencies_count - 1) { | ||||
|         txrx->hopper_idx_frequency++; | ||||
|     } else { | ||||
|         txrx->hopper_idx_frequency = 0; | ||||
|     } | ||||
| 
 | ||||
|     if(txrx->txrx_state == SubGhzTxRxStateRx) { | ||||
|         subghz_rx_end(txrx->worker); | ||||
|         txrx->txrx_state = SubGhzTxRxStateIdle; | ||||
|     }; | ||||
|     if(txrx->txrx_state == SubGhzTxRxStateIdle) { | ||||
|         subghz_protocol_reset(txrx->protocol); | ||||
|         txrx->frequency = subghz_hopper_frequencies[txrx->hopper_idx_frequency]; | ||||
|         subghz_rx(txrx->worker, txrx->frequency); | ||||
|         txrx->txrx_state = SubGhzTxRxStateRx; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -15,9 +15,9 @@ | ||||
| #include <notification/notification-messages.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/modules/submenu.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <gui/modules/popup.h> | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <gui/modules/widget.h> | ||||
| 
 | ||||
| #include <subghz/scenes/subghz_scene.h> | ||||
| 
 | ||||
| @ -26,6 +26,8 @@ | ||||
| #include <lib/subghz/protocols/subghz_protocol_common.h> | ||||
| #include "subghz_history.h" | ||||
| 
 | ||||
| #include <gui/modules/variable-item-list.h> | ||||
| 
 | ||||
| #define SUBGHZ_TEXT_STORE_SIZE 128 | ||||
| 
 | ||||
| #define NOTIFICATION_STARTING_STATE 0u | ||||
| @ -33,48 +35,81 @@ | ||||
| #define NOTIFICATION_TX_STATE 2u | ||||
| #define NOTIFICATION_RX_STATE 3u | ||||
| 
 | ||||
| extern const char* const subghz_frequencies_text[]; | ||||
| extern const uint32_t subghz_frequencies[]; | ||||
| extern const uint32_t subghz_hopper_frequencies[]; | ||||
| extern const uint32_t subghz_frequencies_count; | ||||
| extern const uint32_t subghz_hopper_frequencies_count; | ||||
| extern const uint32_t subghz_frequencies_433_92; | ||||
| 
 | ||||
| struct SubGhz { | ||||
|     Gui* gui; | ||||
|     NotificationApp* notifications; | ||||
| /** SubGhzTxRx state */ | ||||
| typedef enum { | ||||
|     SubGhzTxRxStateIdle, | ||||
|     SubGhzTxRxStateRx, | ||||
|     SubGhzTxRxStateTx, | ||||
| } SubGhzTxRxState; | ||||
| 
 | ||||
| /** SubGhzHopperState state */ | ||||
| typedef enum { | ||||
|     SubGhzHopperStateOFF, | ||||
|     SubGhzHopperStateRunnig, | ||||
|     SubGhzHopperStatePause, | ||||
|     SubGhzHopperStateRSSITimeOut, | ||||
| } SubGhzHopperState; | ||||
| 
 | ||||
| struct SubGhzTxRx { | ||||
|     SubGhzWorker* worker; | ||||
|     SubGhzProtocol* protocol; | ||||
|     SubGhzProtocolCommon* protocol_result; | ||||
|     SubGhzProtocolCommonEncoder* encoder; | ||||
|     uint32_t frequency; | ||||
|     FuriHalSubGhzPreset preset; | ||||
|     SubGhzHistory* history; | ||||
|     uint16_t idx_menu_chosen; | ||||
|     SubGhzTxRxState txrx_state; | ||||
|     //bool hopper_runing;
 | ||||
|     SubGhzHopperState hopper_state; | ||||
|     uint8_t hopper_timeout; | ||||
|     uint8_t hopper_idx_frequency; | ||||
| }; | ||||
| 
 | ||||
| typedef struct SubGhzTxRx SubGhzTxRx; | ||||
| 
 | ||||
| struct SubGhz { | ||||
|     Gui* gui; | ||||
|     NotificationApp* notifications; | ||||
| 
 | ||||
|     SubGhzTxRx* txrx; | ||||
| 
 | ||||
|     SceneManager* scene_manager; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
| 
 | ||||
|     Submenu* submenu; | ||||
|     DialogEx* dialog_ex; | ||||
|     Popup* popup; | ||||
|     TextInput* text_input; | ||||
|     Widget* widget; | ||||
|     char text_store[SUBGHZ_TEXT_STORE_SIZE + 1]; | ||||
|     uint8_t state_notifications; | ||||
| 
 | ||||
|     SubghzReceiver* subghz_receiver; | ||||
|     SubghzTransmitter* subghz_transmitter; | ||||
|     VariableItemList* variable_item_list; | ||||
| 
 | ||||
|     SubghzTestStatic* subghz_test_static; | ||||
|     SubghzTestCarrier* subghz_test_carrier; | ||||
|     SubghzTestPacket* subghz_test_packet; | ||||
|     string_t error_str; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubGhzViewMenu, | ||||
| 
 | ||||
|     SubGhzViewDialogEx, | ||||
|     SubGhzViewReceiver, | ||||
|     SubGhzViewPopup, | ||||
|     SubGhzViewTextInput, | ||||
|     SubGhzViewWidget, | ||||
|     SubGhzViewTransmitter, | ||||
| 
 | ||||
|     SubGhzViewVariableItemList, | ||||
|     SubGhzViewStatic, | ||||
|     SubGhzViewTestCarrier, | ||||
|     SubGhzViewTestPacket, | ||||
| @ -86,8 +121,8 @@ uint32_t subghz_tx(uint32_t frequency); | ||||
| void subghz_idle(void); | ||||
| void subghz_rx_end(void* context); | ||||
| void subghz_sleep(void); | ||||
| void subghz_transmitter_tx_start(void* context); | ||||
| void subghz_transmitter_tx_stop(void* context); | ||||
| void subghz_tx_start(void* context); | ||||
| void subghz_tx_stop(void* context); | ||||
| bool subghz_key_load(SubGhz* subghz, const char* file_path); | ||||
| bool subghz_save_protocol_to_file(void* context, const char* dev_name); | ||||
| bool subghz_load_protocol_from_file(SubGhz* subghz); | ||||
|  | ||||
| @ -1,42 +1,32 @@ | ||||
| #include "subghz_receiver.h" | ||||
| #include "../subghz_i.h" | ||||
| #include <math.h> | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| 
 | ||||
| #include <input/input.h> | ||||
| #include <gui/elements.h> | ||||
| #include <notification/notification-messages.h> | ||||
| #include <lib/subghz/protocols/subghz_protocol_princeton.h> | ||||
| 
 | ||||
| #include <assets_icons.h> | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| 
 | ||||
| #define FRAME_HEIGHT 12 | ||||
| #define MAX_LEN_PX 100 | ||||
| #define MENU_ITEMS 4 | ||||
| 
 | ||||
| #define COUNT_FREQUNCY_HOPPER 3 | ||||
| const uint32_t subghz_frequencies_hopper[] = { | ||||
|     /* 300 - 348 */ | ||||
|     315000000, | ||||
|     /* 387 - 464 */ | ||||
|     433920000, /* LPD433 mid */ | ||||
|     /* 779 - 928 */ | ||||
|     868350000, | ||||
| typedef struct { | ||||
|     string_t item_str; | ||||
|     uint8_t type; | ||||
| } SubGhzReceiverMenuItem; | ||||
| 
 | ||||
| ARRAY_DEF(SubGhzReceiverMenuItemArray, SubGhzReceiverMenuItem, M_POD_OPLIST) | ||||
| 
 | ||||
| #define M_OPL_SubGhzReceiverMenuItemArray_t() \ | ||||
|     ARRAY_OPLIST(SubGhzReceiverMenuItemArray, M_POD_OPLIST) | ||||
| 
 | ||||
| struct SubGhzReceiverHistory { | ||||
|     SubGhzReceiverMenuItemArray_t data; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     ReceiverSceneStart, | ||||
|     ReceiverSceneMain, | ||||
|     ReceiverSceneConfig, | ||||
|     ReceiverSceneInfo, | ||||
| } SubghzReceiverScene; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubGhzHopperStateOFF, | ||||
|     SubGhzHopperStatePause, | ||||
|     SubGhzHopperStateRunnig, | ||||
|     SubGhzHopperStateRSSITimeOut, | ||||
| } SubGhzHopperState; | ||||
| typedef struct SubGhzReceiverHistory SubGhzReceiverHistory; | ||||
| 
 | ||||
| static const Icon* ReceiverItemIcons[] = { | ||||
|     [TYPE_PROTOCOL_UNKNOWN] = &I_Quest_7x8, | ||||
| @ -48,27 +38,16 @@ struct SubghzReceiver { | ||||
|     View* view; | ||||
|     SubghzReceiverCallback callback; | ||||
|     void* context; | ||||
|     SubGhzWorker* worker; | ||||
|     SubGhzProtocol* protocol; | ||||
|     osTimerId timer; | ||||
|     SubGhzHopperState hopper_state; | ||||
|     uint8_t hopper_timeout; | ||||
|     uint32_t event_key_sequence; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     string_t text; | ||||
|     uint16_t scene; | ||||
|     SubGhzProtocolCommon* protocol_result; | ||||
|     SubGhzHistory* history; | ||||
|     uint8_t frequency; | ||||
|     uint8_t temp_frequency; | ||||
|     uint32_t real_frequency; | ||||
| 
 | ||||
|     string_t frequency_str; | ||||
|     string_t preset_str; | ||||
|     string_t history_stat_str; | ||||
|     SubGhzReceiverHistory* history; | ||||
|     uint16_t idx; | ||||
|     uint16_t list_offset; | ||||
|     uint16_t history_item; | ||||
|     bool menu; | ||||
| } SubghzReceiverModel; | ||||
| 
 | ||||
| void subghz_receiver_set_callback( | ||||
| @ -81,35 +60,6 @@ void subghz_receiver_set_callback( | ||||
|     subghz_receiver->context = context; | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_set_protocol( | ||||
|     SubghzReceiver* subghz_receiver, | ||||
|     SubGhzProtocolCommon* protocol_result, | ||||
|     SubGhzProtocol* protocol) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             model->protocol_result = protocol_result; | ||||
|             return true; | ||||
|         }); | ||||
|     subghz_receiver->protocol = protocol; | ||||
| } | ||||
| 
 | ||||
| SubGhzProtocolCommon* subghz_receiver_get_protocol(SubghzReceiver* subghz_receiver) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     SubGhzProtocolCommon* result = NULL; | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             result = model->protocol_result; | ||||
|             return false; | ||||
|         }); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_set_worker(SubghzReceiver* subghz_receiver, SubGhzWorker* worker) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     subghz_receiver->worker = worker; | ||||
| } | ||||
| 
 | ||||
| static void subghz_receiver_update_offset(SubghzReceiver* subghz_receiver) { | ||||
|     furi_assert(subghz_receiver); | ||||
| 
 | ||||
| @ -129,6 +79,38 @@ static void subghz_receiver_update_offset(SubghzReceiver* subghz_receiver) { | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_add_item_to_menu( | ||||
|     SubghzReceiver* subghz_receiver, | ||||
|     const char* name, | ||||
|     uint8_t type) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             SubGhzReceiverMenuItem* item_menu = | ||||
|                 SubGhzReceiverMenuItemArray_push_raw(model->history->data); | ||||
|             string_init_set_str(item_menu->item_str, name); | ||||
|             item_menu->type = type; | ||||
|             model->history_item++; | ||||
|             return true; | ||||
|         }); | ||||
|     subghz_receiver_update_offset(subghz_receiver); | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_add_data_statusbar( | ||||
|     SubghzReceiver* subghz_receiver, | ||||
|     const char* frequency_str, | ||||
|     const char* preset_str, | ||||
|     const char* history_stat_str) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             string_set(model->frequency_str, frequency_str); | ||||
|             string_set(model->preset_str, preset_str); | ||||
|             string_set(model->history_stat_str, history_stat_str); | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| static void subghz_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); | ||||
| @ -144,462 +126,113 @@ static void subghz_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scroll | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_draw(Canvas* canvas, SubghzReceiverModel* model) { | ||||
|     bool scrollbar = model->history_item > 4; | ||||
|     string_t str_buff; | ||||
|     char buffer[64]; | ||||
|     uint32_t frequency; | ||||
|     string_init(str_buff); | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
| 
 | ||||
|     switch(model->scene) { | ||||
|     case ReceiverSceneMain: | ||||
|         for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { | ||||
|             size_t idx = CLAMP(i + model->list_offset, model->history_item, 0); | ||||
|             subghz_history_get_text_item_menu(model->history, str_buff, idx); | ||||
|             elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); | ||||
|             if(model->idx == idx) { | ||||
|                 subghz_receiver_draw_frame(canvas, i, scrollbar); | ||||
|             } else { | ||||
|                 canvas_set_color(canvas, ColorBlack); | ||||
|             } | ||||
|             canvas_draw_icon( | ||||
|                 canvas, | ||||
|                 1, | ||||
|                 2 + i * FRAME_HEIGHT, | ||||
|                 ReceiverItemIcons[subghz_history_get_type_protocol(model->history, idx)]); | ||||
|             canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); | ||||
|             string_clean(str_buff); | ||||
|         } | ||||
|         if(scrollbar) { | ||||
|             elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); | ||||
|         } | ||||
|         canvas_set_color(canvas, ColorBlack); | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|     elements_button_left(canvas, "Config"); | ||||
| 
 | ||||
|         elements_button_left(canvas, "Config"); | ||||
|         canvas_draw_line(canvas, 46, 51, 125, 51); | ||||
|         if(subghz_history_get_text_space_left(model->history, str_buff)) { | ||||
|             canvas_draw_str(canvas, 54, 62, string_get_cstr(str_buff)); | ||||
|         } else { | ||||
|             if((model->real_frequency / 1000 % 10) > 4) { | ||||
|                 frequency = model->real_frequency + 10000; | ||||
|             } else { | ||||
|                 frequency = model->real_frequency; | ||||
|             } | ||||
|             snprintf( | ||||
|                 buffer, | ||||
|                 sizeof(buffer), | ||||
|                 "%03ld.%02ld", | ||||
|                 frequency / 1000000 % 1000, | ||||
|                 frequency / 10000 % 100); | ||||
|             canvas_draw_str(canvas, 44, 62, buffer); | ||||
|             canvas_draw_str(canvas, 79, 62, "AM"); | ||||
|             canvas_draw_str(canvas, 96, 62, string_get_cstr(str_buff)); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     case ReceiverSceneStart: | ||||
|     canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str)); | ||||
|     canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str)); | ||||
|     canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str)); | ||||
|     if(model->history_item == 0) { | ||||
|         canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); | ||||
|         canvas_set_font(canvas, FontPrimary); | ||||
|         canvas_draw_str(canvas, 63, 46, "Scanning..."); | ||||
|         canvas_set_color(canvas, ColorBlack); | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|         elements_button_left(canvas, "Config"); | ||||
|         if((model->real_frequency / 1000 % 10) > 4) { | ||||
|             frequency = model->real_frequency + 10000; | ||||
|         } else { | ||||
|             frequency = model->real_frequency; | ||||
|         } | ||||
|         snprintf( | ||||
|             buffer, | ||||
|             sizeof(buffer), | ||||
|             "%03ld.%02ld", | ||||
|             frequency / 1000000 % 1000, | ||||
|             frequency / 10000 % 100); | ||||
|         canvas_draw_str(canvas, 44, 62, buffer); | ||||
|         canvas_draw_str(canvas, 79, 62, "AM"); | ||||
|         subghz_history_get_text_space_left(model->history, str_buff); | ||||
|         canvas_draw_str(canvas, 96, 62, string_get_cstr(str_buff)); | ||||
|         canvas_draw_line(canvas, 46, 51, 125, 51); | ||||
|         break; | ||||
| 
 | ||||
|     case ReceiverSceneConfig: | ||||
|         if(model->frequency < subghz_frequencies_count) { | ||||
|             snprintf( | ||||
|                 buffer, | ||||
|                 sizeof(buffer), | ||||
|                 "Frequency:  < %03ld.%03ldMHz >", | ||||
|                 model->real_frequency / 1000000 % 1000, | ||||
|                 model->real_frequency / 1000 % 1000); | ||||
|             canvas_draw_str(canvas, 0, 8, buffer); | ||||
|             canvas_draw_str(canvas, 0, 18, "Frequency Hopping: <OFF>"); | ||||
|         } else { | ||||
|             canvas_draw_str(canvas, 0, 8, "Frequency: < --- >"); | ||||
|             canvas_draw_str(canvas, 0, 18, "Frequency Hopping: <ON>"); | ||||
|         } | ||||
|         canvas_draw_str(canvas, 0, 28, "Modulation: <AM>"); | ||||
| 
 | ||||
|         elements_button_center(canvas, "Save"); | ||||
|         break; | ||||
| 
 | ||||
|     case ReceiverSceneInfo: | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|         elements_multiline_text(canvas, 0, 8, string_get_cstr(model->text)); | ||||
|         snprintf( | ||||
|             buffer, | ||||
|             sizeof(buffer), | ||||
|             "%03ld.%03ld", | ||||
|             subghz_history_get_frequency(model->history, model->idx) / 1000000 % 1000, | ||||
|             subghz_history_get_frequency(model->history, model->idx) / 1000 % 1000); | ||||
|         canvas_draw_str(canvas, 90, 8, buffer); | ||||
|         if(model->protocol_result && model->protocol_result->to_save_string && | ||||
|            strcmp(model->protocol_result->name, "KeeLoq")) { | ||||
|             elements_button_right(canvas, "Save"); | ||||
|             elements_button_center(canvas, "Send"); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
| 
 | ||||
|         break; | ||||
|         return; | ||||
|     } | ||||
|     canvas_draw_line(canvas, 46, 51, 125, 51); | ||||
| 
 | ||||
|     bool scrollbar = model->history_item > 4; | ||||
|     string_t str_buff; | ||||
|     string_init(str_buff); | ||||
| 
 | ||||
|     SubGhzReceiverMenuItem* item_menu; | ||||
| 
 | ||||
|     for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { | ||||
|         size_t idx = CLAMP(i + model->list_offset, model->history_item, 0); | ||||
|         item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); | ||||
|         string_set(str_buff, item_menu->item_str); | ||||
|         elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); | ||||
|         if(model->idx == idx) { | ||||
|             subghz_receiver_draw_frame(canvas, i, scrollbar); | ||||
|         } else { | ||||
|             canvas_set_color(canvas, ColorBlack); | ||||
|         } | ||||
|         canvas_draw_icon(canvas, 1, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); | ||||
|         canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); | ||||
|         string_clean(str_buff); | ||||
|     } | ||||
|     if(scrollbar) { | ||||
|         elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); | ||||
|     } | ||||
|     string_clear(str_buff); | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_history_full(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzReceiver* subghz_receiver = context; | ||||
|     subghz_receiver->callback(SubghzReceverEventSendHistoryFull, subghz_receiver->context); | ||||
|     subghz_receiver->hopper_state = SubGhzHopperStateOFF; | ||||
| } | ||||
| 
 | ||||
| bool subghz_receiver_input(InputEvent* event, void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     uint8_t scene = 0; | ||||
|     SubghzReceiver* subghz_receiver = context; | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             scene = model->scene; | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     bool can_be_saved = false; | ||||
| 
 | ||||
|     switch(scene) { | ||||
|     case ReceiverSceneMain: | ||||
|         if(event->key == InputKeyBack && event->type == InputTypeShort) { | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     model->idx = 0; | ||||
|                     model->list_offset = 0; | ||||
|                     model->history_item = 0; | ||||
|                     subghz_history_clean(model->history); | ||||
|                     return true; | ||||
|                 }); | ||||
|             return false; | ||||
|         } else if( | ||||
|             event->key == InputKeyUp && | ||||
|             (event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     if(model->idx != 0) model->idx--; | ||||
|                     return true; | ||||
|                 }); | ||||
|         } else if( | ||||
|             event->key == InputKeyDown && | ||||
|             (event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     if(model->idx != subghz_history_get_item(model->history) - 1) model->idx++; | ||||
|                     return true; | ||||
|                 }); | ||||
|         } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { | ||||
|             subghz_receiver->hopper_state = SubGhzHopperStatePause; | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     model->scene = ReceiverSceneConfig; | ||||
|                     model->temp_frequency = model->frequency; | ||||
|                     return true; | ||||
|                 }); | ||||
|             subghz_receiver->callback(SubghzReceverEventConfig, subghz_receiver->context); | ||||
|         } else if(event->key == InputKeyOk && event->type == InputTypeShort) { | ||||
|             subghz_receiver->event_key_sequence = event->sequence; | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     string_clean(model->text); | ||||
|                     model->protocol_result = subghz_protocol_get_by_name( | ||||
|                         subghz_receiver->protocol, | ||||
|                         subghz_history_get_name(model->history, model->idx)); | ||||
|                     if(model->protocol_result->to_load_protocol != NULL) { | ||||
|                         model->protocol_result->to_load_protocol( | ||||
|                             model->protocol_result, | ||||
|                             subghz_history_get_raw_data(model->history, model->idx)); | ||||
|                         model->protocol_result->to_string(model->protocol_result, model->text); | ||||
|                         model->scene = ReceiverSceneInfo; | ||||
|                     } | ||||
|                     return true; | ||||
|                 }); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     case ReceiverSceneInfo: | ||||
|     if(event->key == InputKeyBack && event->type == InputTypeShort) { | ||||
|         subghz_receiver->callback(SubghzReceverEventBack, subghz_receiver->context); | ||||
|     } else if( | ||||
|         event->key == InputKeyUp && | ||||
|         (event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||
|         with_view_model( | ||||
|             subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                 can_be_saved = | ||||
|                     (model->protocol_result && model->protocol_result->to_save_string && | ||||
|                      strcmp(model->protocol_result->name, "KeeLoq")); | ||||
|                 return false; | ||||
|                 if(model->idx != 0) model->idx--; | ||||
|                 return true; | ||||
|             }); | ||||
|         if(event->key == InputKeyBack && event->type == InputTypeShort) { | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     subghz_rx_end(subghz_receiver->worker); | ||||
|                     model->real_frequency = | ||||
|                         subghz_rx(subghz_receiver->worker, subghz_frequencies[model->frequency]); | ||||
|                     subghz_receiver->hopper_state = SubGhzHopperStateRunnig; | ||||
|                     model->scene = ReceiverSceneMain; | ||||
|                     return true; | ||||
|                 }); | ||||
|             subghz_receiver->callback(SubghzReceverEventMain, subghz_receiver->context); | ||||
|         } else if(can_be_saved && event->key == InputKeyRight) { | ||||
|             subghz_receiver->callback(SubghzReceverEventSave, subghz_receiver->context); | ||||
|             return false; | ||||
|         } else if( | ||||
|             can_be_saved && event->key == InputKeyOk && event->type == InputTypePress && | ||||
|             subghz_receiver->event_key_sequence != event->sequence) { | ||||
|             subghz_receiver->hopper_state = SubGhzHopperStatePause; | ||||
|             subghz_rx_end(subghz_receiver->worker); | ||||
|             subghz_receiver->callback(SubghzReceverEventSendStart, subghz_receiver->context); | ||||
|             return true; | ||||
|         } else if( | ||||
|             can_be_saved && event->key == InputKeyOk && event->type == InputTypeRelease && | ||||
|             subghz_receiver->event_key_sequence != event->sequence) { | ||||
|             subghz_receiver->callback(SubghzReceverEventSendStop, subghz_receiver->context); | ||||
|             return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     case ReceiverSceneConfig: | ||||
|         if(event->type != InputTypeShort) return false; | ||||
|         if(event->key == InputKeyBack) { | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     model->frequency = model->temp_frequency; | ||||
|                     model->real_frequency = subghz_frequencies[model->frequency]; | ||||
|                     subghz_receiver->hopper_state = SubGhzHopperStateRunnig; | ||||
|                     if(subghz_history_get_item(model->history) == 0) { | ||||
|                         model->scene = ReceiverSceneStart; | ||||
|                     } else { | ||||
|                         model->scene = ReceiverSceneMain; | ||||
|                     } | ||||
|                     return true; | ||||
|                 }); | ||||
|             subghz_receiver->callback(SubghzReceverEventMain, subghz_receiver->context); | ||||
|         } else if(event->key == InputKeyOk) { | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     if(model->frequency < subghz_frequencies_count) { | ||||
|                         subghz_rx_end(subghz_receiver->worker); | ||||
|                         model->real_frequency = subghz_rx( | ||||
|                             subghz_receiver->worker, subghz_frequencies[model->frequency]); | ||||
|                         subghz_receiver->hopper_state = SubGhzHopperStateOFF; | ||||
|                     } else { | ||||
|                         osTimerStart(subghz_receiver->timer, 1024 / 10); | ||||
|                         subghz_receiver->hopper_state = SubGhzHopperStateRunnig; | ||||
|                     } | ||||
|                     if(subghz_history_get_item(model->history) == 0) { | ||||
|                         model->scene = ReceiverSceneStart; | ||||
|                     } else { | ||||
|                         model->scene = ReceiverSceneMain; | ||||
|                     } | ||||
|                     return true; | ||||
|                 }); | ||||
|             subghz_receiver->callback(SubghzReceverEventMain, subghz_receiver->context); | ||||
|         } else { | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     bool model_updated = false; | ||||
| 
 | ||||
|                     if(event->key == InputKeyLeft) { | ||||
|                         if(model->frequency > 0) model->frequency--; | ||||
|                         model_updated = true; | ||||
|                     } else if(event->key == InputKeyRight) { | ||||
|                         if(model->frequency < subghz_frequencies_count) model->frequency++; | ||||
|                         model_updated = true; | ||||
|                     } | ||||
|                     if(model_updated) { | ||||
|                         model->real_frequency = subghz_frequencies[model->frequency]; | ||||
|                     } | ||||
|                     return model_updated; | ||||
|                 }); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     case ReceiverSceneStart: | ||||
|         if(event->type != InputTypeShort) return false; | ||||
|         if(event->key == InputKeyBack) { | ||||
|             return false; | ||||
|         } else if(event->key == InputKeyLeft) { | ||||
|             subghz_receiver->hopper_state = SubGhzHopperStatePause; | ||||
|             with_view_model( | ||||
|                 subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                     model->temp_frequency = model->frequency; | ||||
|                     model->scene = ReceiverSceneConfig; | ||||
|                     return true; | ||||
|                 }); | ||||
|             subghz_receiver->callback(SubghzReceverEventConfig, subghz_receiver->context); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     subghz_receiver_update_offset(subghz_receiver); | ||||
|     if(scene != ReceiverSceneInfo) { | ||||
|     } else if( | ||||
|         event->key == InputKeyDown && | ||||
|         (event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||
|         with_view_model( | ||||
|             subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                 if(subghz_history_get_text_space_left(model->history, NULL)) { | ||||
|                     subghz_receiver_history_full(subghz_receiver); | ||||
|                 if(model->idx != model->history_item - 1) model->idx++; | ||||
|                 return true; | ||||
|             }); | ||||
|     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { | ||||
|         subghz_receiver->callback(SubghzReceverEventConfig, subghz_receiver->context); | ||||
|     } else if(event->key == InputKeyOk && event->type == InputTypeShort) { | ||||
|         with_view_model( | ||||
|             subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|                 if(model->history_item != 0) { | ||||
|                     subghz_receiver->callback(SubghzReceverEventOK, subghz_receiver->context); | ||||
|                 } | ||||
|                 return false; | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     subghz_receiver_update_offset(subghz_receiver); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_text_callback(string_t text, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzReceiver* subghz_receiver = context; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             string_set(model->text, text); | ||||
|             model->scene = ReceiverSceneMain; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_protocol_callback(SubGhzProtocolCommon* parser, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzReceiver* subghz_receiver = context; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             model->protocol_result = parser; | ||||
|             subghz_history_set_frequency_preset( | ||||
|                 model->history, | ||||
|                 model->history_item, | ||||
|                 model->real_frequency, | ||||
|                 FuriHalSubGhzPresetOok650Async); | ||||
|             subghz_history_add_to_history(model->history, parser); | ||||
| 
 | ||||
|             model->history_item = subghz_history_get_item(model->history); | ||||
|             model->scene = ReceiverSceneMain; | ||||
|             if(subghz_history_get_text_space_left(model->history, NULL)) { | ||||
|                 subghz_receiver_history_full(subghz_receiver); | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
|     subghz_protocol_reset(subghz_receiver->protocol); | ||||
|     subghz_receiver_update_offset(subghz_receiver); | ||||
| } | ||||
| 
 | ||||
| static void subghz_receiver_timer_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzReceiver* subghz_receiver = context; | ||||
| 
 | ||||
|     switch(subghz_receiver->hopper_state) { | ||||
|     case SubGhzHopperStatePause: | ||||
|         return; | ||||
|         break; | ||||
|     case SubGhzHopperStateOFF: | ||||
|         osTimerStop(subghz_receiver->timer); | ||||
|         return; | ||||
|         break; | ||||
|     case SubGhzHopperStateRSSITimeOut: | ||||
|         if(subghz_receiver->hopper_timeout != 0) { | ||||
|             subghz_receiver->hopper_timeout--; | ||||
|             return; | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
|     float rssi = -127.0f; | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             if(subghz_receiver->hopper_state != SubGhzHopperStateRSSITimeOut) { | ||||
|                 // See RSSI Calculation timings in CC1101 17.3 RSSI
 | ||||
|                 rssi = furi_hal_subghz_get_rssi(); | ||||
| 
 | ||||
|                 // Stay if RSSI is high enough
 | ||||
|                 if(rssi > -90.0f) { | ||||
|                     subghz_receiver->hopper_timeout = 10; | ||||
|                     subghz_receiver->hopper_state = SubGhzHopperStateRSSITimeOut; | ||||
|                     return false; | ||||
|                 } | ||||
|             } else { | ||||
|                 subghz_receiver->hopper_state = SubGhzHopperStateRunnig; | ||||
|             } | ||||
| 
 | ||||
|             // Select next frequency
 | ||||
|             if(model->frequency < COUNT_FREQUNCY_HOPPER - 1) { | ||||
|                 model->frequency++; | ||||
|             } else { | ||||
|                 model->frequency = 0; | ||||
|             } | ||||
| 
 | ||||
|             // Restart radio
 | ||||
|             furi_hal_subghz_idle(); | ||||
|             subghz_protocol_reset(subghz_receiver->protocol); | ||||
|             model->real_frequency = furi_hal_subghz_set_frequency_and_path( | ||||
|                 subghz_frequencies_hopper[model->frequency]); | ||||
|             furi_hal_subghz_rx(); | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_enter(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzReceiver* subghz_receiver = context; | ||||
|     //Start CC1101 Rx
 | ||||
|     subghz_begin(FuriHalSubGhzPresetOok650Async); | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             subghz_rx_end(subghz_receiver->worker); | ||||
|             model->frequency = subghz_frequencies_433_92; | ||||
|             model->real_frequency = | ||||
|                 subghz_rx(subghz_receiver->worker, subghz_frequencies[model->frequency]); | ||||
|             if(subghz_history_get_item(model->history) == 0) { | ||||
|                 model->scene = ReceiverSceneStart; | ||||
|             } else { | ||||
|                 model->scene = ReceiverSceneMain; | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
|     subghz_protocol_enable_dump( | ||||
|         subghz_receiver->protocol, subghz_receiver_protocol_callback, subghz_receiver); | ||||
|     //SubghzReceiver* subghz_receiver = context;
 | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_exit(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzReceiver* subghz_receiver = context; | ||||
|     osTimerStop(subghz_receiver->timer); | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             string_clean(model->text); | ||||
|             return true; | ||||
|             string_clean(model->frequency_str); | ||||
|             string_clean(model->preset_str); | ||||
|             string_clean(model->history_stat_str); | ||||
|                 for | ||||
|                     M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) { | ||||
|                         string_clear(item_menu->item_str); | ||||
|                         item_menu->type = 0; | ||||
|                     } | ||||
|                 SubGhzReceiverMenuItemArray_clean(model->history->data); | ||||
|                 model->idx = 0; | ||||
|                 model->list_offset = 0; | ||||
|                 model->history_item = 0; | ||||
|                 return false; | ||||
|         }); | ||||
|     // Stop CC1101 Rx
 | ||||
|     subghz_rx_end(subghz_receiver->worker); | ||||
|     subghz_sleep(); | ||||
| } | ||||
| 
 | ||||
| SubghzReceiver* subghz_receiver_alloc() { | ||||
| @ -616,14 +249,14 @@ SubghzReceiver* subghz_receiver_alloc() { | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             string_init(model->text); | ||||
|             model->history = subghz_history_alloc(); | ||||
|             string_init(model->frequency_str); | ||||
|             string_init(model->preset_str); | ||||
|             string_init(model->history_stat_str); | ||||
|             model->history = furi_alloc(sizeof(SubGhzReceiverHistory)); | ||||
|             SubGhzReceiverMenuItemArray_init(model->history->data); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     subghz_receiver->timer = | ||||
|         osTimerNew(subghz_receiver_timer_callback, osTimerPeriodic, subghz_receiver, NULL); | ||||
|     subghz_receiver->hopper_state = SubGhzHopperStateOFF; | ||||
|     return subghz_receiver; | ||||
| } | ||||
| 
 | ||||
| @ -632,11 +265,18 @@ void subghz_receiver_free(SubghzReceiver* subghz_receiver) { | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             string_clear(model->text); | ||||
|             subghz_history_free(model->history); | ||||
|             return false; | ||||
|             string_clear(model->frequency_str); | ||||
|             string_clear(model->preset_str); | ||||
|             string_clear(model->history_stat_str); | ||||
|                 for | ||||
|                     M_EACH(item_menu, model->history->data, SubGhzReceiverMenuItemArray_t) { | ||||
|                         string_clear(item_menu->item_str); | ||||
|                         item_menu->type = 0; | ||||
|                     } | ||||
|                 SubGhzReceiverMenuItemArray_clear(model->history->data); | ||||
|                 free(model->history); | ||||
|                 return false; | ||||
|         }); | ||||
|     osTimerDelete(subghz_receiver->timer); | ||||
|     view_free(subghz_receiver->view); | ||||
|     free(subghz_receiver); | ||||
| } | ||||
| @ -646,43 +286,24 @@ View* subghz_receiver_get_view(SubghzReceiver* subghz_receiver) { | ||||
|     return subghz_receiver->view; | ||||
| } | ||||
| 
 | ||||
| uint32_t subghz_receiver_get_frequency(SubghzReceiver* subghz_receiver) { | ||||
| uint16_t subghz_receiver_get_idx_menu(SubghzReceiver* subghz_receiver) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     uint32_t frequency; | ||||
|     uint32_t idx = 0; | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             frequency = subghz_history_get_frequency(model->history, model->idx); | ||||
|             idx = model->idx; | ||||
|             return false; | ||||
|         }); | ||||
|     return frequency; | ||||
|     return idx; | ||||
| } | ||||
| 
 | ||||
| FuriHalSubGhzPreset subghz_receiver_get_preset(SubghzReceiver* subghz_receiver) { | ||||
| void subghz_receiver_set_idx_menu(SubghzReceiver* subghz_receiver, uint16_t idx) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     FuriHalSubGhzPreset preset; | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             preset = subghz_history_get_preset(model->history, model->idx); | ||||
|             return false; | ||||
|             model->idx = idx; | ||||
|             if(model->idx > 2) model->list_offset = idx - 2; | ||||
|             return true; | ||||
|         }); | ||||
|     return preset; | ||||
| } | ||||
| 
 | ||||
| void subghz_receiver_frequency_preset_to_str(SubghzReceiver* subghz_receiver, string_t output) { | ||||
|     furi_assert(subghz_receiver); | ||||
|     uint32_t frequency; | ||||
|     uint32_t preset; | ||||
|     with_view_model( | ||||
|         subghz_receiver->view, (SubghzReceiverModel * model) { | ||||
|             frequency = subghz_history_get_frequency(model->history, model->idx); | ||||
|             preset = (uint32_t)subghz_history_get_preset(model->history, model->idx); | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     string_cat_printf( | ||||
|         output, | ||||
|         "Frequency: %d\n" | ||||
|         "Preset: %d\n", | ||||
|         (int)frequency, | ||||
|         (int)preset); | ||||
|     subghz_receiver_update_offset(subghz_receiver); | ||||
| } | ||||
| @ -1,21 +1,11 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| #include <lib/subghz/protocols/subghz_protocol_common.h> | ||||
| #include <lib/subghz/protocols/subghz_protocol.h> | ||||
| #include <lib/subghz/subghz_worker.h> | ||||
| #include "../subghz_history.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubghzReceverEventOK, | ||||
|     SubghzReceverEventConfig, | ||||
|     SubghzReceverEventMain, | ||||
|     SubghzReceverEventSave, | ||||
|     SubghzReceverEventBack, | ||||
|     SubghzReceverEventMore, | ||||
|     SubghzReceverEventSendStart, | ||||
|     SubghzReceverEventSendStop, | ||||
|     SubghzReceverEventSendHistoryFull, | ||||
| } SubghzReceverEvent; | ||||
| 
 | ||||
| typedef struct SubghzReceiver SubghzReceiver; | ||||
| @ -33,17 +23,19 @@ void subghz_receiver_free(SubghzReceiver* subghz_receiver); | ||||
| 
 | ||||
| View* subghz_receiver_get_view(SubghzReceiver* subghz_receiver); | ||||
| 
 | ||||
| void subghz_receiver_set_protocol( | ||||
| void subghz_receiver_add_data_statusbar( | ||||
|     SubghzReceiver* subghz_receiver, | ||||
|     SubGhzProtocolCommon* protocol_result, | ||||
|     SubGhzProtocol* protocol); | ||||
|     const char* frequency_str, | ||||
|     const char* preset_str, | ||||
|     const char* history_stat_str); | ||||
| 
 | ||||
| SubGhzProtocolCommon* subghz_receiver_get_protocol(SubghzReceiver* subghz_receiver); | ||||
| void subghz_receiver_add_item_to_menu( | ||||
|     SubghzReceiver* subghz_receiver, | ||||
|     const char* name, | ||||
|     uint8_t type); | ||||
| 
 | ||||
| void subghz_receiver_set_worker(SubghzReceiver* subghz_receiver, SubGhzWorker* worker); | ||||
| uint16_t subghz_receiver_get_idx_menu(SubghzReceiver* subghz_receiver); | ||||
| 
 | ||||
| uint32_t subghz_receiver_get_frequency(SubghzReceiver* subghz_receiver); | ||||
| void subghz_receiver_set_idx_menu(SubghzReceiver* subghz_receiver, uint16_t idx); | ||||
| 
 | ||||
| FuriHalSubGhzPreset subghz_receiver_get_preset(SubghzReceiver* subghz_receiver); | ||||
| 
 | ||||
| void subghz_receiver_frequency_preset_to_str(SubghzReceiver* subghz_receiver, string_t output); | ||||
| void subghz_receiver_exit(void* context); | ||||
| @ -1,13 +1,8 @@ | ||||
| #include "subghz_transmitter.h" | ||||
| #include "../subghz_i.h" | ||||
| 
 | ||||
| #include <math.h> | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| #include <input/input.h> | ||||
| #include <gui/elements.h> | ||||
| #include <notification/notification-messages.h> | ||||
| #include <lib/subghz/protocols/subghz_protocol_keeloq.h> | ||||
| 
 | ||||
| struct SubghzTransmitter { | ||||
|     View* view; | ||||
| @ -16,11 +11,10 @@ struct SubghzTransmitter { | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     string_t text; | ||||
|     uint16_t scene; | ||||
|     uint32_t real_frequency; | ||||
|     FuriHalSubGhzPreset preset; | ||||
|     SubGhzProtocolCommon* protocol; | ||||
|     string_t frequency_str; | ||||
|     string_t preset_str; | ||||
|     string_t key_str; | ||||
|     uint8_t show_button; | ||||
| } SubghzTransmitterModel; | ||||
| 
 | ||||
| void subghz_transmitter_set_callback( | ||||
| @ -33,24 +27,19 @@ void subghz_transmitter_set_callback( | ||||
|     subghz_transmitter->context = context; | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_set_protocol( | ||||
| void subghz_transmitter_add_data_to_show( | ||||
|     SubghzTransmitter* subghz_transmitter, | ||||
|     SubGhzProtocolCommon* protocol) { | ||||
|     const char* key_str, | ||||
|     const char* frequency_str, | ||||
|     const char* preset_str, | ||||
|     uint8_t show_button) { | ||||
|     furi_assert(subghz_transmitter); | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             model->protocol = protocol; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_set_frequency_preset( | ||||
|     SubghzTransmitter* subghz_transmitter, | ||||
|     uint32_t frequency, | ||||
|     FuriHalSubGhzPreset preset) { | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             model->real_frequency = frequency; | ||||
|             model->preset = preset; | ||||
|             string_set(model->key_str, key_str); | ||||
|             string_set(model->frequency_str, frequency_str); | ||||
|             string_set(model->preset_str, preset_str); | ||||
|             model->show_button = show_button; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| @ -87,26 +76,13 @@ static void subghz_transmitter_button_right(Canvas* canvas, const char* str) { | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_draw(Canvas* canvas, SubghzTransmitterModel* model) { | ||||
|     char buffer[64]; | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     elements_multiline_text(canvas, 0, 8, string_get_cstr(model->text)); | ||||
|     snprintf( | ||||
|         buffer, | ||||
|         sizeof(buffer), | ||||
|         "%03ld.%03ld", | ||||
|         model->real_frequency / 1000000 % 1000, | ||||
|         model->real_frequency / 1000 % 1000); | ||||
|     canvas_draw_str(canvas, 90, 8, buffer); | ||||
| 
 | ||||
|     if(model->protocol && model->protocol->get_upload_protocol) { | ||||
|         if((!strcmp(model->protocol->name, "KeeLoq")) && | ||||
|            (!strcmp(subghz_protocol_keeloq_get_manufacture_name(model->protocol), "Unknown"))) { | ||||
|             return; | ||||
|         } | ||||
|         subghz_transmitter_button_right(canvas, "Send"); | ||||
|     } | ||||
|     elements_multiline_text(canvas, 0, 8, string_get_cstr(model->key_str)); | ||||
|     canvas_draw_str(canvas, 78, 8, string_get_cstr(model->frequency_str)); | ||||
|     canvas_draw_str(canvas, 113, 8, string_get_cstr(model->preset_str)); | ||||
|     if(model->show_button) subghz_transmitter_button_right(canvas, "Send"); | ||||
| } | ||||
| 
 | ||||
| bool subghz_transmitter_input(InputEvent* event, void* context) { | ||||
| @ -114,26 +90,25 @@ bool subghz_transmitter_input(InputEvent* event, void* context) { | ||||
|     SubghzTransmitter* subghz_transmitter = context; | ||||
|     bool can_be_sent = false; | ||||
| 
 | ||||
|     if(event->key == InputKeyBack) { | ||||
|     if(event->key == InputKeyBack && event->type == InputTypeShort) { | ||||
|         with_view_model( | ||||
|             subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|                 string_clean(model->frequency_str); | ||||
|                 string_clean(model->preset_str); | ||||
|                 string_clean(model->key_str); | ||||
|                 model->show_button = 0; | ||||
|                 return false; | ||||
|             }); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             if(model->protocol && model->protocol->get_upload_protocol) { | ||||
|                 if((!strcmp(model->protocol->name, "KeeLoq")) && | ||||
|                    (!strcmp( | ||||
|                        subghz_protocol_keeloq_get_manufacture_name(model->protocol), "Unknown"))) { | ||||
|                     return false; | ||||
|                 } | ||||
|             if(model->show_button) { | ||||
|                 can_be_sent = true; | ||||
|             } | ||||
|             //can_be_sent = (model->protocol && model->protocol->get_upload_protocol);
 | ||||
|             string_clean(model->text); | ||||
|             model->protocol->to_string(model->protocol, model->text); | ||||
|             return true; | ||||
|         }); | ||||
|     //if(event->type != InputTypeShort) return false;
 | ||||
| 
 | ||||
|     if(can_be_sent && event->key == InputKeyOk && event->type == InputTypePress) { | ||||
|         subghz_transmitter->callback(SubghzTransmitterEventSendStart, subghz_transmitter->context); | ||||
| @ -146,37 +121,14 @@ bool subghz_transmitter_input(InputEvent* event, void* context) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_text_callback(string_t text, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzTransmitter* subghz_transmitter = context; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             string_set(model->text, text); | ||||
|             model->scene = 0; | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_enter(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzTransmitter* subghz_transmitter = context; | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             string_clean(model->text); | ||||
|             model->protocol->to_string(model->protocol, model->text); | ||||
|             return true; | ||||
|         }); | ||||
|     // SubghzTransmitter* subghz_transmitter = context;
 | ||||
| } | ||||
| 
 | ||||
| void subghz_transmitter_exit(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubghzTransmitter* subghz_transmitter = context; | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             string_clean(model->text); | ||||
|             return true; | ||||
|         }); | ||||
|     // SubghzTransmitter* subghz_transmitter = context;
 | ||||
| } | ||||
| 
 | ||||
| SubghzTransmitter* subghz_transmitter_alloc() { | ||||
| @ -194,7 +146,9 @@ SubghzTransmitter* subghz_transmitter_alloc() { | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             string_init(model->text); | ||||
|             string_init(model->frequency_str); | ||||
|             string_init(model->preset_str); | ||||
|             string_init(model->key_str); | ||||
|             return true; | ||||
|         }); | ||||
|     return subghz_transmitter; | ||||
| @ -205,7 +159,9 @@ void subghz_transmitter_free(SubghzTransmitter* subghz_transmitter) { | ||||
| 
 | ||||
|     with_view_model( | ||||
|         subghz_transmitter->view, (SubghzTransmitterModel * model) { | ||||
|             string_clear(model->text); | ||||
|             string_clear(model->frequency_str); | ||||
|             string_clear(model->preset_str); | ||||
|             string_clear(model->key_str); | ||||
|             return true; | ||||
|         }); | ||||
|     view_free(subghz_transmitter->view); | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| #include <lib/subghz/protocols/subghz_protocol_common.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubghzTransmitterEventSendStart, | ||||
| @ -25,10 +24,9 @@ void subghz_transmitter_free(SubghzTransmitter* subghz_transmitter); | ||||
| 
 | ||||
| View* subghz_transmitter_get_view(SubghzTransmitter* subghz_transmitter); | ||||
| 
 | ||||
| void subghz_transmitter_set_protocol( | ||||
| void subghz_transmitter_add_data_to_show( | ||||
|     SubghzTransmitter* subghz_transmitter, | ||||
|     SubGhzProtocolCommon* protocol); | ||||
| void subghz_transmitter_set_frequency_preset( | ||||
|     SubghzTransmitter* subghz_transmitter, | ||||
|     uint32_t frequency, | ||||
|     FuriHalSubGhzPreset preset); | ||||
|     const char* key_str, | ||||
|     const char* frequency_str, | ||||
|     const char* preset_str, | ||||
|     uint8_t show_button); | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
| #include "test_data/irda_samsung_test_data.srcdata" | ||||
| #include "test_data/irda_rc6_test_data.srcdata" | ||||
| #include "test_data/irda_rc5_test_data.srcdata" | ||||
| #include "test_data/irda_sirc_test_data.srcdata" | ||||
| 
 | ||||
| #define RUN_ENCODER(data, expected) \ | ||||
|     run_encoder((data), COUNT_OF(data), (expected), COUNT_OF(expected)) | ||||
| @ -14,6 +15,8 @@ | ||||
| #define RUN_DECODER(data, expected) \ | ||||
|     run_decoder((data), COUNT_OF(data), (expected), COUNT_OF(expected)) | ||||
| 
 | ||||
| #define RUN_ENCODER_DECODER(data) run_encoder_decoder((data), COUNT_OF(data)) | ||||
| 
 | ||||
| static IrdaDecoderHandler* decoder_handler; | ||||
| static IrdaEncoderHandler* encoder_handler; | ||||
| 
 | ||||
| @ -33,27 +36,44 @@ static void compare_message_results( | ||||
|     mu_check(message_decoded->protocol == message_expected->protocol); | ||||
|     mu_check(message_decoded->command == message_expected->command); | ||||
|     mu_check(message_decoded->address == message_expected->address); | ||||
|     mu_check(message_decoded->repeat == message_expected->repeat); | ||||
|     if((message_expected->protocol == IrdaProtocolSIRC) || | ||||
|        (message_expected->protocol == IrdaProtocolSIRC15) || | ||||
|        (message_expected->protocol == IrdaProtocolSIRC20)) { | ||||
|         mu_check(message_decoded->repeat == false); | ||||
|     } else { | ||||
|         mu_check(message_decoded->repeat == message_expected->repeat); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void | ||||
|     run_encoder_fill_array(IrdaEncoderHandler* handler, uint32_t* timings, uint32_t* timings_len) { | ||||
| /* Encodes signal and merges same levels (high+high, low+low) */ | ||||
| static void run_encoder_fill_array( | ||||
|     IrdaEncoderHandler* handler, | ||||
|     uint32_t* timings, | ||||
|     uint32_t* timings_len, | ||||
|     bool* start_level) { | ||||
|     uint32_t duration = 0; | ||||
|     bool level = false; // start from space
 | ||||
|     bool level = false; | ||||
|     bool level_read; | ||||
|     IrdaStatus status = IrdaStatusError; | ||||
|     int i = 0; | ||||
|     bool first = true; | ||||
| 
 | ||||
|     while(1) { | ||||
|         status = irda_encode(handler, &duration, &level_read); | ||||
|         if(level_read != level) { | ||||
|             level = level_read; | ||||
|         if(first) { | ||||
|             if(start_level) *start_level = level_read; | ||||
|             first = false; | ||||
|             timings[0] = 0; | ||||
|         } else if(level_read != level) { | ||||
|             ++i; | ||||
|             furi_assert(i < *timings_len); | ||||
|             timings[i] = 0; | ||||
|         } | ||||
|         level = level_read; | ||||
|         timings[i] += duration; | ||||
| 
 | ||||
|         furi_assert((status == IrdaStatusOk) || (status == IrdaStatusDone)); | ||||
|         if(status == IrdaStatusDone) break; | ||||
|         furi_assert(i < *timings_len); | ||||
|     } | ||||
| 
 | ||||
|     *timings_len = i + 1; | ||||
| @ -66,8 +86,9 @@ static void run_encoder( | ||||
|     const uint32_t expected_timings[], | ||||
|     uint32_t expected_timings_len) { | ||||
|     uint32_t* timings = 0; | ||||
|     uint32_t timings_len = 0; | ||||
|     uint32_t timings_len = 200; | ||||
|     uint32_t j = 0; | ||||
|     timings = furi_alloc(sizeof(uint32_t) * timings_len); | ||||
| 
 | ||||
|     for(uint32_t message_counter = 0; message_counter < input_messages_len; ++message_counter) { | ||||
|         const IrdaMessage* message = &input_messages[message_counter]; | ||||
| @ -76,44 +97,51 @@ static void run_encoder( | ||||
|         } | ||||
| 
 | ||||
|         timings_len = 200; | ||||
|         timings = furi_alloc(sizeof(uint32_t) * timings_len); | ||||
|         run_encoder_fill_array(encoder_handler, timings, &timings_len); | ||||
|         run_encoder_fill_array(encoder_handler, timings, &timings_len, NULL); | ||||
|         furi_assert(timings_len <= 200); | ||||
| 
 | ||||
|         for(int i = 0; i < timings_len; ++i, ++j) { | ||||
|             mu_check(MATCH_BIT_TIMING(timings[i], expected_timings[j], 120)); | ||||
|             mu_check(MATCH_TIMING(timings[i], expected_timings[j], 120)); | ||||
|             mu_assert(j < expected_timings_len, "encoded more timings than expected"); | ||||
|         } | ||||
| 
 | ||||
|         free(timings); | ||||
|     } | ||||
|     free(timings); | ||||
|     mu_assert(j == expected_timings_len, "encoded less timings than expected"); | ||||
| } | ||||
| 
 | ||||
| static void run_encoder_decoder(const IrdaMessage input_messages[], uint32_t input_messages_len) { | ||||
|     uint32_t* timings = 0; | ||||
|     uint32_t timings_len = 0; | ||||
|     uint32_t timings_len = 200; | ||||
|     bool level = false; | ||||
|     timings = furi_alloc(sizeof(uint32_t) * timings_len); | ||||
| 
 | ||||
|     for(uint32_t message_counter = 0; message_counter < input_messages_len; ++message_counter) { | ||||
|         const IrdaMessage* message_encoded = &input_messages[message_counter]; | ||||
|         if(!message_encoded->repeat) { | ||||
|             irda_reset_encoder(encoder_handler, message_encoded); | ||||
|             level = false; | ||||
|         } | ||||
| 
 | ||||
|         timings_len = 200; | ||||
|         timings = furi_alloc(sizeof(uint32_t) * timings_len); | ||||
|         run_encoder_fill_array(encoder_handler, timings, &timings_len); | ||||
|         run_encoder_fill_array(encoder_handler, timings, &timings_len, &level); | ||||
|         furi_assert(timings_len <= 200); | ||||
| 
 | ||||
|         const IrdaMessage* message_decoded = 0; | ||||
|         for(int i = 0; i < timings_len; ++i) { | ||||
|             message_decoded = irda_decode(decoder_handler, level, timings[i]); | ||||
|             if(i < timings_len - 1) | ||||
|             if((i == timings_len - 2) && level && message_decoded) { | ||||
|                 /* In case we end with space timing - message can be decoded at last mark.
 | ||||
|                  * Exception - SIRC protocol, which has variable message length (12/15/20), | ||||
|                  * and decoder recognizes protocol by silence time before next message | ||||
|                  * or by timeout (irda_check_decoder_ready()). */ | ||||
|                 break; | ||||
|             } else if(i < timings_len - 1) { | ||||
|                 mu_check(!message_decoded); | ||||
|             else | ||||
|             } else { | ||||
|                 if(!message_decoded) { | ||||
|                     message_decoded = irda_check_decoder_ready(decoder_handler); | ||||
|                 } | ||||
|                 mu_check(message_decoded); | ||||
|             } | ||||
|             level = !level; | ||||
|         } | ||||
|         if(message_decoded) { | ||||
| @ -121,9 +149,8 @@ static void run_encoder_decoder(const IrdaMessage input_messages[], uint32_t inp | ||||
|         } else { | ||||
|             mu_check(0); | ||||
|         } | ||||
| 
 | ||||
|         free(timings); | ||||
|     } | ||||
|     free(timings); | ||||
| } | ||||
| 
 | ||||
| static void run_decoder( | ||||
| @ -131,21 +158,49 @@ static void run_decoder( | ||||
|     uint32_t input_delays_len, | ||||
|     const IrdaMessage* message_expected, | ||||
|     uint32_t message_expected_len) { | ||||
|     const IrdaMessage* message_decoded = 0; | ||||
|     IrdaMessage message_decoded_check_local; | ||||
|     bool level = 0; | ||||
|     uint32_t message_counter = 0; | ||||
|     const IrdaMessage* message_decoded = 0; | ||||
| 
 | ||||
|     for(uint32_t i = 0; i < input_delays_len; ++i) { | ||||
|         const IrdaMessage* message_decoded_check = 0; | ||||
| 
 | ||||
|         if(input_delays[i] > IRDA_RAW_RX_TIMING_DELAY_US) { | ||||
|             message_decoded_check = irda_check_decoder_ready(decoder_handler); | ||||
|             if(message_decoded_check) { | ||||
|                 /* irda_decode() can reset message, but we have to call irda_decode() to perform real
 | ||||
|                  * simulation: irda_check() by timeout, then irda_decode() when meet edge */ | ||||
|                 message_decoded_check_local = *message_decoded_check; | ||||
|                 message_decoded_check = &message_decoded_check_local; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         message_decoded = irda_decode(decoder_handler, level, input_delays[i]); | ||||
|         if(message_decoded) { | ||||
| 
 | ||||
|         if(message_decoded_check || message_decoded) { | ||||
|             mu_assert( | ||||
|                 !(message_decoded_check && message_decoded), | ||||
|                 "both messages decoded: check_ready() and irda_decode()"); | ||||
| 
 | ||||
|             if(message_decoded_check) { | ||||
|                 message_decoded = message_decoded_check; | ||||
|             } | ||||
| 
 | ||||
|             mu_assert(message_counter < message_expected_len, "decoded more than expected"); | ||||
|             if(message_counter >= message_expected_len) break; | ||||
|             compare_message_results(message_decoded, &message_expected[message_counter]); | ||||
| 
 | ||||
|             ++message_counter; | ||||
|         } | ||||
|         level = !level; | ||||
|     } | ||||
| 
 | ||||
|     message_decoded = irda_check_decoder_ready(decoder_handler); | ||||
|     if(message_decoded) { | ||||
|         compare_message_results(message_decoded, &message_expected[message_counter]); | ||||
|         ++message_counter; | ||||
|     } | ||||
| 
 | ||||
|     mu_assert(message_counter == message_expected_len, "decoded less than expected"); | ||||
| } | ||||
| 
 | ||||
| @ -155,6 +210,7 @@ MU_TEST(test_decoder_samsung32) { | ||||
| 
 | ||||
| MU_TEST(test_mix) { | ||||
|     RUN_DECODER(test_decoder_rc5_input2, test_decoder_rc5_expected2); | ||||
|     RUN_DECODER(test_decoder_sirc_input1, test_decoder_sirc_expected1); | ||||
|     RUN_DECODER(test_decoder_necext_input1, test_decoder_necext_expected1); | ||||
|     // can use encoder data for decoding, but can't do opposite
 | ||||
|     RUN_DECODER(test_encoder_rc6_expected1, test_encoder_rc6_input1); | ||||
| @ -162,12 +218,16 @@ MU_TEST(test_mix) { | ||||
|     RUN_DECODER(test_decoder_rc6_input1, test_decoder_rc6_expected1); | ||||
|     RUN_DECODER(test_decoder_samsung32_input1, test_decoder_samsung32_expected1); | ||||
|     RUN_DECODER(test_decoder_rc5_input1, test_decoder_rc5_expected1); | ||||
|     RUN_DECODER(test_decoder_sirc_input2, test_decoder_sirc_expected2); | ||||
|     RUN_DECODER(test_decoder_necext_input1, test_decoder_necext_expected1); | ||||
|     RUN_DECODER(test_decoder_sirc_input4, test_decoder_sirc_expected4); | ||||
|     RUN_DECODER(test_decoder_nec_input2, test_decoder_nec_expected2); | ||||
|     RUN_DECODER(test_decoder_rc6_input1, test_decoder_rc6_expected1); | ||||
|     RUN_DECODER(test_decoder_necext_input1, test_decoder_necext_expected1); | ||||
|     RUN_DECODER(test_decoder_sirc_input5, test_decoder_sirc_expected5); | ||||
|     RUN_DECODER(test_decoder_rc5_input5, test_decoder_rc5_expected5); | ||||
|     RUN_DECODER(test_decoder_samsung32_input1, test_decoder_samsung32_expected1); | ||||
|     RUN_DECODER(test_decoder_sirc_input3, test_decoder_sirc_expected3); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_decoder_nec1) { | ||||
| @ -191,6 +251,20 @@ MU_TEST(test_decoder_necext1) { | ||||
|     RUN_DECODER(test_decoder_necext_input1, test_decoder_necext_expected1); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_encoder_sirc) { | ||||
|     RUN_ENCODER(test_encoder_sirc_input1, test_encoder_sirc_expected1); | ||||
|     RUN_ENCODER(test_encoder_sirc_input2, test_encoder_sirc_expected2); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_decoder_sirc) { | ||||
|     RUN_DECODER(test_decoder_sirc_input3, test_decoder_sirc_expected3); | ||||
|     RUN_DECODER(test_decoder_sirc_input1, test_decoder_sirc_expected1); | ||||
|     RUN_DECODER(test_decoder_sirc_input2, test_decoder_sirc_expected2); | ||||
|     RUN_DECODER(test_decoder_sirc_input4, test_decoder_sirc_expected4); | ||||
|     RUN_DECODER(test_decoder_sirc_input5, test_decoder_sirc_expected5); | ||||
|     RUN_ENCODER_DECODER(test_sirc); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_decoder_rc5) { | ||||
|     RUN_DECODER(test_decoder_rc5x_input1, test_decoder_rc5x_expected1); | ||||
|     RUN_DECODER(test_decoder_rc5_input1, test_decoder_rc5_expected1); | ||||
| @ -219,16 +293,19 @@ MU_TEST(test_encoder_rc6) { | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_encoder_decoder_all) { | ||||
|     run_encoder_decoder(test_nec_all, COUNT_OF(test_nec_all)); | ||||
|     run_encoder_decoder(test_necext_all, COUNT_OF(test_necext_all)); | ||||
|     run_encoder_decoder(test_samsung32_all, COUNT_OF(test_samsung32_all)); | ||||
|     run_encoder_decoder(test_rc6_all, COUNT_OF(test_rc6_all)); | ||||
|     run_encoder_decoder(test_rc5_all, COUNT_OF(test_rc5_all)); | ||||
|     RUN_ENCODER_DECODER(test_nec); | ||||
|     RUN_ENCODER_DECODER(test_necext); | ||||
|     RUN_ENCODER_DECODER(test_samsung32); | ||||
|     RUN_ENCODER_DECODER(test_rc6); | ||||
|     RUN_ENCODER_DECODER(test_rc5); | ||||
|     RUN_ENCODER_DECODER(test_sirc); | ||||
| } | ||||
| 
 | ||||
| MU_TEST_SUITE(test_irda_decoder_encoder) { | ||||
|     MU_SUITE_CONFIGURE(&test_setup, &test_teardown); | ||||
| 
 | ||||
|     MU_RUN_TEST(test_encoder_sirc); | ||||
|     MU_RUN_TEST(test_decoder_sirc); | ||||
|     MU_RUN_TEST(test_encoder_rc5x); | ||||
|     MU_RUN_TEST(test_encoder_rc5); | ||||
|     MU_RUN_TEST(test_decoder_rc5); | ||||
|  | ||||
| @ -178,7 +178,7 @@ const IrdaMessage test_decoder_nec_expected2[] = { | ||||
|     {IrdaProtocolNEC,     0x00,      0x0A,   true}, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_nec_all[] = { | ||||
| const IrdaMessage test_nec[] = { | ||||
|     {IrdaProtocolNEC,     0x00,      0x00,  false}, | ||||
|     {IrdaProtocolNEC,     0x01,      0x00,  false}, | ||||
|     {IrdaProtocolNEC,     0x01,      0x80,  false}, | ||||
|  | ||||
| @ -223,7 +223,7 @@ const IrdaMessage test_decoder_necext_expected1[] = { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const IrdaMessage test_necext_all[] = { | ||||
| const IrdaMessage test_necext[] = { | ||||
|     {IrdaProtocolNECext,     0x0000,      0x00,  false}, | ||||
|     {IrdaProtocolNECext,     0x0001,      0x00,  false}, | ||||
|     {IrdaProtocolNECext,     0x0001,      0x80,  false}, | ||||
|  | ||||
| @ -128,7 +128,7 @@ const IrdaMessage test_decoder_rc5_expected_all_repeats[] = { | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| const IrdaMessage test_rc5_all[] = { | ||||
| const IrdaMessage test_rc5[] = { | ||||
|     {IrdaProtocolRC5,     0x1F,      0x3F,  false}, | ||||
|     {IrdaProtocolRC5,     0x00,      0x00,  false}, | ||||
|     {IrdaProtocolRC5,     0x10,      0x01,  false}, | ||||
|  | ||||
| @ -65,7 +65,7 @@ const uint32_t test_encoder_rc6_expected1[] = { | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| const IrdaMessage test_rc6_all[] = { | ||||
| const IrdaMessage test_rc6[] = { | ||||
|     {IrdaProtocolRC6,     0x00,      0x00,  false},     // t 0 | ||||
|     {IrdaProtocolRC6,     0x80,      0x00,  false},     // t 1 | ||||
|     {IrdaProtocolRC6,     0x80,      0x01,  false},     // t 0 | ||||
|  | ||||
| @ -221,7 +221,7 @@ const IrdaMessage test_decoder_samsung32_expected1[] = { | ||||
|     {IrdaProtocolSamsung32, 0x0E, 0x01, false}, {IrdaProtocolSamsung32, 0x0E, 0x01, true}, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_samsung32_all[] = { | ||||
| const IrdaMessage test_samsung32[] = { | ||||
|     {IrdaProtocolSamsung32,     0x00,      0x00,  false}, | ||||
|     {IrdaProtocolSamsung32,     0x01,      0x00,  false}, | ||||
|     {IrdaProtocolSamsung32,     0x01,      0x80,  false}, | ||||
|  | ||||
| @ -0,0 +1,485 @@ | ||||
| const uint32_t test_decoder_sirc_input1[] = {  /* 121 timings */ | ||||
| 1000000,    2420, 608, 1194, 608, 596, 604, 1198, 603, 591, 610, 1192, 609, 596, 605, 599, 601, 593, 607, 597, 604, 590, 610, 594, 606, 1196, | ||||
|     25957,  2426, 603, 1199, 603, 591, 610, 1192, 610, 594, 606, 1196, 606, 599, 603, 591, 609, 595, 606, 598, 602, 592, 609, 596, 605, 1197, | ||||
|     25960,  2423, 606, 1196, 606, 599, 602, 1200, 602, 592, 609, 1193, 609, 596, 606, 599, 602, 592, 609, 595, 605, 600, 601, 593, 608, 1194, | ||||
| 1000000,    2422, 607, 1195, 607, 598, 603, 1199, 604, 590, 610, 1192, 610, 594, 606, 598, 603, 591, 609, 595, 605, 600, 601, 593, 607, 1195, | ||||
|     25955,  2418, 610, 1192, 610, 594, 606, 1196, 606, 599, 602, 1200, 602, 592, 608, 596, 604, 590, 611, 594, 607, 597, 603, 591, 609, 1193, | ||||
|     25959,  2424, 604, 1198, 604, 590, 610, 1192, 610, 594, 606, 1196, 605, 600, 601, 593, 608, 597, 603, 591, 610, 595, 606, 598, 602, 1200, | ||||
| 1000000,    2424, 605, 599, 601, 593, 607, 597, 603, 591, 610, 594, 606, 1196, 606, 1196, 605, 600, 601, 593, 608, 597, 604, 590, 611, 1191, | ||||
|     26586,  2425, 604, 590, 611, 593, 607, 598, 603, 591, 610, 595, 606, 1196, 606, 1196, 606, 599, 602, 592, 608, 596, 604, 590, 611, 1191, | ||||
|     26586,  2424, 604, 590, 611, 593, 607, 598, 603, 591, 609, 595, 605, 1197, 605, 1197, 604, 590, 611, 593, 607, 597, 603, 591, 610, 1192, | ||||
| 1000000,    2424, 604, 1198, 604, 590, 611, 1191, 610, 594, 606, 598, 603, 1199, 602, 1200, 602, 592, 609, 595, 605, 600, 601, 593, 608, 1194, | ||||
|     25386,  2419, 610, 1192, 610, 594, 606, 1196, 607, 597, 603, 591, 610, 1192, 609, 1193, 610, 594, 606, 598, 602, 592, 609, 595, 605, 1197, | ||||
|     25385,  2421, 608, 1194, 608, 596, 605, 1197, 605, 599, 601, 593, 608, 1194, 608, 1194, 608, 596, 605, 589, 611, 594, 607, 597, 604, 1198, | ||||
| 1000000,    2426, 603, 1199, 602, 1200, 602, 1200, 602, 592, 608, 1194, 608, 596, 604, 590, 611, 594, 607, 597, 603, 591, 610, 594, 606, 1196, 605, 600, 601, 593, 608, 596, 604, 590, 610, 594, 607, 1195, 606, 598, 603, 591, | ||||
|     15078,  2419, 610, 1192, 610, 1192, 610, 1192, 610, 594, 606, 1196, 605, 600, 601, 593, 608, 597, 604, 590, 610, 595, 606, 598, 602, 1200, 602, 592, 608, 597, 604, 590, 611, 594, 607, 597, 603, 1199, 603, 591, 609, 595, | ||||
|     15075,  2422, 607, 1195, 607, 1195, 607, 1195, 607, 597, 604, 1198, 603, 591, 610, 594, 606, 598, 603, 591, 609, 595, 605, 600, 601, 1191, 611, 594, 607, 597, 603, 591, 610, 594, 606, 598, 602, 1200, 602, 592, 608, 596, | ||||
| 1000000,    2422, 607, 1195, 606, 599, 602, 592, 608, 596, 604, 590, 610, 1192, 610, 594, 606, 599, 602, 592, 608, 596, 604, 590, 610, 1192, | ||||
|     26585,  2426, 602, 1200, 602, 592, 608, 596, 604, 590, 611, 594, 607, 1195, 607, 598, 603, 591, 610, 594, 606, 598, 603, 591, 609, 1193, | ||||
|     26586,  2425, 604, 1198, 603, 591, 610, 594, 606, 598, 602, 592, 609, 1193, 608, 597, 605, 600, 601, 593, 607, 597, 604, 590, 610, 1192, | ||||
| 1000000,    2418, 610, 594, 606, 598, 603, 1199, 603, 1199, 603, 1199, 603, 1199, 603, 1199, 603, 591, 610, 1192, 610, 594, 606, 1196, 606, 1196, 606, 1196, 606, 598, 602, 592, 609, 1193, 609, 1193, 609, 1193, 609, 595, 605, 599, | ||||
|     11557,  2418, 611, 594, 607, 598, 603, 1199, 603, 1199, 603, 1199, 602, 1200, 602, 1200, 601, 593, 608, 1194, 607, 597, 604, 1198, 603, 1199, 603, 1199, 602, 592, 608, 596, 604, 1198, 603, 1199, 603, 1199, 603, 591, 609, 595, | ||||
|     11561,  2424, 604, 590, 610, 594, 607, 1195, 606, 1196, 606, 1196, 606, 1196, 606, 1196, 605, 600, 601, 1191, 611, 594, 607, 1195, 607, 1195, 607, 1195, 607, 597, 603, 591, 610, 1192, 610, 1192, 610, 1192, 610, 594, 606, 598, | ||||
| 1000000,    2424, 604, 590, 611, 594, 607, 1195, 607, 1195, 607, 1195, 607, 1195, 607, 1195, 606, 598, 603, 1199, 602, 592, 609, 1193, 608, 1194, 608, 1194, 608, 596, 604, 590, 611, 1191, 611, 1191, 611, 1191, 611, 594, 607, 598, | ||||
|     11559,  2427, 602, 592, 608, 596, 605, 1197, 604, 1198, 604, 1198, 604, 1198, 604, 1198, 604, 590, 610, 1192, 610, 595, 606, 1196, 606, 1196, 606, 1196, 606, 599, 603, 591, 609, 1193, 609, 1193, 609, 1193, 608, 597, 605, 589, | ||||
|     11567,  2418, 610, 595, 607, 597, 603, 1199, 603, 1199, 602, 1200, 602, 1200, 601, 1201, 601, 593, 608, 1194, 607, 598, 603, 1199, 603, 1199, 603, 1199, 603, 591, 609, 595, 605, 1197, 605, 1197, 605, 1197, 604, 590, 611, 594, | ||||
| 1000000,    2421, 608, 597, 604, 590, 610, 1192, 610, 1192, 609, 1193, 609, 1193, 609, 1193, 608, 596, 605, 1197, 604, 590, 610, 1192, 611, 1191, 610, 1192, 610, 594, 606, 598, 603, 1199, 603, 1199, 602, 1200, 602, 592, 608, 596, | ||||
|     11561,  2424, 604, 590, 610, 594, 606, 1196, 606, 1196, 606, 1196, 606, 1196, 605, 1197, 605, 600, 601, 1201, 601, 593, 607, 1195, 607, 1195, 606, 1196, 606, 598, 602, 592, 608, 1194, 608, 1194, 607, 1195, 607, 597, 603, 591, | ||||
|     11564,  2421, 607, 597, 604, 590, 610, 1192, 610, 1192, 610, 1192, 610, 1192, 610, 1192, 609, 595, 606, 1196, 606, 598, 602, 1200, 601, 1201, 601, 1201, 601, 593, 607, 598, 603, 1199, 603, 1199, 602, 1200, 602, 592, 608, 596, | ||||
| 1000000,    2420, 609, 595, 606, 598, 602, 1200, 602, 1200, 602, 1200, 602, 1200, 602, 1200, 602, 592, 608, 1194, 608, 596, 604, 1198, 603, 1199, 603, 1199, 603, 591, 610, 594, 606, 1196, 606, 1196, 606, 1196, 606, 598, 602, 592, | ||||
|     11565,  2420, 609, 595, 605, 600, 601, 1201, 601, 1201, 601, 1201, 601, 1201, 601, 1201, 601, 593, 607, 1195, 607, 597, 603, 1199, 603, 1199, 603, 1199, 603, 591, 609, 595, 605, 1197, 605, 1197, 605, 1197, 605, 599, 601, 593, | ||||
|     11563,  2422, 607, 597, 603, 591, 609, 1193, 609, 1193, 608, 1194, 608, 1194, 608, 1194, 582, 623, 603, 1199, 603, 591, 610, 1202, 599, 1203, 599, 1203, 599, 595, 581, 623, 577, 1225, 601, 1201, 601, 1201, 601, 593, 582, 623, | ||||
| 1000000,    2425, 602, 1200, 602, 1200, 602, 592, 608, 1194, 608, 1194, 607, 1195, 607, 1195, 607, 597, 603, 1199, 602, 592, 609, 1193, 608, 1194, 608, 1194, 607, 597, 603, 601, 575, 1227, 599, 1203, 599, 1203, 573, 621, 580, 625, | ||||
|     10931,  2426, 578, 1224, 578, 1224, 578, 616, 585, 1217, 585, 1217, 585, 1217, 585, 1217, 585, 620, 580, 1222, 580, 624, 577, 1225, 577, 1225, 577, 1225, 577, 617, 583, 622, 580, 1222, 580, 1222, 579, 1223, 579, 625, 576, 618, | ||||
|     10936,  2421, 583, 1219, 583, 1219, 582, 622, 579, 1223, 578, 1224, 578, 1224, 578, 1224, 578, 616, 584, 1218, 584, 621, 580, 1222, 579, 1223, 579, 1223, 579, 625, 576, 618, 583, 1219, 582, 1220, 582, 1220, 582, 622, 578, 616, | ||||
| 1000000,    2419, 584, 620, 580, 1222, 580, 624, 576, 1226, 576, 1226, 576, 1226, 576, 1226, 576, 618, 582, 1220, 582, 622, 579, 1223, 578, 1224, 579, 1223, 578, 616, 585, 619, 581, 1221, 581, 1221, 581, 1221, 580, 624, 576, 618, | ||||
|     11563,  2422, 582, 622, 579, 1223, 578, 616, 585, 1217, 585, 1217, 584, 1218, 584, 1218, 583, 622, 579, 1223, 579, 625, 575, 1216, 585, 1217, 585, 1217, 585, 619, 581, 623, 577, 1225, 577, 1225, 577, 1225, 576, 618, 583, 621, | ||||
|     11558,  2427, 577, 617, 584, 1218, 583, 621, 579, 1223, 579, 1223, 578, 1224, 578, 1224, 578, 647, 553, 1249, 553, 651, 549, 1253, 548, 1254, 549, 1253, 549, 645, 555, 649, 551, 1251, 551, 1251, 551, 1251, 550, 654, 546, 648, | ||||
| 1000000,    2456, 548, 646, 554, 650, 551, 653, 547, 1255, 547, 1255, 547, 1255, 547, 1255, 547, 647, 554, 1248, 554, 650, 551, 1251, 551, 1251, 551, 1251, 551, 653, 547, 647, 554, 1248, 554, 1248, 554, 1248, 553, 651, 550, 644, | ||||
|     12112,  2449, 555, 649, 551, 654, 547, 647, 554, 1248, 554, 1248, 553, 1249, 553, 1249, 553, 651, 549, 1253, 549, 645, 555, 1247, 555, 1247, 555, 1247, 554, 650, 550, 655, 547, 1244, 557, 1224, 578, 1224, 579, 615, 585, 619, | ||||
|     12139,  2423, 580, 624, 576, 618, 582, 622, 578, 1224, 578, 1224, 578, 1224, 578, 1224, 578, 616, 584, 1218, 584, 620, 580, 1222, 580, 1222, 581, 1221, 580, 624, 577, 617, 584, 1218, 584, 1218, 584, 1218, 584, 620, 581, 623, | ||||
| 1000000,    2422, 582, 1220, 581, 623, 578, 616, 584, 1218, 584, 1218, 584, 1218, 584, 1218, 584, 620, 580, 1222, 580, 624, 576, 1226, 577, 1225, 576, 1226, 576, 618, 583, 622, 579, 1223, 578, 1224, 577, 1225, 577, 617, 585, 619, | ||||
|     11559,  2426, 577, 1225, 577, 617, 583, 621, 579, 1223, 579, 1223, 578, 1224, 578, 1224, 578, 616, 584, 1218, 584, 620, 580, 1222, 580, 1222, 579, 1223, 579, 625, 575, 650, 550, 1252, 550, 1252, 549, 1253, 549, 645, 556, 648, | ||||
|     11531,  2453, 549, 1253, 548, 646, 555, 649, 551, 1251, 551, 1251, 550, 1252, 549, 1253, 549, 645, 556, 1246, 555, 649, 552, 1219, 582, 1220, 582, 1220, 582, 622, 578, 616, 585, 1217, 584, 1218, 584, 1218, 584, 620, 581, 623, | ||||
| 1000000,    2428, 576, 618, 582, 1220, 583, 622, 579, 1223, 578, 1224, 578, 1224, 578, 1224, 578, 616, 585, 1217, 585, 619, 582, 1220, 582, 1220, 582, 1220, 582, 622, 578, 616, 585, 1217, 585, 1217, 584, 1218, 584, 620, 581, 623, | ||||
|     11557,  2427, 577, 617, 584, 1218, 583, 621, 580, 1222, 580, 1222, 580, 1222, 580, 1222, 580, 624, 576, 1246, 555, 649, 552, 1250, 552, 1250, 551, 1251, 551, 653, 548, 646, 554, 1248, 555, 1247, 555, 1247, 555, 649, 551, 653, | ||||
|     11528,  2447, 556, 648, 552, 1250, 552, 653, 548, 1254, 548, 1254, 547, 1245, 557, 1245, 557, 647, 553, 1249, 552, 652, 549, 1253, 548, 1254, 548, 1254, 548, 646, 555, 649, 551, 1251, 551, 1251, 551, 1251, 551, 643, 557, 647, | ||||
| 1000000,    2418, 610, 594, 606, 598, 603, 1199, 603, 1199, 602, 1200, 602, 1200, 602, 1200, 602, 592, 608, 1194, 608, 596, 604, 1198, 604, 1198, 604, 1198, 603, 591, 610, 594, 606, 1196, 606, 1196, 605, 1197, 605, 599, 601, 593, | ||||
|     11563,  2422, 606, 598, 602, 592, 608, 1194, 608, 1194, 608, 1194, 607, 1195, 607, 1195, 607, 597, 603, 1199, 603, 591, 609, 1193, 609, 1193, 609, 1193, 608, 596, 605, 599, 601, 1201, 600, 1191, 611, 1191, 611, 593, 606, 598, | ||||
|     11558,  2427, 601, 593, 607, 597, 603, 1199, 603, 1199, 603, 1199, 602, 1200, 602, 1200, 601, 593, 607, 1195, 607, 597, 603, 1199, 603, 1199, 602, 1200, 602, 592, 608, 596, 604, 1198, 604, 1198, 604, 1198, 603, 591, 609, 595, | ||||
| 1000000,    2424, 605, 1197, 604, 600, 600, 1191, 610, 1192, 610, 1192, 610, 1192, 609, 1193, 609, 595, 605, 1197, 606, 598, 602, 1200, 602, 1200, 601, 1201, 601, 593, 607, 597, 603, 1199, 603, 1199, 603, 1199, 603, 591, 609, 595, | ||||
|     10937,  2420, 609, 1193, 609, 595, 605, 1197, 605, 1197, 605, 1197, 605, 1197, 605, 1197, 605, 600, 601, 1201, 601, 593, 608, 1194, 608, 1194, 608, 1194, 609, 595, 605, 599, 601, 1201, 601, 1201, 601, 1201, 601, 593, 608, 596, | ||||
|     10936,  2420, 608, 1194, 608, 596, 604, 1198, 605, 1197, 605, 1197, 605, 1197, 605, 1197, 605, 600, 601, 1201, 601, 593, 607, 1195, 608, 1194, 608, 1194, 608, 596, 604, 600, 600, 1202, 601, 1201, 601, 1201, 601, 593, 607, 597, | ||||
| 1000000,    2420, 609, 1193, 608, 1194, 608, 596, 579, 625, 600, 1202, 600, 1202, 600, 1202, 600, 594, 607, 1195, 607, 597, 603, 1199, 603, 1199, 602, 1200, 602, 592, 609, 595, 605, 1197, 605, 1197, 605, 1197, 604, 600, 600, 594, | ||||
|     11561,  2423, 605, 1197, 605, 1197, 605, 599, 601, 593, 607, 1195, 607, 1195, 607, 1195, 607, 597, 604, 1198, 603, 591, 610, 1192, 610, 1192, 610, 1192, 610, 594, 607, 597, 603, 1199, 603, 1199, 603, 1199, 603, 591, 610, 594, | ||||
|     11563,  2421, 608, 1194, 608, 1194, 608, 596, 605, 599, 601, 1201, 602, 1200, 602, 1200, 602, 592, 609, 1193, 609, 595, 605, 1197, 606, 1196, 606, 1196, 606, 598, 602, 592, 609, 1193, 609, 1193, 610, 1192, 610, 594, 607, 597, | ||||
| 1000000,    2428, 576, 1226, 600, 1192, 610, 594, 606, 598, 602, 1200, 602, 592, 609, 595, 605, 600, 601, 593, 607, 597, 603, 591, 609, 1193, | ||||
|     25955,  2427, 601, 1190, 610, 1192, 610, 594, 606, 598, 602, 1200, 601, 593, 607, 597, 603, 591, 610, 594, 606, 598, 602, 592, 608, 1194, | ||||
|     25957,  2425, 604, 1198, 603, 1199, 603, 591, 609, 595, 605, 1197, 605, 599, 601, 593, 607, 598, 603, 591, 610, 594, 606, 598, 602, 1200, | ||||
|     25952,  2420, 608, 1194, 608, 1194, 608, 596, 604, 601, 600, 1191, 610, 594, 606, 598, 603, 591, 609, 595, 605, 599, 601, 593, 608, 1194, | ||||
| 1000000,    2421, 607, 597, 603, 1199, 603, 591, 610, 594, 606, 1196, 605, 600, 576, 618, 583, 621, 604, 601, 575, 619, 606, 598, 603, 1199, | ||||
|     26576,  2424, 605, 600, 576, 1226, 601, 593, 608, 596, 604, 1198, 604, 600, 600, 594, 607, 597, 603, 591, 609, 595, 606, 598, 602, 1200, | ||||
|     26579,  2420, 583, 621, 605, 1197, 604, 600, 601, 593, 607, 1195, 607, 597, 604, 590, 610, 594, 607, 597, 603, 591, 610, 594, 606, 1196, | ||||
|     26584,  2426, 603, 591, 609, 1193, 609, 595, 605, 599, 601, 1201, 601, 593, 607, 597, 603, 591, 610, 594, 606, 598, 603, 591, 609, 1193, | ||||
| 1000000,    2418, 610, 594, 606, 598, 602, 592, 609, 595, 605, 1197, 605, 1197, 605, 600, 602, 592, 608, 1194, 608, 596, 605, 1197, 605, 1197, 604, 1198, 604, 600, 601, 593, 607, 1195, 607, 1195, 607, 1195, 607, 597, 603, 591, | ||||
|     13368,  2419, 610, 594, 606, 598, 602, 592, 608, 596, 605, 1197, 604, 1198, 604, 600, 601, 593, 607, 1195, 607, 597, 603, 1199, 603, 1199, 603, 1199, 603, 591, 609, 595, 605, 1197, 605, 1197, 605, 1197, 604, 601, 600, 594, | ||||
|     13362,  2425, 604, 601, 600, 594, 607, 597, 603, 591, 610, 1192, 610, 1192, 610, 594, 606, 598, 603, 1199, 602, 592, 609, 1193, 609, 1193, 608, 1194, 608, 596, 604, 601, 600, 1191, 610, 1192, 610, 1192, 610, 594, 607, 597, | ||||
| 1000000,    2427, 601, 1201, 601, 593, 582, 623, 603, 1199, 578, 1224, 603, 1199, 577, 617, 584, 620, 605, 1197, 605, 600, 601, 1201, 601, 1201, 601, 1201, 601, 593, 582, 622, 603, 1199, 603, 1199, 578, 1224, 603, 591, 610, 594, | ||||
|     12139,  2423, 605, 1197, 605, 600, 601, 593, 608, 1194, 608, 1194, 607, 1195, 607, 597, 604, 600, 601, 1201, 601, 593, 607, 1195, 608, 1194, 608, 1194, 583, 622, 604, 601, 600, 1202, 575, 1227, 601, 1201, 576, 618, 607, 597, | ||||
|     12137,  2424, 604, 1198, 604, 590, 610, 594, 607, 1195, 607, 1195, 607, 1195, 607, 597, 604, 601, 600, 1202, 600, 594, 607, 1195, 606, 1196, 581, 1221, 607, 597, 603, 591, 610, 1202, 600, 1202, 576, 1226, 602, 592, 609, 595, | ||||
| 1000000,    2422, 607, 1195, 607, 597, 603, 591, 585, 619, 606, 1196, 606, 1196, 581, 624, 577, 617, 609, 1193, 584, 621, 605, 1197, 604, 1198, 604, 1198, 604, 590, 610, 595, 581, 1221, 605, 1197, 605, 1197, 605, 599, 601, 593, | ||||
|     12763,  2427, 603, 1199, 603, 591, 609, 595, 605, 599, 577, 1225, 602, 1200, 601, 593, 583, 622, 604, 1198, 604, 590, 610, 1192, 610, 1192, 610, 1192, 610, 594, 606, 598, 603, 1199, 603, 1199, 603, 1199, 603, 591, 610, 594, | ||||
|     12763,  2427, 602, 1200, 601, 593, 608, 596, 604, 600, 600, 1202, 601, 1201, 601, 593, 607, 597, 604, 1198, 604, 590, 610, 1192, 610, 1192, 611, 1191, 610, 594, 607, 598, 578, 1224, 603, 1199, 603, 1199, 603, 591, 610, 594, | ||||
| 1000000,    2422, 607, 597, 603, 591, 610, 1192, 610, 594, 606, 1196, 580, 1222, 605, 600, 601, 593, 583, 1219, 608, 596, 579, 1223, 604, 1198, 604, 1198, 604, 590, 610, 594, 606, 1196, 606, 1196, 606, 1196, 606, 599, 602, 592, | ||||
|     12765,  2425, 603, 591, 610, 594, 606, 1196, 606, 598, 602, 1200, 601, 1201, 601, 593, 582, 623, 604, 1198, 604, 590, 610, 1192, 610, 1192, 609, 1193, 609, 596, 605, 599, 601, 1201, 601, 1201, 601, 1201, 600, 594, 607, 597, | ||||
|     12758,  2421, 607, 597, 603, 591, 609, 1193, 609, 595, 605, 1197, 605, 1197, 605, 599, 601, 593, 607, 1195, 607, 597, 603, 1199, 603, 1199, 603, 1199, 603, 591, 609, 595, 580, 1222, 605, 1197, 605, 1197, 605, 600, 600, 594, | ||||
| 1000000,    2422, 580, 625, 601, 1201, 601, 593, 608, 596, 604, 1198, 604, 1198, 604, 600, 600, 594, 607, 1195, 607, 597, 603, 1199, 603, 1199, 603, 1199, 578, 616, 609, 595, 606, 1196, 606, 1196, 606, 1196, 581, 624, 602, 592, | ||||
|     12766,  2424, 605, 599, 601, 1201, 601, 593, 608, 596, 604, 1198, 604, 1198, 579, 625, 601, 593, 608, 1194, 608, 596, 604, 1198, 580, 1222, 605, 1197, 605, 599, 602, 592, 608, 1194, 609, 1193, 609, 1193, 609, 596, 605, 599, | ||||
|     12759,  2420, 608, 597, 604, 1198, 604, 590, 610, 594, 606, 1196, 606, 1196, 606, 598, 602, 592, 608, 1194, 607, 597, 604, 1198, 603, 1199, 603, 1199, 602, 592, 609, 595, 605, 1197, 605, 1197, 605, 1197, 604, 600, 601, 593, | ||||
| 1000000,    2429, 600, 1202, 575, 1227, 599, 595, 606, 598, 602, 1200, 602, 1200, 602, 592, 609, 595, 605, 1197, 605, 599, 601, 1201, 601, 1201, 601, 1201, 601, 593, 607, 597, 603, 1199, 603, 1199, 603, 1199, 603, 591, 609, 595, | ||||
|     12136,  2425, 603, 1199, 603, 1199, 603, 591, 609, 595, 605, 1197, 605, 1197, 605, 599, 601, 593, 608, 1194, 608, 596, 604, 1198, 604, 1198, 603, 1199, 603, 591, 609, 595, 606, 1196, 606, 1196, 605, 1197, 605, 599, 601, 593, | ||||
|     12137,  2424, 604, 1198, 603, 1199, 603, 591, 609, 595, 606, 1196, 605, 1197, 605, 599, 601, 593, 607, 1195, 607, 597, 603, 1199, 603, 1199, 603, 1199, 602, 592, 609, 595, 605, 1197, 605, 1197, 604, 1198, 604, 600, 600, 594, | ||||
| 1000000,    2429, 574, 1228, 573, 1229, 573, 1229, 573, 1229, 573, 621, 581, 624, 577, 617, 583, 622, 580, 1222, 579, 625, 576, 1226, 577, 1225, 576, 1226, 577, 617, 584, 620, 581, 1221, 580, 1222, 579, 1223, 579, 625, 577, 617, | ||||
|     12142,  2419, 584, 1218, 584, 1218, 583, 1219, 583, 1219, 582, 623, 578, 616, 585, 619, 581, 623, 578, 1224, 577, 617, 584, 1218, 584, 1218, 584, 1218, 584, 620, 580, 624, 576, 1226, 575, 1227, 500, 1353, 523, 620, 582, 622, /* failed, noise pollution 500, 1353 timings */ | ||||
|     12134,  2427, 576, 1226, 576, 1226, 576, 1226, 576, 1226, 576, 618, 583, 622, 579, 625, 576, 618, 583, 1219, 583, 673, 527, 1223, 579, 1223, 579, 1223, 579, 625, 576, 618, 582, 1220, 582, 1220, 582, 1220, 582, 622, 578, 616, | ||||
|     12140,  2421, 582, 1220, 581, 1221, 581, 1221, 580, 1222, 580, 624, 576, 618, 582, 622, 578, 616, 585, 1217, 584, 620, 581, 1221, 580, 1222, 580, 1222, 580, 624, 576, 618, 582, 1220, 582, 1220, 582, 1220, 582, 623, 578, 616, | ||||
| 1000000,    2419, 584, 672, 528, 625, 577, 617, 583, 1219, 582, 1220, 581, 1221, 582, 622, 578, 616, 585, 1217, 583, 621, 580, 1222, 580, 1222, 580, 1222, 580, 624, 576, 618, 583, 1219, 583, 1219, 582, 1220, 583, 621, 580, 624, | ||||
|     12732,  2427, 577, 617, 584, 672, 528, 676, 524, 1226, 576, 1226, 576, 1226, 576, 618, 583, 621, 579, 1223, 579, 625, 575, 1227, 575, 1227, 575, 1227, 575, 671, 529, 675, 526, 1224, 578, 1224, 578, 1224, 578, 616, 585, 619, | ||||
|     12738,  2421, 583, 621, 580, 624, 576, 618, 582, 1220, 582, 1220, 583, 1219, 583, 621, 579, 625, 576, 1226, 576, 670, 530, 1220, 582, 1220, 582, 1220, 581, 624, 577, 617, 584, 1218, 583, 1219, 583, 1219, 583, 622, 580, 624, | ||||
| 1000000,    2430, 599, 1192, 609, 595, 605, 1197, 580, 624, 601, 1201, 601, 593, 607, 597, 603, 591, 610, 594, 606, 598, 602, 592, 608, 1194, | ||||
|     25958,  2423, 605, 1197, 604, 600, 575, 1227, 600, 594, 606, 1196, 606, 598, 602, 592, 608, 596, 604, 601, 600, 594, 607, 597, 603, 1199, | ||||
|     25949,  2422, 607, 1195, 606, 598, 603, 1199, 602, 592, 608, 1194, 608, 596, 604, 600, 601, 593, 607, 597, 604, 600, 600, 594, 607, 1195, | ||||
|     25958,  2423, 606, 1196, 606, 598, 602, 1200, 602, 592, 608, 1194, 608, 596, 605, 600, 601, 593, 607, 597, 603, 591, 610, 594, 606, 1196, | ||||
|     25957,  2425, 604, 1198, 603, 591, 610, 1192, 610, 594, 606, 1196, 606, 598, 602, 592, 609, 595, 605, 599, 602, 592, 608, 596, 604, 1198, | ||||
|     25956,  2426, 604, 1198, 603, 591, 610, 1192, 610, 594, 606, 1196, 606, 598, 602, 592, 608, 597, 605, 599, 601, 593, 607, 597, 603, 1199, | ||||
|     25952,  2420, 609, 1193, 608, 596, 604, 1198, 604, 590, 610, 1192, 610, 594, 606, 598, 602, 592, 608, 596, 605, 599, 601, 593, 607, 1195, | ||||
| 1000000,    2422, 583, 1219, 608, 596, 605, 1197, 580, 624, 601, 1201, 601, 593, 608, 596, 604, 600, 601, 593, 607, 597, 604, 590, 610, 1202, | ||||
|     25955,  2427, 603, 1199, 603, 591, 609, 1193, 610, 594, 606, 1196, 607, 597, 603, 591, 609, 595, 606, 598, 602, 592, 609, 595, 605, 1197, | ||||
|     25957,  2425, 604, 1198, 604, 590, 610, 1192, 610, 594, 581, 1221, 606, 598, 602, 592, 608, 596, 605, 599, 601, 593, 607, 597, 604, 1198, | ||||
|     25954,  2418, 610, 1192, 610, 594, 606, 1196, 605, 599, 601, 1201, 601, 593, 608, 596, 604, 590, 610, 594, 606, 598, 603, 591, 609, 1193, | ||||
|     25958,  2424, 604, 1198, 604, 590, 610, 1192, 610, 594, 606, 1196, 606, 598, 602, 592, 609, 595, 605, 600, 601, 593, 607, 597, 603, 1199, | ||||
|     25953,  2419, 610, 1192, 609, 595, 605, 1197, 605, 600, 601, 1201, 600, 594, 607, 597, 603, 591, 609, 595, 605, 599, 601, 593, 608, 1194, | ||||
|     25954,  2418, 611, 1191, 610, 594, 606, 1196, 606, 598, 602, 1200, 602, 592, 608, 596, 604, 601, 600, 594, 607, 597, 603, 591, 609, 1193, | ||||
|     25958,  2424, 605, 1197, 605, 599, 601, 1201, 600, 594, 607, 1195, 607, 597, 603, 591, 609, 596, 605, 599, 602, 592, 608, 596, 605, 1197, | ||||
|     25954,  2428, 601, 1190, 610, 594, 607, 1195, 606, 598, 602, 1200, 602, 592, 608, 596, 604, 590, 610, 594, 606, 598, 603, 591, 609, 1193, | ||||
|     25960,  2422, 607, 1195, 606, 598, 602, 1200, 602, 592, 609, 1193, 609, 595, 605, 599, 601, 593, 607, 597, 604, 590, 610, 594, 606, 1196, | ||||
|     25957,  2424, 605, 1197, 604, 600, 600, 1202, 601, 593, 607, 1195, 607, 597, 603, 591, 610, 594, 606, 598, 603, 591, 609, 595, 606, 1196, | ||||
| 1000000,    2421, 608, 1194, 608, 596, 604, 1198, 604, 601, 575, 1227, 601, 593, 607, 598, 604, 600, 600, 594, 607, 598, 604, 601, 600, 1202, | ||||
|     25953,  2419, 609, 1193, 609, 596, 605, 1197, 605, 600, 601, 1201, 601, 593, 607, 597, 603, 591, 610, 594, 606, 599, 602, 592, 608, 1194, | ||||
|     25958,  2424, 605, 1197, 605, 600, 601, 1201, 600, 594, 607, 1195, 606, 598, 602, 592, 609, 595, 605, 600, 601, 593, 608, 596, 604, 1198, | ||||
| 1000000,    2423, 606, 1196, 606, 598, 602, 1200, 602, 592, 608, 1194, 608, 597, 604, 590, 610, 594, 607, 597, 603, 591, 610, 594, 606, 1196, | ||||
|     25958,  2424, 605, 1197, 604, 600, 601, 1201, 601, 593, 607, 1195, 607, 597, 604, 590, 610, 595, 607, 597, 603, 591, 610, 594, 607, 1195, | ||||
|     25961,  2421, 608, 1194, 608, 596, 605, 1197, 605, 600, 601, 1201, 601, 593, 607, 597, 603, 591, 610, 594, 606, 598, 602, 592, 609, 1193, | ||||
| 1000000,    2429, 601, 1201, 601, 593, 607, 1195, 608, 596, 604, 1198, 604, 600, 601, 593, 607, 597, 604, 590, 611, 593, 607, 597, 604, 1198, | ||||
|     25959,  2422, 607, 1195, 607, 597, 603, 1199, 603, 591, 609, 1193, 610, 594, 606, 598, 602, 592, 609, 595, 605, 599, 602, 592, 608, 1194, | ||||
|     25959,  2421, 608, 1194, 607, 597, 603, 1199, 603, 591, 610, 1192, 610, 594, 606, 598, 603, 591, 609, 595, 605, 599, 602, 592, 608, 1194, | ||||
|     25959,  2422, 608, 1194, 608, 596, 604, 1198, 604, 590, 611, 1201, 601, 593, 607, 597, 604, 590, 610, 594, 607, 597, 603, 591, 609, 1193, | ||||
|     25962,  2418, 610, 1192, 610, 594, 606, 1196, 607, 597, 603, 1199, 603, 591, 609, 595, 605, 599, 602, 592, 608, 596, 605, 599, 601, 1201, | ||||
| 
 | ||||
| 1000000,    2357, 610, 1183, 592, 1191, 614, 1179, 595, 1188, 618, 584, 613, 1180, 593, 588, 613, 1190, 612, 590, 605, 587, 613, 589, 605, 587, | ||||
|     25398,  2355, 623, 1180, 591, 1181, 627, 1186, 589, 1183, 618, 584, 616, 1187, 587, 584, 614, 1189, 615, 587, 605, 587, 615, 587, 609, 593, | ||||
| 1000000,    2383, 609, 1214, 600, 612, 580, 1213, 608, 614, 583, 1210, 605, 607, 586, 616, 611, 1192, 599, 613, 608, 614, 579, 613, 615, 607, | ||||
|     26670,  2387, 601, 1212, 600, 612, 589, 1214, 603, 609, 586, 1217, 595, 607, 594, 618, 606, 1187, 602, 610, 609, 613, 588, 614, 610, 612, | ||||
| 1000000,    2445, 582, 1221, 603, 548, 602, 1191, 609, 572, 602, 1191, 609, 542, 607, 544, 631, 1172, 603, 568, 606, 545, 605, 566, 608, 543, | ||||
|     26263,  2414, 611, 1192, 607, 544, 606, 1197, 602, 569, 606, 1197, 602, 539, 611, 540, 635, 1168, 606, 565, 610, 541, 608, 563, 587, 564, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_decoder_sirc_expected1[] = { | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x60, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x60, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x60, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x65, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x65, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x65, false}, | ||||
|     {IrdaProtocolSIRC20,       0x410,  0x17, false}, | ||||
|     {IrdaProtocolSIRC20,       0x410,  0x17, false}, | ||||
|     {IrdaProtocolSIRC20,       0x410,  0x17, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x21, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x21, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x21, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7B, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7B, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7B, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7A, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7A, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7A, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x78, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x78, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x78, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x79, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x79, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x79, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7A, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7A, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7A, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7C, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7D, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7D, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x7D, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x73, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x73, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x73, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x13, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x13, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x13, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x13, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x12, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x12, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x12, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x12, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x30, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x30, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x30, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x39, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x39, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x39, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x31, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x31, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x31, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x34, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x34, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x34, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x32, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x32, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x32, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x33, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x33, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x33, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x0F, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x0F, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x0F, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x38, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x38, false}, | ||||
|     {IrdaProtocolSIRC20,       0x73A,  0x38, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x10,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x01,   0x2F, false}, | ||||
|     {IrdaProtocolSIRC,         0x01,   0x2F, false}, | ||||
|     {IrdaProtocolSIRC,         0x01,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x01,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x01,   0x15, false}, | ||||
|     {IrdaProtocolSIRC,         0x01,   0x15, false}, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| //   command (0x55)    address (0x0A)       SIRC | ||||
| //   1 0 1 0 1 0 1     0 1 0 1 0 | ||||
| //   2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| // | ||||
| //   command (0x7F)    address (0x1F)       SIRC | ||||
| //   1 1 1 1 1 1 1     1 1 1 1 1 | ||||
| //   2400, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, | ||||
| // | ||||
| //   command (0x00)    address (0x00)       SIRC | ||||
| //   0 0 0 0 0 0 0     0 0 0 0 0 | ||||
| //   2400, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
| // | ||||
| //   command (0x53)    address (0x0D)       SIRC | ||||
| //   1 1 0 0 1 0 1     1 0 1 1 0 | ||||
| //   2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, | ||||
| 
 | ||||
| const uint32_t test_decoder_sirc_input2[] = { | ||||
| 1000000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 600, /* failed - should be skipped */ | ||||
| 
 | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, | ||||
| 1000000, 2400, 600, 1200, 600, 600, /* failed - should be skipped */ | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, | ||||
| 1000000, 2400, 600, 1200, 600,  /* failed - should be skipped */ | ||||
|          2400, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, | ||||
| 1000000, 2400, 600, 1200, 600, 600, /* failed - should be skipped */ | ||||
| 1000000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 
 | ||||
| 1000000, 2400, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
| 
 | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_decoder_sirc_expected2[] = { | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     /* failed - 13 data bits */ | ||||
| 
 | ||||
|     {IrdaProtocolSIRC, 0x1F, 0x7F, false}, | ||||
|     /* failed - 2 data bits */ | ||||
|     {IrdaProtocolSIRC, 0x1F, 0x7F, false}, | ||||
|     /* failed - sudden end */ | ||||
|     {IrdaProtocolSIRC, 0x1F, 0x7F, false}, | ||||
|     /* failed */ | ||||
|     {IrdaProtocolSIRC, 0x0A, 0x55, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC, 0x00, 0x00, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC, 0x0D, 0x53, false}, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| //   command (0x13)    address (0xFD)       SIRC15 | ||||
| //   1 1 0 0 1 0 0     1 0 1 1 1 1 1 1 | ||||
| //   2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, | ||||
| // | ||||
| //   command (0x53)    address (0x7D)       SIRC15 | ||||
| //   1 1 0 0 1 0 1     1 0 1 1 1 1 1 0 | ||||
| //   2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| // | ||||
| //   command (0x53)    address (0x0D)       SIRC15 | ||||
| //   1 1 0 0 1 0 1     1 0 1 1 0 0 0 0 | ||||
| //   2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
| 
 | ||||
| const uint32_t test_decoder_sirc_input3[] = { | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
|   10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
|   10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 
 | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
|   10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
|   10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
| 
 | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 600, 600, 600, | ||||
| 
 | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 
 | ||||
|   10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_decoder_sirc_expected3[] = { | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x0D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x0D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x0D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x0D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x0D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0xFD, 0x13, false}, | ||||
| }; | ||||
| 
 | ||||
| const uint32_t test_decoder_sirc_input4[] = { | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_decoder_sirc_expected4[] = { | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false},      //    {IrdaProtocolSIRC20, 0x15, 0x3ED3, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
| }; | ||||
| 
 | ||||
| const uint32_t test_decoder_sirc_input5[] = { | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 1000000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 
 | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 
 | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 
 | ||||
| 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| 10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_decoder_sirc_expected5[] = { | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC20, 0xFB5, 0x53, false}, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const IrdaMessage test_encoder_sirc_input1[] = { | ||||
|     {IrdaProtocolSIRC, 0xA, 0x55, false}, | ||||
| }; | ||||
| 
 | ||||
| const uint32_t test_encoder_sirc_expected1[] = { | ||||
| 10000, 2400, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 600, 600, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| const IrdaMessage test_encoder_sirc_input2[] = { | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, true}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, true}, | ||||
| }; | ||||
| 
 | ||||
| const uint32_t test_encoder_sirc_expected2[] = { | ||||
|     10000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, 600, /* 2 low levels in row */ | ||||
|     18000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, 600, /* 2 low levels in row */ | ||||
|     18000, 2400, 600, 1200, 600, 1200, 600, 600, 600, 600, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 600, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 1200, 600, 600, 600, | ||||
| }; | ||||
| 
 | ||||
| const IrdaMessage test_sirc[] = { | ||||
|     {IrdaProtocolSIRC20, 0x1FFF, 0x7F, false}, | ||||
|     {IrdaProtocolSIRC20, 0x1FFF, 0x7F, true}, | ||||
|     {IrdaProtocolSIRC20, 0x1FFF, 0x7F, true}, | ||||
|     {IrdaProtocolSIRC, 0x00, 0x00, false}, | ||||
|     {IrdaProtocolSIRC, 0x00, 0x00, true}, | ||||
|     {IrdaProtocolSIRC, 0x00, 0x00, true}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC, 0x1A, 0x22, false}, | ||||
|     {IrdaProtocolSIRC, 0x1A, 0x22, true}, | ||||
|     {IrdaProtocolSIRC, 0x1A, 0x22, true}, | ||||
|     {IrdaProtocolSIRC, 0x17, 0x0A, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, false}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, true}, | ||||
|     {IrdaProtocolSIRC15, 0x7D, 0x53, true}, | ||||
|     {IrdaProtocolSIRC15, 0x71, 0x0, false}, | ||||
|     {IrdaProtocolSIRC15, 0x15, 0x01, false}, | ||||
|     {IrdaProtocolSIRC15, 0x01, 0x15, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC20, 0xAA, 0x55, false}, | ||||
|     {IrdaProtocolSIRC20, 0x331, 0x71, false}, | ||||
| 
 | ||||
|     {IrdaProtocolSIRC, 0x00, 0x00, false}, | ||||
|     {IrdaProtocolSIRC, 0x1F, 0x7F, false}, | ||||
|     {IrdaProtocolSIRC15, 0x00, 0x00, false}, | ||||
|     {IrdaProtocolSIRC15, 0xFF, 0x7F, false}, | ||||
|     {IrdaProtocolSIRC20, 0x00, 0x00, false}, | ||||
|     {IrdaProtocolSIRC20, 0x1FFF, 0x7F, false}, | ||||
| }; | ||||
| 
 | ||||
| @ -4,7 +4,7 @@ include				$(PROJECT_ROOT)/assets/assets.mk | ||||
| 
 | ||||
| $(ASSETS): $(ASSETS_SOURCES) $(ASSETS_COMPILLER) | ||||
| 	@echo "\tASSETS\t" $@ | ||||
| 	@$(ASSETS_COMPILLER) icons -s $(ASSETS_SOURCE_DIR) -o $(ASSETS_COMPILED_DIR) | ||||
| 	@$(ASSETS_COMPILLER) icons "$(ASSETS_SOURCE_DIR)" "$(ASSETS_COMPILED_DIR)" | ||||
| 
 | ||||
| clean: | ||||
| 	@echo "\tCLEAN\t" | ||||
|  | ||||
| @ -56,7 +56,7 @@ _Min_Stack_Size = 0x400; /* required amount of stack */ | ||||
| MEMORY | ||||
| { | ||||
| FLASH (rx)                 : ORIGIN = 0x08000000, LENGTH = 32K | ||||
| RAM1 (xrw)                 : ORIGIN = 0x20000004, LENGTH = 0x2FFFC | ||||
| RAM1 (xrw)                 : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 | ||||
| RAM_SHARED (xrw)           : ORIGIN = 0x20030000, LENGTH = 10K | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										189
									
								
								bootloader/targets/f7/furi-hal/furi-hal-gpio.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								bootloader/targets/f7/furi-hal/furi-hal-gpio.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,189 @@ | ||||
| #include <furi-hal-gpio.h> | ||||
| #include <stddef.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #define GET_SYSCFG_EXTI_PORT(gpio)                \ | ||||
|     (((gpio) == (GPIOA)) ? LL_SYSCFG_EXTI_PORTA : \ | ||||
|      ((gpio) == (GPIOB)) ? LL_SYSCFG_EXTI_PORTB : \ | ||||
|      ((gpio) == (GPIOC)) ? LL_SYSCFG_EXTI_PORTC : \ | ||||
|      ((gpio) == (GPIOD)) ? LL_SYSCFG_EXTI_PORTD : \ | ||||
|      ((gpio) == (GPIOE)) ? LL_SYSCFG_EXTI_PORTE : \ | ||||
|                            LL_SYSCFG_EXTI_PORTH) | ||||
| 
 | ||||
| #define GPIO_PIN_MAP(pin, prefix)               \ | ||||
|     (((pin) == (LL_GPIO_PIN_0))  ? prefix##0 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_1))  ? prefix##1 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_2))  ? prefix##2 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_3))  ? prefix##3 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_4))  ? prefix##4 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_5))  ? prefix##5 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_6))  ? prefix##6 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_7))  ? prefix##7 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_8))  ? prefix##8 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_9))  ? prefix##9 :  \ | ||||
|      ((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \ | ||||
|      ((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \ | ||||
|      ((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \ | ||||
|      ((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \ | ||||
|      ((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \ | ||||
|                                    prefix##15) | ||||
| 
 | ||||
| #define GET_SYSCFG_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_SYSCFG_EXTI_LINE) | ||||
| #define GET_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_EXTI_LINE_) | ||||
| 
 | ||||
| static volatile GpioInterrupt gpio_interrupt[GPIO_NUMBER]; | ||||
| 
 | ||||
| static uint8_t hal_gpio_get_pin_num(const GpioPin* gpio) { | ||||
|     uint8_t pin_num = 0; | ||||
|     for(pin_num = 0; pin_num < GPIO_NUMBER; pin_num++) { | ||||
|         if(gpio->pin & (1 << pin_num)) break; | ||||
|     } | ||||
|     return pin_num; | ||||
| } | ||||
| 
 | ||||
| void hal_gpio_init_simple(const GpioPin* gpio, const GpioMode mode) { | ||||
|     hal_gpio_init(gpio, mode, GpioPullNo, GpioSpeedLow); | ||||
| } | ||||
| 
 | ||||
| void hal_gpio_init( | ||||
|     const GpioPin* gpio, | ||||
|     const GpioMode mode, | ||||
|     const GpioPull pull, | ||||
|     const GpioSpeed speed) { | ||||
|     // we cannot set alternate mode in this function
 | ||||
|     assert(mode != GpioModeAltFunctionPushPull); | ||||
|     assert(mode != GpioModeAltFunctionOpenDrain); | ||||
| 
 | ||||
|     hal_gpio_init_ex(gpio, mode, pull, speed, GpioAltFnUnused); | ||||
| } | ||||
| 
 | ||||
| void hal_gpio_init_ex( | ||||
|     const GpioPin* gpio, | ||||
|     const GpioMode mode, | ||||
|     const GpioPull pull, | ||||
|     const GpioSpeed speed, | ||||
|     const GpioAltFn alt_fn) { | ||||
|     uint32_t sys_exti_port = GET_SYSCFG_EXTI_PORT(gpio->port); | ||||
|     uint32_t sys_exti_line = GET_SYSCFG_EXTI_LINE(gpio->pin); | ||||
|     uint32_t exti_line = GET_EXTI_LINE(gpio->pin); | ||||
| 
 | ||||
|     // Configure gpio with interrupts disabled
 | ||||
|     __disable_irq(); | ||||
|     // Set gpio speed
 | ||||
|     if(speed == GpioSpeedLow) { | ||||
|         LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_LOW); | ||||
|     } else if(speed == GpioSpeedMedium) { | ||||
|         LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_MEDIUM); | ||||
|     } else if(speed == GpioSpeedHigh) { | ||||
|         LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_HIGH); | ||||
|     } else { | ||||
|         LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_VERY_HIGH); | ||||
|     } | ||||
|     // Set gpio pull mode
 | ||||
|     if(pull == GpioPullNo) { | ||||
|         LL_GPIO_SetPinPull(gpio->port, gpio->pin, LL_GPIO_PULL_NO); | ||||
|     } else if(pull == GpioPullUp) { | ||||
|         LL_GPIO_SetPinPull(gpio->port, gpio->pin, LL_GPIO_PULL_UP); | ||||
|     } else { | ||||
|         LL_GPIO_SetPinPull(gpio->port, gpio->pin, LL_GPIO_PULL_DOWN); | ||||
|     } | ||||
|     // Set gpio mode
 | ||||
|     if(mode >= GpioModeInterruptRise) { | ||||
|         // Set pin in interrupt mode
 | ||||
|         LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT); | ||||
|         LL_SYSCFG_SetEXTISource(sys_exti_port, sys_exti_line); | ||||
|         if(mode == GpioModeInterruptRise || mode == GpioModeInterruptRiseFall) { | ||||
|             LL_EXTI_EnableIT_0_31(exti_line); | ||||
|             LL_EXTI_EnableRisingTrig_0_31(exti_line); | ||||
|         } | ||||
|         if(mode == GpioModeInterruptFall || mode == GpioModeInterruptRiseFall) { | ||||
|             LL_EXTI_EnableIT_0_31(exti_line); | ||||
|             LL_EXTI_EnableFallingTrig_0_31(exti_line); | ||||
|         } | ||||
|         if(mode == GpioModeEventRise || mode == GpioModeInterruptRiseFall) { | ||||
|             LL_EXTI_EnableEvent_0_31(exti_line); | ||||
|             LL_EXTI_EnableRisingTrig_0_31(exti_line); | ||||
|         } | ||||
|         if(mode == GpioModeEventFall || mode == GpioModeInterruptRiseFall) { | ||||
|             LL_EXTI_EnableEvent_0_31(exti_line); | ||||
|             LL_EXTI_EnableFallingTrig_0_31(exti_line); | ||||
|         } | ||||
|     } else { | ||||
|         // Disable interrupt if it was set
 | ||||
|         if(LL_SYSCFG_GetEXTISource(sys_exti_line) == sys_exti_port && | ||||
|            LL_EXTI_IsEnabledIT_0_31(exti_line)) { | ||||
|             LL_EXTI_DisableIT_0_31(exti_line); | ||||
|             LL_EXTI_DisableRisingTrig_0_31(exti_line); | ||||
|             LL_EXTI_DisableFallingTrig_0_31(exti_line); | ||||
|         } | ||||
|         // Set not interrupt pin modes
 | ||||
|         if(mode == GpioModeInput) { | ||||
|             LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT); | ||||
|         } else if(mode == GpioModeOutputPushPull || mode == GpioModeAltFunctionPushPull) { | ||||
|             LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_OUTPUT); | ||||
|             LL_GPIO_SetPinOutputType(gpio->port, gpio->pin, LL_GPIO_OUTPUT_PUSHPULL); | ||||
|         } else if(mode == GpioModeOutputOpenDrain || mode == GpioModeAltFunctionOpenDrain) { | ||||
|             LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_OUTPUT); | ||||
|             LL_GPIO_SetPinOutputType(gpio->port, gpio->pin, LL_GPIO_OUTPUT_OPENDRAIN); | ||||
|         } else if(mode == GpioModeAnalog) { | ||||
|             LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_ANALOG); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(mode == GpioModeAltFunctionPushPull || mode == GpioModeAltFunctionOpenDrain) { | ||||
|         // enable alternate mode
 | ||||
|         LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_ALTERNATE); | ||||
| 
 | ||||
|         // set alternate function
 | ||||
|         if(hal_gpio_get_pin_num(gpio) < 8) { | ||||
|             LL_GPIO_SetAFPin_0_7(gpio->port, gpio->pin, alt_fn); | ||||
|         } else { | ||||
|             LL_GPIO_SetAFPin_8_15(gpio->port, gpio->pin, alt_fn); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     __enable_irq(); | ||||
| } | ||||
| 
 | ||||
| void hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, void* ctx) { | ||||
|     assert(gpio); | ||||
|     assert(cb); | ||||
| 
 | ||||
|     __disable_irq(); | ||||
|     uint8_t pin_num = hal_gpio_get_pin_num(gpio); | ||||
|     gpio_interrupt[pin_num].callback = cb; | ||||
|     gpio_interrupt[pin_num].context = ctx; | ||||
|     gpio_interrupt[pin_num].ready = true; | ||||
|     __enable_irq(); | ||||
| } | ||||
| 
 | ||||
| void hal_gpio_enable_int_callback(const GpioPin* gpio) { | ||||
|     assert(gpio); | ||||
| 
 | ||||
|     __disable_irq(); | ||||
|     uint8_t pin_num = hal_gpio_get_pin_num(gpio); | ||||
|     if(gpio_interrupt[pin_num].callback) { | ||||
|         gpio_interrupt[pin_num].ready = true; | ||||
|     } | ||||
|     __enable_irq(); | ||||
| } | ||||
| 
 | ||||
| void hal_gpio_disable_int_callback(const GpioPin* gpio) { | ||||
|     assert(gpio); | ||||
| 
 | ||||
|     __disable_irq(); | ||||
|     uint8_t pin_num = hal_gpio_get_pin_num(gpio); | ||||
|     gpio_interrupt[pin_num].ready = false; | ||||
|     __enable_irq(); | ||||
| } | ||||
| 
 | ||||
| void hal_gpio_remove_int_callback(const GpioPin* gpio) { | ||||
|     assert(gpio); | ||||
| 
 | ||||
|     __disable_irq(); | ||||
|     uint8_t pin_num = hal_gpio_get_pin_num(gpio); | ||||
|     gpio_interrupt[pin_num].callback = NULL; | ||||
|     gpio_interrupt[pin_num].context = NULL; | ||||
|     gpio_interrupt[pin_num].ready = false; | ||||
|     __enable_irq(); | ||||
| } | ||||
							
								
								
									
										264
									
								
								bootloader/targets/f7/furi-hal/furi-hal-gpio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								bootloader/targets/f7/furi-hal/furi-hal-gpio.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,264 @@ | ||||
| #pragma once | ||||
| #include "main.h" | ||||
| #include "stdbool.h" | ||||
| #include <stm32wbxx_ll_gpio.h> | ||||
| #include <stm32wbxx_ll_system.h> | ||||
| #include <stm32wbxx_ll_exti.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /**
 | ||||
|  * Number of gpio on one port | ||||
|  */ | ||||
| #define GPIO_NUMBER (16U) | ||||
| 
 | ||||
| /**
 | ||||
|  * Interrupt callback prototype | ||||
|  */ | ||||
| typedef void (*GpioExtiCallback)(void* ctx); | ||||
| 
 | ||||
| /**
 | ||||
|  * Gpio interrupt type | ||||
|  */ | ||||
| typedef struct { | ||||
|     GpioExtiCallback callback; | ||||
|     void* context; | ||||
|     volatile bool ready; | ||||
| } GpioInterrupt; | ||||
| 
 | ||||
| /**
 | ||||
|  * Gpio modes | ||||
|  */ | ||||
| typedef enum { | ||||
|     GpioModeInput, | ||||
|     GpioModeOutputPushPull, | ||||
|     GpioModeOutputOpenDrain, | ||||
|     GpioModeAltFunctionPushPull, | ||||
|     GpioModeAltFunctionOpenDrain, | ||||
|     GpioModeAnalog, | ||||
|     GpioModeInterruptRise, | ||||
|     GpioModeInterruptFall, | ||||
|     GpioModeInterruptRiseFall, | ||||
|     GpioModeEventRise, | ||||
|     GpioModeEventFall, | ||||
|     GpioModeEventRiseFall, | ||||
| } GpioMode; | ||||
| 
 | ||||
| /**
 | ||||
|  * Gpio pull modes | ||||
|  */ | ||||
| typedef enum { | ||||
|     GpioPullNo, | ||||
|     GpioPullUp, | ||||
|     GpioPullDown, | ||||
| } GpioPull; | ||||
| 
 | ||||
| /**
 | ||||
|  * Gpio speed modes | ||||
|  */ | ||||
| typedef enum { | ||||
|     GpioSpeedLow, | ||||
|     GpioSpeedMedium, | ||||
|     GpioSpeedHigh, | ||||
|     GpioSpeedVeryHigh, | ||||
| } GpioSpeed; | ||||
| 
 | ||||
| /**
 | ||||
|  * Gpio alternate functions | ||||
|  */ | ||||
| typedef enum { | ||||
|     GpioAltFn0MCO = 0, /*!< MCO Alternate Function mapping */ | ||||
|     GpioAltFn0LSCO = 0, /*!< LSCO Alternate Function mapping */ | ||||
|     GpioAltFn0JTMS_SWDIO = 0, /*!< JTMS-SWDIO Alternate Function mapping */ | ||||
|     GpioAltFn0JTCK_SWCLK = 0, /*!< JTCK-SWCLK Alternate Function mapping */ | ||||
|     GpioAltFn0JTDI = 0, /*!< JTDI Alternate Function mapping */ | ||||
|     GpioAltFn0RTC_OUT = 0, /*!< RCT_OUT Alternate Function mapping */ | ||||
|     GpioAltFn0JTD_TRACE = 0, /*!< JTDO-TRACESWO Alternate Function mapping */ | ||||
|     GpioAltFn0NJTRST = 0, /*!< NJTRST Alternate Function mapping */ | ||||
|     GpioAltFn0RTC_REFIN = 0, /*!< RTC_REFIN Alternate Function mapping */ | ||||
|     GpioAltFn0TRACED0 = 0, /*!< TRACED0 Alternate Function mapping */ | ||||
|     GpioAltFn0TRACED1 = 0, /*!< TRACED1 Alternate Function mapping */ | ||||
|     GpioAltFn0TRACED2 = 0, /*!< TRACED2 Alternate Function mapping */ | ||||
|     GpioAltFn0TRACED3 = 0, /*!< TRACED3 Alternate Function mapping */ | ||||
|     GpioAltFn0TRIG_INOUT = 0, /*!< TRIG_INOUT Alternate Function mapping */ | ||||
|     GpioAltFn0TRACECK = 0, /*!< TRACECK Alternate Function mapping */ | ||||
|     GpioAltFn0SYS = 0, /*!< System Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn1TIM1 = 1, /*!< TIM1 Alternate Function mapping */ | ||||
|     GpioAltFn1TIM2 = 1, /*!< TIM2 Alternate Function mapping */ | ||||
|     GpioAltFn1LPTIM1 = 1, /*!< LPTIM1 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn2TIM2 = 2, /*!< TIM2 Alternate Function mapping */ | ||||
|     GpioAltFn2TIM1 = 2, /*!< TIM1 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn3SAI1 = 3, /*!< SAI1_CK1 Alternate Function mapping */ | ||||
|     GpioAltFn3SPI2 = 3, /*!< SPI2 Alternate Function mapping */ | ||||
|     GpioAltFn3TIM1 = 3, /*!< TIM1 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn4I2C1 = 4, /*!< I2C1 Alternate Function mapping */ | ||||
|     GpioAltFn4I2C3 = 4, /*!< I2C3 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn5SPI1 = 5, /*!< SPI1 Alternate Function mapping */ | ||||
|     GpioAltFn5SPI2 = 5, /*!< SPI2 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn6MCO = 6, /*!< MCO Alternate Function mapping */ | ||||
|     GpioAltFn6LSCO = 6, /*!< LSCO Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB0 = 6, /*!< RF_DTB0 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB1 = 6, /*!< RF_DTB1 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB2 = 6, /*!< RF_DTB2 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB3 = 6, /*!< RF_DTB3 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB4 = 6, /*!< RF_DTB4 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB5 = 6, /*!< RF_DTB5 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB6 = 6, /*!< RF_DTB6 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB7 = 6, /*!< RF_DTB7 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB8 = 6, /*!< RF_DTB8 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB9 = 6, /*!< RF_DTB9 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB10 = 6, /*!< RF_DTB10 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB11 = 6, /*!< RF_DTB11 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB12 = 6, /*!< RF_DTB12 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB13 = 6, /*!< RF_DTB13 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB14 = 6, /*!< RF_DTB14 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB15 = 6, /*!< RF_DTB15 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB16 = 6, /*!< RF_DTB16 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB17 = 6, /*!< RF_DTB17 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_DTB18 = 6, /*!< RF_DTB18 Alternate Function mapping */ | ||||
|     GpioAltFn6RF_MISO = 6, /*!< RF_MISO Alternate Function mapping */ | ||||
|     GpioAltFn6RF_MOSI = 6, /*!< RF_MOSI Alternate Function mapping */ | ||||
|     GpioAltFn6RF_SCK = 6, /*!< RF_SCK Alternate Function mapping */ | ||||
|     GpioAltFn6RF_NSS = 6, /*!< RF_NSS Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn7USART1 = 7, /*!< USART1 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn8LPUART1 = 8, /*!< LPUART1 Alternate Function mapping */ | ||||
|     GpioAltFn8IR = 8, /*!< IR Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn9TSC = 9, /*!< TSC Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn10QUADSPI = 10, /*!< QUADSPI Alternate Function mapping */ | ||||
|     GpioAltFn10USB = 10, /*!< USB Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn11LCD = 11, /*!< LCD Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn12COMP1 = 12, /*!< COMP1 Alternate Function mapping */ | ||||
|     GpioAltFn12COMP2 = 12, /*!< COMP2 Alternate Function mapping */ | ||||
|     GpioAltFn12TIM1 = 12, /*!< TIM1 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn13SAI1 = 13, /*!< SAI1 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn14TIM2 = 14, /*!< TIM2 Alternate Function mapping */ | ||||
|     GpioAltFn14TIM16 = 14, /*!< TIM16 Alternate Function mapping */ | ||||
|     GpioAltFn14TIM17 = 14, /*!< TIM17 Alternate Function mapping */ | ||||
|     GpioAltFn14LPTIM2 = 14, /*!< LPTIM2 Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFn15EVENTOUT = 15, /*!< EVENTOUT Alternate Function mapping */ | ||||
| 
 | ||||
|     GpioAltFnUnused = 16, /*!< just dummy value */ | ||||
| } GpioAltFn; | ||||
| 
 | ||||
| /**
 | ||||
|  * Gpio structure | ||||
|  */ | ||||
| typedef struct { | ||||
|     GPIO_TypeDef* port; | ||||
|     uint16_t pin; | ||||
| } GpioPin; | ||||
| 
 | ||||
| /**
 | ||||
|  * GPIO initialization function, simple version | ||||
|  * @param gpio  GpioPin | ||||
|  * @param mode  GpioMode | ||||
|  */ | ||||
| void hal_gpio_init_simple(const GpioPin* gpio, const GpioMode mode); | ||||
| 
 | ||||
| /**
 | ||||
|  * GPIO initialization function, normal version | ||||
|  * @param gpio  GpioPin | ||||
|  * @param mode  GpioMode | ||||
|  * @param pull  GpioPull | ||||
|  * @param speed GpioSpeed | ||||
|  */ | ||||
| void hal_gpio_init( | ||||
|     const GpioPin* gpio, | ||||
|     const GpioMode mode, | ||||
|     const GpioPull pull, | ||||
|     const GpioSpeed speed); | ||||
| 
 | ||||
| /**
 | ||||
|  * GPIO initialization function, extended version | ||||
|  * @param gpio  GpioPin | ||||
|  * @param mode  GpioMode | ||||
|  * @param pull  GpioPull | ||||
|  * @param speed GpioSpeed | ||||
|  * @param alt_fn GpioAltFn | ||||
|  */ | ||||
| void hal_gpio_init_ex( | ||||
|     const GpioPin* gpio, | ||||
|     const GpioMode mode, | ||||
|     const GpioPull pull, | ||||
|     const GpioSpeed speed, | ||||
|     const GpioAltFn alt_fn); | ||||
| 
 | ||||
| /**
 | ||||
|  * Add and enable interrupt | ||||
|  * @param gpio GpioPin | ||||
|  * @param cb   GpioExtiCallback | ||||
|  * @param ctx  context for callback | ||||
|  */ | ||||
| void hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, void* ctx); | ||||
| 
 | ||||
| /**
 | ||||
|  * Enable interrupt | ||||
|  * @param gpio GpioPin | ||||
|  */ | ||||
| void hal_gpio_enable_int_callback(const GpioPin* gpio); | ||||
| 
 | ||||
| /**
 | ||||
|  * Disable interrupt | ||||
|  * @param gpio GpioPin | ||||
|  */ | ||||
| void hal_gpio_disable_int_callback(const GpioPin* gpio); | ||||
| 
 | ||||
| /**
 | ||||
|  * Remove interrupt | ||||
|  * @param gpio GpioPin | ||||
|  */ | ||||
| void hal_gpio_remove_int_callback(const GpioPin* gpio); | ||||
| 
 | ||||
| /**
 | ||||
|  * GPIO write pin | ||||
|  * @param gpio  GpioPin | ||||
|  * @param state true / false | ||||
|  */ | ||||
| static inline void hal_gpio_write(const GpioPin* gpio, const bool state) { | ||||
|     // writing to BSSR is an atomic operation
 | ||||
|     if(state == true) { | ||||
|         gpio->port->BSRR = gpio->pin; | ||||
|     } else { | ||||
|         gpio->port->BSRR = (uint32_t)gpio->pin << GPIO_NUMBER; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * GPIO read pin | ||||
|  * @param gpio GpioPin | ||||
|  * @return true / false | ||||
|  */ | ||||
| static inline bool hal_gpio_read(const GpioPin* gpio) { | ||||
|     if((gpio->port->IDR & gpio->pin) != 0x00U) { | ||||
|         return true; | ||||
|     } else { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Get RFID IN level | ||||
|  * @return false = LOW, true = HIGH | ||||
|  */ | ||||
| bool hal_gpio_get_rfid_in_level(); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										137
									
								
								bootloader/targets/f7/furi-hal/furi-hal-i2c.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								bootloader/targets/f7/furi-hal/furi-hal-i2c.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,137 @@ | ||||
| #include <furi-hal-i2c.h> | ||||
| 
 | ||||
| #include <stm32wbxx_ll_bus.h> | ||||
| #include <stm32wbxx_ll_i2c.h> | ||||
| #include <stm32wbxx_ll_rcc.h> | ||||
| #include <stm32wbxx_ll_gpio.h> | ||||
| #include <stm32wbxx_ll_cortex.h> | ||||
| 
 | ||||
| void furi_hal_i2c_init() { | ||||
|     LL_I2C_InitTypeDef I2C_InitStruct = {0}; | ||||
|     LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; | ||||
| 
 | ||||
|     LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1); | ||||
| 
 | ||||
|     LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); | ||||
|     GPIO_InitStruct.Pin = POWER_I2C_SCL_Pin | POWER_I2C_SDA_Pin; | ||||
|     GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; | ||||
|     GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; | ||||
|     GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN; | ||||
|     GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; | ||||
|     GPIO_InitStruct.Alternate = LL_GPIO_AF_4; | ||||
|     LL_GPIO_Init(GPIOA, &GPIO_InitStruct); | ||||
| 
 | ||||
|     LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1); | ||||
| 
 | ||||
|     I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C; | ||||
|     I2C_InitStruct.Timing = POWER_I2C_TIMINGS; | ||||
|     I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE; | ||||
|     I2C_InitStruct.DigitalFilter = 0; | ||||
|     I2C_InitStruct.OwnAddress1 = 0; | ||||
|     I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK; | ||||
|     I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT; | ||||
|     LL_I2C_Init(I2C1, &I2C_InitStruct); | ||||
|     LL_I2C_EnableAutoEndMode(I2C1); | ||||
|     LL_I2C_SetOwnAddress2(I2C1, 0, LL_I2C_OWNADDRESS2_NOMASK); | ||||
|     LL_I2C_DisableOwnAddress2(I2C1); | ||||
|     LL_I2C_DisableGeneralCall(I2C1); | ||||
|     LL_I2C_EnableClockStretching(I2C1); | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_i2c_tx( | ||||
|     I2C_TypeDef* instance, | ||||
|     uint8_t address, | ||||
|     const uint8_t* data, | ||||
|     uint8_t size, | ||||
|     uint32_t timeout) { | ||||
|     uint32_t time_left = timeout; | ||||
|     bool ret = true; | ||||
| 
 | ||||
|     while(LL_I2C_IsActiveFlag_BUSY(instance)) | ||||
|         ; | ||||
| 
 | ||||
|     LL_I2C_HandleTransfer( | ||||
|         instance, | ||||
|         address, | ||||
|         LL_I2C_ADDRSLAVE_7BIT, | ||||
|         size, | ||||
|         LL_I2C_MODE_AUTOEND, | ||||
|         LL_I2C_GENERATE_START_WRITE); | ||||
| 
 | ||||
|     while(!LL_I2C_IsActiveFlag_STOP(instance) || size > 0) { | ||||
|         if(LL_I2C_IsActiveFlag_TXIS(instance)) { | ||||
|             LL_I2C_TransmitData8(instance, (*data)); | ||||
|             data++; | ||||
|             size--; | ||||
|             time_left = timeout; | ||||
|         } | ||||
| 
 | ||||
|         if(LL_SYSTICK_IsActiveCounterFlag()) { | ||||
|             if(--time_left == 0) { | ||||
|                 ret = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     LL_I2C_ClearFlag_STOP(instance); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_i2c_rx( | ||||
|     I2C_TypeDef* instance, | ||||
|     uint8_t address, | ||||
|     uint8_t* data, | ||||
|     uint8_t size, | ||||
|     uint32_t timeout) { | ||||
|     uint32_t time_left = timeout; | ||||
|     bool ret = true; | ||||
| 
 | ||||
|     while(LL_I2C_IsActiveFlag_BUSY(instance)) | ||||
|         ; | ||||
| 
 | ||||
|     LL_I2C_HandleTransfer( | ||||
|         instance, | ||||
|         address, | ||||
|         LL_I2C_ADDRSLAVE_7BIT, | ||||
|         size, | ||||
|         LL_I2C_MODE_AUTOEND, | ||||
|         LL_I2C_GENERATE_START_READ); | ||||
| 
 | ||||
|     while(!LL_I2C_IsActiveFlag_STOP(instance) || size > 0) { | ||||
|         if(LL_I2C_IsActiveFlag_RXNE(instance)) { | ||||
|             *data = LL_I2C_ReceiveData8(instance); | ||||
|             data++; | ||||
|             size--; | ||||
|             time_left = timeout; | ||||
|         } | ||||
| 
 | ||||
|         if(LL_SYSTICK_IsActiveCounterFlag()) { | ||||
|             if(--time_left == 0) { | ||||
|                 ret = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     LL_I2C_ClearFlag_STOP(instance); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_i2c_trx( | ||||
|     I2C_TypeDef* instance, | ||||
|     uint8_t address, | ||||
|     const uint8_t* tx_data, | ||||
|     uint8_t tx_size, | ||||
|     uint8_t* rx_data, | ||||
|     uint8_t rx_size, | ||||
|     uint32_t timeout) { | ||||
|     if(furi_hal_i2c_tx(instance, address, tx_data, tx_size, timeout) && | ||||
|        furi_hal_i2c_rx(instance, address, rx_data, rx_size, timeout)) { | ||||
|         return true; | ||||
|     } else { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								bootloader/targets/f7/furi-hal/furi-hal-i2c.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								bootloader/targets/f7/furi-hal/furi-hal-i2c.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <furi-hal-resources.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void furi_hal_i2c_init(); | ||||
| 
 | ||||
| bool furi_hal_i2c_tx( | ||||
|     I2C_TypeDef* instance, | ||||
|     const uint8_t address, | ||||
|     const uint8_t* data, | ||||
|     const uint8_t size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| bool furi_hal_i2c_rx( | ||||
|     I2C_TypeDef* instance, | ||||
|     const uint8_t address, | ||||
|     uint8_t* data, | ||||
|     const uint8_t size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| bool furi_hal_i2c_trx( | ||||
|     I2C_TypeDef* instance, | ||||
|     const uint8_t address, | ||||
|     const uint8_t* tx_data, | ||||
|     const uint8_t tx_size, | ||||
|     uint8_t* rx_data, | ||||
|     const uint8_t rx_size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| #define with_furi_hal_i2c(type, pointer, function_body)       \ | ||||
|     {                                                         \ | ||||
|         *pointer = ({ type __fn__ function_body __fn__; })(); \ | ||||
|     } | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										43
									
								
								bootloader/targets/f7/furi-hal/furi-hal-light.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								bootloader/targets/f7/furi-hal/furi-hal-light.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| #include <furi-hal-light.h> | ||||
| #include <lp5562.h> | ||||
| 
 | ||||
| #define LED_CURRENT_RED 50 | ||||
| #define LED_CURRENT_GREEN 50 | ||||
| #define LED_CURRENT_BLUE 50 | ||||
| #define LED_CURRENT_WHITE 150 | ||||
| 
 | ||||
| void furi_hal_light_init() { | ||||
|     lp5562_reset(); | ||||
| 
 | ||||
|     lp5562_set_channel_current(LP5562ChannelRed, LED_CURRENT_RED); | ||||
|     lp5562_set_channel_current(LP5562ChannelGreen, LED_CURRENT_GREEN); | ||||
|     lp5562_set_channel_current(LP5562ChannelBlue, LED_CURRENT_BLUE); | ||||
|     lp5562_set_channel_current(LP5562ChannelWhite, LED_CURRENT_WHITE); | ||||
| 
 | ||||
|     lp5562_set_channel_value(LP5562ChannelRed, 0x00); | ||||
|     lp5562_set_channel_value(LP5562ChannelGreen, 0x00); | ||||
|     lp5562_set_channel_value(LP5562ChannelBlue, 0x00); | ||||
|     lp5562_set_channel_value(LP5562ChannelWhite, 0x00); | ||||
| 
 | ||||
|     lp5562_enable(); | ||||
|     lp5562_configure(); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_light_set(Light light, uint8_t value) { | ||||
|     switch(light) { | ||||
|     case LightRed: | ||||
|         lp5562_set_channel_value(LP5562ChannelRed, value); | ||||
|         break; | ||||
|     case LightGreen: | ||||
|         lp5562_set_channel_value(LP5562ChannelGreen, value); | ||||
|         break; | ||||
|     case LightBlue: | ||||
|         lp5562_set_channel_value(LP5562ChannelBlue, value); | ||||
|         break; | ||||
|     case LightBacklight: | ||||
|         lp5562_set_channel_value(LP5562ChannelWhite, value); | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								bootloader/targets/f7/furi-hal/furi-hal-light.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								bootloader/targets/f7/furi-hal/furi-hal-light.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <furi-hal-resources.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void furi_hal_light_init(); | ||||
| 
 | ||||
| void furi_hal_light_set(Light light, uint8_t value); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										41
									
								
								bootloader/targets/f7/furi-hal/furi-hal-resources.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								bootloader/targets/f7/furi-hal/furi-hal-resources.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| #include "furi-hal-resources.h" | ||||
| #include "main.h" | ||||
| 
 | ||||
| const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin}; | ||||
| const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin}; | ||||
| 
 | ||||
| const GpioPin gpio_cc1101_g0 = {.port = CC1101_G0_GPIO_Port, .pin = CC1101_G0_Pin}; | ||||
| const GpioPin gpio_rf_sw_0 = {.port = RF_SW_0_GPIO_Port, .pin = RF_SW_0_Pin}; | ||||
| 
 | ||||
| const GpioPin gpio_subghz_cs = {.port = CC1101_CS_GPIO_Port, .pin = CC1101_CS_Pin}; | ||||
| const GpioPin gpio_display_cs = {.port = DISPLAY_CS_GPIO_Port, .pin = DISPLAY_CS_Pin}; | ||||
| const GpioPin gpio_display_rst = {.port = DISPLAY_RST_GPIO_Port, .pin = DISPLAY_RST_Pin}; | ||||
| const GpioPin gpio_display_di = {.port = DISPLAY_DI_GPIO_Port, .pin = DISPLAY_DI_Pin}; | ||||
| const GpioPin gpio_sdcard_cs = {.port = SD_CS_GPIO_Port, .pin = SD_CS_Pin}; | ||||
| const GpioPin gpio_nfc_cs = {.port = NFC_CS_GPIO_Port, .pin = NFC_CS_Pin}; | ||||
| 
 | ||||
| const GpioPin gpio_spi_d_miso = {.port = SPI_D_MISO_GPIO_Port, .pin = SPI_D_MISO_Pin}; | ||||
| const GpioPin gpio_spi_d_mosi = {.port = SPI_D_MOSI_GPIO_Port, .pin = SPI_D_MOSI_Pin}; | ||||
| const GpioPin gpio_spi_d_sck = {.port = SPI_D_SCK_GPIO_Port, .pin = SPI_D_SCK_Pin}; | ||||
| const GpioPin gpio_spi_r_miso = {.port = SPI_R_MISO_GPIO_Port, .pin = SPI_R_MISO_Pin}; | ||||
| const GpioPin gpio_spi_r_mosi = {.port = SPI_R_MOSI_GPIO_Port, .pin = SPI_R_MOSI_Pin}; | ||||
| const GpioPin gpio_spi_r_sck = {.port = SPI_R_SCK_GPIO_Port, .pin = SPI_R_SCK_Pin}; | ||||
| 
 | ||||
| const GpioPin gpio_ext_pc0 = {.port = GPIOC, .pin = LL_GPIO_PIN_0}; | ||||
| const GpioPin gpio_ext_pc1 = {.port = GPIOC, .pin = LL_GPIO_PIN_1}; | ||||
| const GpioPin gpio_ext_pc3 = {.port = GPIOC, .pin = LL_GPIO_PIN_3}; | ||||
| const GpioPin gpio_ext_pb2 = {.port = GPIOB, .pin = LL_GPIO_PIN_2}; | ||||
| const GpioPin gpio_ext_pb3 = {.port = GPIOB, .pin = LL_GPIO_PIN_3}; | ||||
| const GpioPin gpio_ext_pa4 = {.port = GPIOA, .pin = LL_GPIO_PIN_4}; | ||||
| const GpioPin gpio_ext_pa6 = {.port = GPIOA, .pin = LL_GPIO_PIN_6}; | ||||
| const GpioPin gpio_ext_pa7 = {.port = GPIOA, .pin = LL_GPIO_PIN_7}; | ||||
| 
 | ||||
| const GpioPin gpio_rfid_pull = {.port = RFID_PULL_GPIO_Port, .pin = RFID_PULL_Pin}; | ||||
| const GpioPin gpio_rfid_carrier_out = {.port = RFID_OUT_GPIO_Port, .pin = RFID_OUT_Pin}; | ||||
| const GpioPin gpio_rfid_data_in = {.port = RFID_RF_IN_GPIO_Port, .pin = RFID_RF_IN_Pin}; | ||||
| 
 | ||||
| const GpioPin gpio_irda_rx = {.port = IR_RX_GPIO_Port, .pin = IR_RX_Pin}; | ||||
| const GpioPin gpio_irda_tx = {.port = IR_TX_GPIO_Port, .pin = IR_TX_Pin}; | ||||
| 
 | ||||
| const GpioPin gpio_usart_tx = {.port = USART1_TX_Port, .pin = USART1_TX_Pin}; | ||||
| const GpioPin gpio_usart_rx = {.port = USART1_RX_Port, .pin = USART1_RX_Pin}; | ||||
							
								
								
									
										82
									
								
								bootloader/targets/f7/furi-hal/furi-hal-resources.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								bootloader/targets/f7/furi-hal/furi-hal-resources.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stm32wbxx.h> | ||||
| #include <stm32wbxx_ll_gpio.h> | ||||
| #include <furi-hal-gpio.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #define POWER_I2C_SCL_Pin LL_GPIO_PIN_9 | ||||
| #define POWER_I2C_SCL_GPIO_Port GPIOA | ||||
| #define POWER_I2C_SDA_Pin LL_GPIO_PIN_10 | ||||
| #define POWER_I2C_SDA_GPIO_Port GPIOA | ||||
| 
 | ||||
| #define POWER_I2C I2C1 | ||||
| /* Timing register value is computed with the STM32CubeMX Tool,
 | ||||
|   * Fast Mode @100kHz with I2CCLK = 64 MHz, | ||||
|   * rise time = 0ns, fall time = 0ns | ||||
|   */ | ||||
| #define POWER_I2C_TIMINGS 0x10707DBC | ||||
| 
 | ||||
| /* Input Keys */ | ||||
| typedef enum { | ||||
|     InputKeyUp, | ||||
|     InputKeyDown, | ||||
|     InputKeyRight, | ||||
|     InputKeyLeft, | ||||
|     InputKeyOk, | ||||
|     InputKeyBack, | ||||
| } InputKey; | ||||
| 
 | ||||
| /* Light */ | ||||
| typedef enum { | ||||
|     LightRed, | ||||
|     LightGreen, | ||||
|     LightBlue, | ||||
|     LightBacklight, | ||||
| } Light; | ||||
| 
 | ||||
| extern const GpioPin vibro_gpio; | ||||
| extern const GpioPin ibutton_gpio; | ||||
| 
 | ||||
| extern const GpioPin gpio_cc1101_g0; | ||||
| extern const GpioPin gpio_rf_sw_0; | ||||
| 
 | ||||
| extern const GpioPin gpio_subghz_cs; | ||||
| extern const GpioPin gpio_display_cs; | ||||
| extern const GpioPin gpio_display_rst; | ||||
| extern const GpioPin gpio_display_di; | ||||
| extern const GpioPin gpio_sdcard_cs; | ||||
| extern const GpioPin gpio_nfc_cs; | ||||
| 
 | ||||
| extern const GpioPin gpio_spi_d_miso; | ||||
| extern const GpioPin gpio_spi_d_mosi; | ||||
| extern const GpioPin gpio_spi_d_sck; | ||||
| extern const GpioPin gpio_spi_r_miso; | ||||
| extern const GpioPin gpio_spi_r_mosi; | ||||
| extern const GpioPin gpio_spi_r_sck; | ||||
| 
 | ||||
| extern const GpioPin gpio_ext_pc0; | ||||
| extern const GpioPin gpio_ext_pc1; | ||||
| extern const GpioPin gpio_ext_pc3; | ||||
| extern const GpioPin gpio_ext_pb2; | ||||
| extern const GpioPin gpio_ext_pb3; | ||||
| extern const GpioPin gpio_ext_pa4; | ||||
| extern const GpioPin gpio_ext_pa6; | ||||
| extern const GpioPin gpio_ext_pa7; | ||||
| 
 | ||||
| extern const GpioPin gpio_rfid_pull; | ||||
| extern const GpioPin gpio_rfid_carrier_out; | ||||
| extern const GpioPin gpio_rfid_data_in; | ||||
| 
 | ||||
| extern const GpioPin gpio_irda_rx; | ||||
| extern const GpioPin gpio_irda_tx; | ||||
| 
 | ||||
| extern const GpioPin gpio_usart_tx; | ||||
| extern const GpioPin gpio_usart_rx; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										114
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi-config.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi-config.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| #include <furi-hal-spi-config.h> | ||||
| #include <furi-hal-resources.h> | ||||
| 
 | ||||
| #define SPI_R SPI1 | ||||
| #define SPI_D SPI2 | ||||
| 
 | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_nfc = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
|     .ClockPolarity = LL_SPI_POLARITY_LOW, | ||||
|     .ClockPhase = LL_SPI_PHASE_2EDGE, | ||||
|     .NSS = LL_SPI_NSS_SOFT, | ||||
|     .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8, | ||||
|     .BitOrder = LL_SPI_MSB_FIRST, | ||||
|     .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
| 
 | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_subghz = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
|     .ClockPolarity = LL_SPI_POLARITY_LOW, | ||||
|     .ClockPhase = LL_SPI_PHASE_1EDGE, | ||||
|     .NSS = LL_SPI_NSS_SOFT, | ||||
|     .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8, | ||||
|     .BitOrder = LL_SPI_MSB_FIRST, | ||||
|     .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
| 
 | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_display = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
|     .ClockPolarity = LL_SPI_POLARITY_LOW, | ||||
|     .ClockPhase = LL_SPI_PHASE_1EDGE, | ||||
|     .NSS = LL_SPI_NSS_SOFT, | ||||
|     .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV16, | ||||
|     .BitOrder = LL_SPI_MSB_FIRST, | ||||
|     .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * SD Card in fast mode (after init) | ||||
|  */ | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_sd_fast = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
|     .ClockPolarity = LL_SPI_POLARITY_LOW, | ||||
|     .ClockPhase = LL_SPI_PHASE_1EDGE, | ||||
|     .NSS = LL_SPI_NSS_SOFT, | ||||
|     .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV2, | ||||
|     .BitOrder = LL_SPI_MSB_FIRST, | ||||
|     .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * SD Card in slow mode (before init) | ||||
|  */ | ||||
| const LL_SPI_InitTypeDef furi_hal_spi_config_sd_slow = { | ||||
|     .Mode = LL_SPI_MODE_MASTER, | ||||
|     .TransferDirection = LL_SPI_FULL_DUPLEX, | ||||
|     .DataWidth = LL_SPI_DATAWIDTH_8BIT, | ||||
|     .ClockPolarity = LL_SPI_POLARITY_LOW, | ||||
|     .ClockPhase = LL_SPI_PHASE_1EDGE, | ||||
|     .NSS = LL_SPI_NSS_SOFT, | ||||
|     .BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV32, | ||||
|     .BitOrder = LL_SPI_MSB_FIRST, | ||||
|     .CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE, | ||||
|     .CRCPoly = 7, | ||||
| }; | ||||
| 
 | ||||
| const FuriHalSpiBus spi_r = { | ||||
|     .spi = SPI_R, | ||||
|     .miso = &gpio_spi_r_miso, | ||||
|     .mosi = &gpio_spi_r_mosi, | ||||
|     .clk = &gpio_spi_r_sck, | ||||
| }; | ||||
| 
 | ||||
| const FuriHalSpiBus spi_d = { | ||||
|     .spi = SPI_D, | ||||
|     .miso = &gpio_spi_d_miso, | ||||
|     .mosi = &gpio_spi_d_mosi, | ||||
|     .clk = &gpio_spi_d_sck, | ||||
| }; | ||||
| 
 | ||||
| const FuriHalSpiDevice furi_hal_spi_devices[FuriHalSpiDeviceIdMax] = { | ||||
|     { | ||||
|         .bus = &spi_r, | ||||
|         .config = &furi_hal_spi_config_subghz, | ||||
|         .chip_select = &gpio_subghz_cs, | ||||
|     }, | ||||
|     { | ||||
|         .bus = &spi_d, | ||||
|         .config = &furi_hal_spi_config_display, | ||||
|         .chip_select = &gpio_display_cs, | ||||
|     }, | ||||
|     { | ||||
|         .bus = &spi_d, | ||||
|         .config = &furi_hal_spi_config_sd_fast, | ||||
|         .chip_select = &gpio_sdcard_cs, | ||||
|     }, | ||||
|     { | ||||
|         .bus = &spi_d, | ||||
|         .config = &furi_hal_spi_config_sd_slow, | ||||
|         .chip_select = &gpio_sdcard_cs, | ||||
|     }, | ||||
|     {.bus = &spi_r, .config = &furi_hal_spi_config_nfc, .chip_select = &gpio_nfc_cs}, | ||||
| }; | ||||
							
								
								
									
										61
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi-config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi-config.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi-hal-gpio.h> | ||||
| #include <stm32wbxx_ll_spi.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_nfc; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_subghz; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_display; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_sd_fast; | ||||
| extern const LL_SPI_InitTypeDef furi_hal_spi_config_sd_slow; | ||||
| 
 | ||||
| /** FURI HAL SPI BUS handler
 | ||||
|  * Structure content may change at some point | ||||
|  */ | ||||
| typedef struct { | ||||
|     const SPI_TypeDef* spi; | ||||
|     const GpioPin* miso; | ||||
|     const GpioPin* mosi; | ||||
|     const GpioPin* clk; | ||||
| } FuriHalSpiBus; | ||||
| 
 | ||||
| /** FURI HAL SPI Device handler
 | ||||
|  * Structure content may change at some point | ||||
|  */ | ||||
| typedef struct { | ||||
|     const FuriHalSpiBus* bus; | ||||
|     const LL_SPI_InitTypeDef* config; | ||||
|     const GpioPin* chip_select; | ||||
| } FuriHalSpiDevice; | ||||
| 
 | ||||
| /** FURI HAL SPI Standard Device IDs */ | ||||
| typedef enum { | ||||
|     FuriHalSpiDeviceIdSubGhz, /** SubGhz: CC1101, non-standard SPI usage */ | ||||
|     FuriHalSpiDeviceIdDisplay, /** Display: ERC12864, only have MOSI */ | ||||
|     FuriHalSpiDeviceIdSdCardFast, /** SDCARD: fast mode, after initialization */ | ||||
|     FuriHalSpiDeviceIdSdCardSlow, /** SDCARD: slow mode, before initialization */ | ||||
|     FuriHalSpiDeviceIdNfc, /** NFC: ST25R3916, pretty standard, but RFAL makes it complex */ | ||||
| 
 | ||||
|     FuriHalSpiDeviceIdMax, /** Service Value, do not use */ | ||||
| } FuriHalSpiDeviceId; | ||||
| 
 | ||||
| /** Furi Hal Spi Bus R
 | ||||
|  * CC1101, Nfc | ||||
|  */ | ||||
| extern const FuriHalSpiBus spi_r; | ||||
| 
 | ||||
| /** Furi Hal Spi Bus D
 | ||||
|  * Display, SdCard | ||||
|  */ | ||||
| extern const FuriHalSpiBus spi_d; | ||||
| 
 | ||||
| /** Furi Hal Spi devices */ | ||||
| extern const FuriHalSpiDevice furi_hal_spi_devices[FuriHalSpiDeviceIdMax]; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										240
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,240 @@ | ||||
| #include "furi-hal-spi.h" | ||||
| #include "furi-hal-resources.h" | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #include <stm32wbxx_ll_spi.h> | ||||
| #include <stm32wbxx_ll_utils.h> | ||||
| #include <stm32wbxx_ll_cortex.h> | ||||
| 
 | ||||
| extern void Enable_SPI(SPI_TypeDef* spi); | ||||
| 
 | ||||
| void furi_hal_spi_init() { | ||||
|     for(size_t i = 0; i < FuriHalSpiDeviceIdMax; ++i) { | ||||
|         hal_gpio_write(furi_hal_spi_devices[i].chip_select, true); | ||||
|         hal_gpio_init( | ||||
|             furi_hal_spi_devices[i].chip_select, | ||||
|             GpioModeOutputPushPull, | ||||
|             GpioPullNo, | ||||
|             GpioSpeedVeryHigh); | ||||
|     } | ||||
| 
 | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_r_miso, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI1); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_r_mosi, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI1); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_r_sck, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullNo, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI1); | ||||
| 
 | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_d_miso, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_d_mosi, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
|     hal_gpio_init_ex( | ||||
|         &gpio_spi_d_sck, | ||||
|         GpioModeAltFunctionPushPull, | ||||
|         GpioPullUp, | ||||
|         GpioSpeedVeryHigh, | ||||
|         GpioAltFn5SPI2); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_spi_bus_lock(const FuriHalSpiBus* bus) { | ||||
|     assert(bus); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_spi_bus_unlock(const FuriHalSpiBus* bus) { | ||||
|     assert(bus); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_spi_bus_configure(const FuriHalSpiBus* bus, const LL_SPI_InitTypeDef* config) { | ||||
|     assert(bus); | ||||
|     LL_SPI_DeInit((SPI_TypeDef*)bus->spi); | ||||
|     LL_SPI_Init((SPI_TypeDef*)bus->spi, (LL_SPI_InitTypeDef*)config); | ||||
|     LL_SPI_SetRxFIFOThreshold((SPI_TypeDef*)bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); | ||||
|     LL_SPI_Enable((SPI_TypeDef*)bus->spi); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_spi_bus_end_txrx(const FuriHalSpiBus* bus, uint32_t timeout) { | ||||
|     while(LL_SPI_GetTxFIFOLevel((SPI_TypeDef*)bus->spi) != LL_SPI_TX_FIFO_EMPTY) | ||||
|         ; | ||||
|     while(LL_SPI_IsActiveFlag_BSY((SPI_TypeDef*)bus->spi)) | ||||
|         ; | ||||
|     while(LL_SPI_GetRxFIFOLevel((SPI_TypeDef*)bus->spi) != LL_SPI_RX_FIFO_EMPTY) { | ||||
|         LL_SPI_ReceiveData8((SPI_TypeDef*)bus->spi); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_spi_bus_rx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
|     assert(bus); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
| 
 | ||||
|     return furi_hal_spi_bus_trx(bus, buffer, buffer, size, timeout); | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_spi_bus_tx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout) { | ||||
|     assert(bus); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
|     bool ret = true; | ||||
| 
 | ||||
|     while(size > 0) { | ||||
|         if(LL_SPI_IsActiveFlag_TXE((SPI_TypeDef*)bus->spi)) { | ||||
|             LL_SPI_TransmitData8((SPI_TypeDef*)bus->spi, *buffer); | ||||
|             buffer++; | ||||
|             size--; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_spi_bus_end_txrx(bus, timeout); | ||||
|     LL_SPI_ClearFlag_OVR((SPI_TypeDef*)bus->spi); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_spi_bus_trx( | ||||
|     const FuriHalSpiBus* bus, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(bus); | ||||
|     assert(tx_buffer); | ||||
|     assert(rx_buffer); | ||||
|     assert(size > 0); | ||||
|     bool ret = true; | ||||
|     size_t tx_size = size; | ||||
|     bool tx_allowed = true; | ||||
| 
 | ||||
|     while(size > 0) { | ||||
|         if(tx_size > 0 && LL_SPI_IsActiveFlag_TXE((SPI_TypeDef*)bus->spi) && tx_allowed) { | ||||
|             LL_SPI_TransmitData8((SPI_TypeDef*)bus->spi, *tx_buffer); | ||||
|             tx_buffer++; | ||||
|             tx_size--; | ||||
|             tx_allowed = false; | ||||
|         } | ||||
| 
 | ||||
|         if(LL_SPI_IsActiveFlag_RXNE((SPI_TypeDef*)bus->spi)) { | ||||
|             *rx_buffer = LL_SPI_ReceiveData8((SPI_TypeDef*)bus->spi); | ||||
|             rx_buffer++; | ||||
|             size--; | ||||
|             tx_allowed = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_spi_bus_end_txrx(bus, timeout); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void furi_hal_spi_device_configure(const FuriHalSpiDevice* device) { | ||||
|     assert(device); | ||||
|     assert(device->config); | ||||
| 
 | ||||
|     furi_hal_spi_bus_configure(device->bus, device->config); | ||||
| } | ||||
| 
 | ||||
| const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id) { | ||||
|     assert(device_id < FuriHalSpiDeviceIdMax); | ||||
| 
 | ||||
|     const FuriHalSpiDevice* device = &furi_hal_spi_devices[device_id]; | ||||
|     assert(device); | ||||
| 
 | ||||
|     furi_hal_spi_bus_lock(device->bus); | ||||
|     furi_hal_spi_device_configure(device); | ||||
| 
 | ||||
|     return device; | ||||
| } | ||||
| 
 | ||||
| void furi_hal_spi_device_return(const FuriHalSpiDevice* device) { | ||||
|     furi_hal_spi_bus_unlock(device->bus); | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_spi_device_rx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(device); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
| 
 | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, false); | ||||
|     } | ||||
| 
 | ||||
|     bool ret = furi_hal_spi_bus_rx(device->bus, buffer, size, timeout); | ||||
| 
 | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, true); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_spi_device_tx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(device); | ||||
|     assert(buffer); | ||||
|     assert(size > 0); | ||||
| 
 | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, false); | ||||
|     } | ||||
| 
 | ||||
|     bool ret = furi_hal_spi_bus_tx(device->bus, buffer, size, timeout); | ||||
| 
 | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, true); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool furi_hal_spi_device_trx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout) { | ||||
|     assert(device); | ||||
|     assert(tx_buffer); | ||||
|     assert(rx_buffer); | ||||
|     assert(size > 0); | ||||
| 
 | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, false); | ||||
|     } | ||||
| 
 | ||||
|     bool ret = furi_hal_spi_bus_trx(device->bus, tx_buffer, rx_buffer, size, timeout); | ||||
| 
 | ||||
|     if(device->chip_select) { | ||||
|         hal_gpio_write(device->chip_select, true); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
							
								
								
									
										129
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								bootloader/targets/f7/furi-hal/furi-hal-spi.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "main.h" | ||||
| 
 | ||||
| #include "furi-hal-spi-config.h" | ||||
| #include <furi-hal-gpio.h> | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stddef.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /**
 | ||||
|  * Init SPI API | ||||
|  */ | ||||
| void furi_hal_spi_init(); | ||||
| 
 | ||||
| /* Bus Level API */ | ||||
| 
 | ||||
| /** Lock SPI bus
 | ||||
|  * Takes bus mutex, if used | ||||
|  */ | ||||
| void furi_hal_spi_bus_lock(const FuriHalSpiBus* bus); | ||||
| 
 | ||||
| /** Unlock SPI bus
 | ||||
|  * Releases BUS mutex, if used | ||||
|  */ | ||||
| void furi_hal_spi_bus_unlock(const FuriHalSpiBus* bus); | ||||
| 
 | ||||
| /**
 | ||||
|  * Configure SPI bus | ||||
|  * @param bus - spi bus handler | ||||
|  * @param config - spi configuration structure | ||||
|  */ | ||||
| void furi_hal_spi_bus_configure(const FuriHalSpiBus* bus, const LL_SPI_InitTypeDef* config); | ||||
| 
 | ||||
| /** SPI Receive
 | ||||
|  * @param bus - spi bus handler | ||||
|  * @param buffer - receive buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_bus_rx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout); | ||||
| 
 | ||||
| /** SPI Transmit
 | ||||
|  * @param bus - spi bus handler | ||||
|  * @param buffer - transmit buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_bus_tx(const FuriHalSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout); | ||||
| 
 | ||||
| /** SPI Transmit and Receive
 | ||||
|  * @param bus - spi bus handlere | ||||
|  * @param tx_buffer - device handle | ||||
|  * @param rx_buffer - device handle | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_bus_trx( | ||||
|     const FuriHalSpiBus* bus, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| /* Device Level API */ | ||||
| 
 | ||||
| /** Reconfigure SPI bus for device
 | ||||
|  * @param device - device description | ||||
|  */ | ||||
| void furi_hal_spi_device_configure(const FuriHalSpiDevice* device); | ||||
| 
 | ||||
| /** Get Device handle
 | ||||
|  * And lock access to the corresponding SPI BUS | ||||
|  * @param device_id - device identifier | ||||
|  * @return device handle | ||||
|  */ | ||||
| const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id); | ||||
| 
 | ||||
| /** Return Device handle
 | ||||
|  * And unlock access to the corresponding SPI BUS | ||||
|  * @param device - device handle | ||||
|  */ | ||||
| void furi_hal_spi_device_return(const FuriHalSpiDevice* device); | ||||
| 
 | ||||
| /** SPI Recieve
 | ||||
|  * @param device - device handle | ||||
|  * @param buffer - receive buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_device_rx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| /** SPI Transmit
 | ||||
|  * @param device - device handle | ||||
|  * @param buffer - transmit buffer | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_device_tx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| /** SPI Transmit and Receive
 | ||||
|  * @param device - device handle | ||||
|  * @param tx_buffer - device handle | ||||
|  * @param rx_buffer - device handle | ||||
|  * @param size - transaction size | ||||
|  * @param timeout - bus operation timeout in ms | ||||
|  */ | ||||
| bool furi_hal_spi_device_trx( | ||||
|     const FuriHalSpiDevice* device, | ||||
|     uint8_t* tx_buffer, | ||||
|     uint8_t* rx_buffer, | ||||
|     size_t size, | ||||
|     uint32_t timeout); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										7
									
								
								bootloader/targets/f7/furi-hal/furi-hal.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								bootloader/targets/f7/furi-hal/furi-hal.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| #include <furi-hal.h> | ||||
| 
 | ||||
| void furi_hal_init() { | ||||
|     furi_hal_i2c_init(); | ||||
|     furi_hal_light_init(); | ||||
|     furi_hal_spi_init(); | ||||
| } | ||||
							
								
								
									
										8
									
								
								bootloader/targets/f7/furi-hal/furi-hal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								bootloader/targets/f7/furi-hal/furi-hal.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi-hal-i2c.h> | ||||
| #include <furi-hal-light.h> | ||||
| #include <furi-hal-resources.h> | ||||
| #include <furi-hal-spi.h> | ||||
| 
 | ||||
| void furi_hal_init(); | ||||
							
								
								
									
										108
									
								
								bootloader/targets/f7/furi-hal/main.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								bootloader/targets/f7/furi-hal/main.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stm32wbxx.h> | ||||
| #include <stm32wbxx_ll_gpio.h> | ||||
| #include <stm32wbxx_ll_spi.h> | ||||
| 
 | ||||
| #define BUTTON_BACK_GPIO_Port GPIOC | ||||
| #define BUTTON_BACK_Pin LL_GPIO_PIN_13 | ||||
| #define BUTTON_DOWN_GPIO_Port GPIOC | ||||
| #define BUTTON_DOWN_Pin LL_GPIO_PIN_6 | ||||
| #define BUTTON_LEFT_GPIO_Port GPIOB | ||||
| #define BUTTON_LEFT_Pin LL_GPIO_PIN_11 | ||||
| #define BUTTON_OK_GPIO_Port GPIOH | ||||
| #define BUTTON_OK_Pin LL_GPIO_PIN_3 | ||||
| #define BUTTON_RIGHT_GPIO_Port GPIOB | ||||
| #define BUTTON_RIGHT_Pin LL_GPIO_PIN_12 | ||||
| #define BUTTON_UP_GPIO_Port GPIOB | ||||
| #define BUTTON_UP_Pin LL_GPIO_PIN_10 | ||||
| 
 | ||||
| #define CC1101_CS_GPIO_Port GPIOD | ||||
| #define CC1101_CS_Pin LL_GPIO_PIN_0 | ||||
| #define CC1101_G0_GPIO_Port GPIOA | ||||
| #define CC1101_G0_Pin LL_GPIO_PIN_1 | ||||
| 
 | ||||
| #define DISPLAY_CS_GPIO_Port GPIOC | ||||
| #define DISPLAY_CS_Pin LL_GPIO_PIN_11 | ||||
| #define DISPLAY_DI_GPIO_Port GPIOB | ||||
| #define DISPLAY_DI_Pin LL_GPIO_PIN_1 | ||||
| #define DISPLAY_RST_GPIO_Port GPIOB | ||||
| #define DISPLAY_RST_Pin LL_GPIO_PIN_0 | ||||
| 
 | ||||
| #define IR_RX_GPIO_Port GPIOA | ||||
| #define IR_RX_Pin LL_GPIO_PIN_0 | ||||
| #define IR_TX_GPIO_Port GPIOB | ||||
| #define IR_TX_Pin LL_GPIO_PIN_9 | ||||
| 
 | ||||
| #define NFC_CS_GPIO_Port GPIOE | ||||
| #define NFC_CS_Pin LL_GPIO_PIN_4 | ||||
| 
 | ||||
| #define PA4_GPIO_Port GPIOA | ||||
| #define PA4_Pin LL_GPIO_PIN_4 | ||||
| #define PA6_GPIO_Port GPIOA | ||||
| #define PA6_Pin LL_GPIO_PIN_6 | ||||
| #define PA7_GPIO_Port GPIOA | ||||
| #define PA7_Pin LL_GPIO_PIN_7 | ||||
| #define PB2_GPIO_Port GPIOB | ||||
| #define PB2_Pin LL_GPIO_PIN_2 | ||||
| #define PB3_GPIO_Port GPIOB | ||||
| #define PB3_Pin LL_GPIO_PIN_3 | ||||
| #define PC0_GPIO_Port GPIOC | ||||
| #define PC0_Pin LL_GPIO_PIN_0 | ||||
| #define PC1_GPIO_Port GPIOC | ||||
| #define PC1_Pin LL_GPIO_PIN_1 | ||||
| #define PC3_GPIO_Port GPIOC | ||||
| #define PC3_Pin LL_GPIO_PIN_3 | ||||
| 
 | ||||
| #define PERIPH_POWER_GPIO_Port GPIOA | ||||
| #define PERIPH_POWER_Pin LL_GPIO_PIN_3 | ||||
| 
 | ||||
| #define QUARTZ_32MHZ_IN_GPIO_Port GPIOC | ||||
| #define QUARTZ_32MHZ_IN_Pin LL_GPIO_PIN_14 | ||||
| #define QUARTZ_32MHZ_OUT_GPIO_Port GPIOC | ||||
| #define QUARTZ_32MHZ_OUT_Pin LL_GPIO_PIN_15 | ||||
| 
 | ||||
| #define RFID_OUT_GPIO_Port GPIOB | ||||
| #define RFID_OUT_Pin LL_GPIO_PIN_13 | ||||
| #define RFID_PULL_GPIO_Port GPIOA | ||||
| #define RFID_PULL_Pin LL_GPIO_PIN_2 | ||||
| #define RFID_RF_IN_GPIO_Port GPIOC | ||||
| #define RFID_RF_IN_Pin LL_GPIO_PIN_5 | ||||
| #define RFID_TUNE_GPIO_Port GPIOA | ||||
| #define RFID_TUNE_Pin LL_GPIO_PIN_8 | ||||
| 
 | ||||
| #define RF_SW_0_GPIO_Port GPIOC | ||||
| #define RF_SW_0_Pin LL_GPIO_PIN_4 | ||||
| 
 | ||||
| #define SD_CD_GPIO_Port GPIOC | ||||
| #define SD_CD_Pin LL_GPIO_PIN_10 | ||||
| #define SD_CS_GPIO_Port GPIOC | ||||
| #define SD_CS_Pin LL_GPIO_PIN_12 | ||||
| 
 | ||||
| #define SPEAKER_GPIO_Port GPIOB | ||||
| #define SPEAKER_Pin LL_GPIO_PIN_8 | ||||
| 
 | ||||
| #define VIBRO_GPIO_Port GPIOA | ||||
| #define VIBRO_Pin LL_GPIO_PIN_15 | ||||
| 
 | ||||
| #define iBTN_GPIO_Port GPIOB | ||||
| #define iBTN_Pin LL_GPIO_PIN_14 | ||||
| 
 | ||||
| #define USART1_TX_Pin LL_GPIO_PIN_6 | ||||
| #define USART1_TX_Port GPIOB | ||||
| #define USART1_RX_Pin LL_GPIO_PIN_7 | ||||
| #define USART1_RX_Port GPIOB | ||||
| 
 | ||||
| #define SPI_D_MISO_GPIO_Port GPIOC | ||||
| #define SPI_D_MISO_Pin LL_GPIO_PIN_2 | ||||
| #define SPI_D_MOSI_GPIO_Port GPIOB | ||||
| #define SPI_D_MOSI_Pin LL_GPIO_PIN_15 | ||||
| #define SPI_D_SCK_GPIO_Port GPIOD | ||||
| #define SPI_D_SCK_Pin LL_GPIO_PIN_1 | ||||
| 
 | ||||
| #define SPI_R_MISO_GPIO_Port GPIOB | ||||
| #define SPI_R_MISO_Pin LL_GPIO_PIN_4 | ||||
| #define SPI_R_MOSI_GPIO_Port GPIOB | ||||
| #define SPI_R_MOSI_Pin LL_GPIO_PIN_5 | ||||
| #define SPI_R_SCK_GPIO_Port GPIOA | ||||
| #define SPI_R_SCK_Pin LL_GPIO_PIN_5 | ||||
							
								
								
									
										69
									
								
								bootloader/targets/f7/furi-hal/u8g2_periphery.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								bootloader/targets/f7/furi-hal/u8g2_periphery.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| #include <u8g2.h> | ||||
| #include <assert.h> | ||||
| #include <furi-hal.h> | ||||
| #include <stm32wbxx_ll_utils.h> | ||||
| 
 | ||||
| static FuriHalSpiDevice* u8g2_periphery_display = NULL; | ||||
| 
 | ||||
| uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) { | ||||
|     switch(msg) { | ||||
|     case U8X8_MSG_GPIO_AND_DELAY_INIT: | ||||
|         /* HAL initialization contains all what we need so we can skip this part. */ | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_DELAY_MILLI: | ||||
|         LL_mDelay(arg_int); | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_DELAY_10MICRO: | ||||
|         LL_mDelay(1); | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_DELAY_100NANO: | ||||
|         asm("nop"); | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_GPIO_RESET: | ||||
|         hal_gpio_write(&gpio_display_rst, arg_int); | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return 1; | ||||
| } | ||||
| 
 | ||||
| uint8_t u8x8_hw_spi_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) { | ||||
|     switch(msg) { | ||||
|     case U8X8_MSG_BYTE_SEND: | ||||
|         furi_hal_spi_bus_tx(u8g2_periphery_display->bus, (uint8_t*)arg_ptr, arg_int, 10000); | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_BYTE_SET_DC: | ||||
|         hal_gpio_write(&gpio_display_di, arg_int); | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_BYTE_INIT: | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_BYTE_START_TRANSFER: | ||||
|         assert(u8g2_periphery_display == NULL); | ||||
|         u8g2_periphery_display = | ||||
|             (FuriHalSpiDevice*)furi_hal_spi_device_get(FuriHalSpiDeviceIdDisplay); | ||||
|         hal_gpio_write(u8g2_periphery_display->chip_select, false); | ||||
|         break; | ||||
| 
 | ||||
|     case U8X8_MSG_BYTE_END_TRANSFER: | ||||
|         assert(u8g2_periphery_display); | ||||
|         hal_gpio_write(u8g2_periphery_display->chip_select, true); | ||||
|         furi_hal_spi_device_return(u8g2_periphery_display); | ||||
|         u8g2_periphery_display = NULL; | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     return 1; | ||||
| } | ||||
							
								
								
									
										187
									
								
								bootloader/targets/f7/stm32wb55xx_flash_cm4.ld
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								bootloader/targets/f7/stm32wb55xx_flash_cm4.ld
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | ||||
| /** | ||||
| ***************************************************************************** | ||||
| ** | ||||
| **  File        : stm32wb55xx_flash_cm4.ld | ||||
| ** | ||||
| **  Abstract    : System Workbench Minimal System calls file | ||||
| ** | ||||
| ** 		          For more information about which c-functions | ||||
| **                need which of these lowlevel functions | ||||
| **                please consult the Newlib libc-manual | ||||
| ** | ||||
| **  Environment : System Workbench for MCU | ||||
| ** | ||||
| **  Distribution: The file is distributed “as is,” without any warranty | ||||
| **                of any kind. | ||||
| ** | ||||
| ***************************************************************************** | ||||
| ** | ||||
| ** <h2><center>© COPYRIGHT(c) 2019 Ac6</center></h2> | ||||
| ** | ||||
| ** Redistribution and use in source and binary forms, with or without modification, | ||||
| ** are permitted provided that the following conditions are met: | ||||
| **   1. Redistributions of source code must retain the above copyright notice, | ||||
| **      this list of conditions and the following disclaimer. | ||||
| **   2. Redistributions in binary form must reproduce the above copyright notice, | ||||
| **      this list of conditions and the following disclaimer in the documentation | ||||
| **      and/or other materials provided with the distribution. | ||||
| **   3. Neither the name of Ac6 nor the names of its contributors | ||||
| **      may be used to endorse or promote products derived from this software | ||||
| **      without specific prior written permission. | ||||
| ** | ||||
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| ** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| ** | ||||
| ***************************************************************************** | ||||
| */ | ||||
| 
 | ||||
| /* Entry Point */ | ||||
| ENTRY(Reset_Handler) | ||||
| 
 | ||||
| /* Highest address of the user mode stack */ | ||||
| _estack = 0x20030000;    /* end of RAM */ | ||||
| /* Generate a link error if heap and stack don't fit into RAM */ | ||||
| _Min_Heap_Size = 0x200;      /* required amount of heap  */ | ||||
| _Min_Stack_Size = 0x400; /* required amount of stack */ | ||||
| 
 | ||||
| /* Specify the memory areas */ | ||||
| MEMORY | ||||
| { | ||||
| FLASH (rx)                 : ORIGIN = 0x08000000, LENGTH = 32K | ||||
| RAM1 (xrw)                 : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 | ||||
| RAM_SHARED (xrw)           : ORIGIN = 0x20030000, LENGTH = 10K | ||||
| } | ||||
| 
 | ||||
| /* Define output sections */ | ||||
| SECTIONS | ||||
| { | ||||
|   /* The startup code goes first into FLASH */ | ||||
|   .isr_vector : | ||||
|   { | ||||
|     . = ALIGN(4); | ||||
|     KEEP(*(.isr_vector)) /* Startup code */ | ||||
|     . = ALIGN(4); | ||||
|   } >FLASH | ||||
| 
 | ||||
|   /* The program code and other data goes into FLASH */ | ||||
|   .text : | ||||
|   { | ||||
|     . = ALIGN(4); | ||||
|     *(.text)           /* .text sections (code) */ | ||||
|     *(.text*)          /* .text* sections (code) */ | ||||
|     *(.glue_7)         /* glue arm to thumb code */ | ||||
|     *(.glue_7t)        /* glue thumb to arm code */ | ||||
|     *(.eh_frame) | ||||
| 
 | ||||
|     KEEP (*(.init)) | ||||
|     KEEP (*(.fini)) | ||||
| 
 | ||||
|     . = ALIGN(4); | ||||
|     _etext = .;        /* define a global symbols at end of code */ | ||||
|   } >FLASH | ||||
| 
 | ||||
|   /* Constant data goes into FLASH */ | ||||
|   .rodata : | ||||
|   { | ||||
|     . = ALIGN(4); | ||||
|     *(.rodata)         /* .rodata sections (constants, strings, etc.) */ | ||||
|     *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */ | ||||
|     . = ALIGN(4); | ||||
|   } >FLASH | ||||
| 
 | ||||
|   .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH | ||||
|   .ARM : { | ||||
|     __exidx_start = .; | ||||
|     *(.ARM.exidx*) | ||||
|     __exidx_end = .; | ||||
|   } >FLASH | ||||
| 
 | ||||
|   .preinit_array     : | ||||
|   { | ||||
|     PROVIDE_HIDDEN (__preinit_array_start = .); | ||||
|     KEEP (*(.preinit_array*)) | ||||
|     PROVIDE_HIDDEN (__preinit_array_end = .); | ||||
|   } >FLASH | ||||
|   .init_array : | ||||
|   { | ||||
|     PROVIDE_HIDDEN (__init_array_start = .); | ||||
|     KEEP (*(SORT(.init_array.*))) | ||||
|     KEEP (*(.init_array*)) | ||||
|     PROVIDE_HIDDEN (__init_array_end = .); | ||||
|   } >FLASH | ||||
|   .fini_array : | ||||
|   { | ||||
|     PROVIDE_HIDDEN (__fini_array_start = .); | ||||
|     KEEP (*(SORT(.fini_array.*))) | ||||
|     KEEP (*(.fini_array*)) | ||||
|     PROVIDE_HIDDEN (__fini_array_end = .); | ||||
|   } >FLASH | ||||
| 
 | ||||
|   /* used by the startup to initialize data */ | ||||
|   _sidata = LOADADDR(.data); | ||||
| 
 | ||||
|   /* Initialized data sections goes into RAM, load LMA copy after code */ | ||||
|   .data :  | ||||
|   { | ||||
|     . = ALIGN(4); | ||||
|     _sdata = .;        /* create a global symbol at data start */ | ||||
|     *(.data)           /* .data sections */ | ||||
|     *(.data*)          /* .data* sections */ | ||||
| 
 | ||||
|     . = ALIGN(4); | ||||
|     _edata = .;        /* define a global symbol at data end */ | ||||
|   } >RAM1 AT> FLASH | ||||
| 
 | ||||
|    | ||||
|   /* Uninitialized data section */ | ||||
|   . = ALIGN(4); | ||||
|   .bss : | ||||
|   { | ||||
|     /* This is used by the startup in order to initialize the .bss secion */ | ||||
|     _sbss = .;         /* define a global symbol at bss start */ | ||||
|     __bss_start__ = _sbss; | ||||
|     *(.bss) | ||||
|     *(.bss*) | ||||
|     *(COMMON) | ||||
| 
 | ||||
|     . = ALIGN(4); | ||||
|     _ebss = .;         /* define a global symbol at bss end */ | ||||
|     __bss_end__ = _ebss; | ||||
|   } >RAM1 | ||||
| 
 | ||||
|   /* User_heap_stack section, used to check that there is enough RAM left */ | ||||
|   ._user_heap_stack : | ||||
|   { | ||||
|     . = ALIGN(8); | ||||
|     PROVIDE ( end = . ); | ||||
|     PROVIDE ( _end = . ); | ||||
|     . = . + _Min_Heap_Size; | ||||
|     . = . + _Min_Stack_Size; | ||||
|     . = ALIGN(8); | ||||
|   } >RAM1 | ||||
| 
 | ||||
|    | ||||
| 
 | ||||
|   /* Remove information from the standard libraries */ | ||||
|   /DISCARD/ : | ||||
|   { | ||||
|     libc.a ( * ) | ||||
|     libm.a ( * ) | ||||
|     libgcc.a ( * ) | ||||
|   } | ||||
| 
 | ||||
|   .ARM.attributes 0       : { *(.ARM.attributes) } | ||||
|    MAPPING_TABLE (NOLOAD) : { *(MAPPING_TABLE) } >RAM_SHARED | ||||
|    MB_MEM1 (NOLOAD)       : { *(MB_MEM1) } >RAM_SHARED | ||||
|    MB_MEM2 (NOLOAD)       : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										264
									
								
								bootloader/targets/f7/target.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								bootloader/targets/f7/target.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,264 @@ | ||||
| #include <target.h> | ||||
| #include <stm32wbxx.h> | ||||
| #include <stm32wbxx_ll_system.h> | ||||
| #include <stm32wbxx_ll_bus.h> | ||||
| #include <stm32wbxx_ll_utils.h> | ||||
| #include <stm32wbxx_ll_rcc.h> | ||||
| #include <stm32wbxx_ll_rtc.h> | ||||
| #include <stm32wbxx_ll_pwr.h> | ||||
| #include <stm32wbxx_ll_gpio.h> | ||||
| #include <stm32wbxx_hal_flash.h> | ||||
| 
 | ||||
| #include <lib/toolbox/version.h> | ||||
| #include <furi-hal.h> | ||||
| #include <u8g2.h> | ||||
| 
 | ||||
| const uint8_t I_DFU_128x50[] = { | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x07, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x38, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC0, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x75, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x0A, 0x00, 0x00, 0x0F, 0x60, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xE0, 0x0F, 0x00, 0xC0, 0xE0, 0x4F, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x30, 0x1E, 0x90, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x18, 0x00, 0x8C, 0x01, 0xA0, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x81, 0xFF, 0x19, 0x00, 0x63, 0x00, 0xC0, 0xF0, 0x07, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x60, 0x5E, 0x1F, 0x80, 0x18, 0x00, 0xE0, 0x0E, 0x18, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x18, 0xAF, 0x0F, 0x40, 0x06, 0x00, 0xF8, 0x01, 0x20, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x06, 0x57, 0x01, 0x20, 0x01, 0x00, 0x78, 0x00, 0x3E, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x81, 0xAF, 0x02, 0x90, 0x00, 0x00, 0x38, 0x80, 0x41, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x80, 0x57, 0x01, 0x48, 0x00, 0x00, 0x10, 0x60, 0x40, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x10, 0x80, 0xAB, 0x00, 0x24, 0x00, 0x00, 0x08, 0x10, 0x40, | ||||
|     0x3F, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0xC0, 0x57, 0x01, 0x12, 0x00, 0x00, 0x04, 0x08, 0x40, | ||||
|     0xC0, 0x0F, 0x00, 0x00, 0xC0, 0x07, 0x03, 0xF0, 0xAB, 0x00, 0x0A, 0x00, 0x00, 0x02, 0x04, 0x40, | ||||
|     0x00, 0xF0, 0x1F, 0x80, 0x3F, 0xC0, 0x00, 0xFC, 0x55, 0x01, 0x05, 0xE0, 0x00, 0x01, 0x04, 0x40, | ||||
|     0x00, 0x00, 0xE0, 0x7F, 0x00, 0x30, 0x00, 0xFF, 0xAB, 0x00, 0x05, 0xE0, 0x80, 0x00, 0x02, 0x40, | ||||
|     0x0F, 0x00, 0x00, 0x00, 0x80, 0x0F, 0xE0, 0xCF, 0x55, 0x81, 0x02, 0xF0, 0x40, 0x00, 0x02, 0x40, | ||||
|     0xF0, 0x0F, 0x00, 0x00, 0x7F, 0x00, 0xFE, 0xC3, 0xAB, 0x80, 0x02, 0x78, 0x20, 0x00, 0x01, 0x40, | ||||
|     0x00, 0xF0, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xC0, 0xD5, 0x81, 0x01, 0x7E, 0x10, 0x80, 0x00, 0x20, | ||||
|     0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x0F, 0xE0, 0xFA, 0x83, 0xC1, 0x3F, 0x08, 0x80, 0x00, 0x20, | ||||
|     0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0xD8, 0x07, 0x83, 0xF1, 0x1F, 0x04, 0x40, 0x00, 0x20, | ||||
|     0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0xC7, 0x01, 0x83, 0xF1, 0x0F, 0x00, 0x20, 0x00, 0x10, | ||||
|     0xE0, 0xFF, 0xFF, 0xFF, 0x3F, 0xC0, 0x7F, 0x40, 0x80, 0x83, 0xE1, 0x01, 0x00, 0x20, 0x00, 0x18, | ||||
|     0xFC, 0xFF, 0xFF, 0xFF, 0x03, 0x3F, 0x00, 0x20, 0xFC, 0x83, 0x01, 0x00, 0x00, 0x10, 0x00, 0x18, | ||||
|     0xFF, 0xFF, 0xFF, 0x3F, 0xF0, 0x00, 0x00, 0x10, 0xD7, 0x01, 0x03, 0x00, 0x00, 0x08, 0x00, 0x1C, | ||||
|     0xFF, 0xFF, 0x01, 0x00, 0x0F, 0x00, 0x00, 0x88, 0xAB, 0x02, 0xE3, 0x01, 0x00, 0x08, 0x00, 0x0C, | ||||
|     0xFF, 0x07, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xC4, 0x55, 0x05, 0x1E, 0x00, 0x00, 0x04, 0x00, 0x0E, | ||||
|     0x7F, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0xA3, 0xAB, 0x02, 0x06, 0x00, 0x00, 0x02, 0x00, 0x0F, | ||||
|     0x0F, 0x00, 0x80, 0x03, 0x00, 0x00, 0xC0, 0x10, 0x57, 0x05, 0x02, 0x00, 0x00, 0x01, 0x80, 0x07, | ||||
|     0x03, 0x00, 0x70, 0x00, 0x00, 0x00, 0x30, 0x08, 0xAB, 0x0A, 0x02, 0x00, 0xC0, 0x00, 0xC0, 0x07, | ||||
|     0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x84, 0x57, 0x15, 0x01, 0x00, 0x30, 0x00, 0xE0, 0x07, | ||||
|     0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0xC3, 0xFF, 0x2A, 0x01, 0x00, 0x0C, 0x00, 0xF0, 0x0F, | ||||
|     0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0xC0, 0xE0, 0xFE, 0x55, 0x01, 0x82, 0x03, 0x00, 0xF8, 0x15, | ||||
|     0x00, 0x30, 0x00, 0x00, 0x00, 0x1C, 0x30, 0x78, 0xFE, 0xAA, 0x01, 0x7C, 0x00, 0x00, 0xFC, 0x23, | ||||
|     0x00, 0x0E, 0x00, 0x00, 0xC0, 0x03, 0x0C, 0x3C, 0x7F, 0x5D, 0x01, 0x00, 0x00, 0x00, 0xFF, 0x45, | ||||
|     0xC0, 0x01, 0x00, 0x00, 0x3E, 0x00, 0x02, 0x8F, 0xBF, 0xAE, 0x03, 0x00, 0x00, 0xC0, 0xFF, 0x82, | ||||
|     0x30, 0x00, 0x00, 0xC0, 0x01, 0x80, 0xC1, 0x43, 0xFE, 0x5D, 0x01, 0x00, 0x00, 0xF0, 0xFF, 0x05, | ||||
|     0x0F, 0x00, 0x80, 0x3F, 0x00, 0x60, 0xF0, 0x31, 0xF6, 0xAE, 0x03, 0x00, 0x00, 0xFA, 0xAF, 0x02, | ||||
|     0xFC, 0xFF, 0x7F, 0x00, 0x00, 0x18, 0x7C, 0x08, 0x23, 0xFF, 0x05, 0x00, 0x00, 0xFD, 0x55, 0x01, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x1F, 0x84, 0x30, 0xFE, 0x0A, 0x00, 0x00, 0xAA, 0xAA, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x80, 0xF1, 0x07, 0x43, 0x18, 0xFF, 0x15, 0x00, 0x00, 0x54, 0x15, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x80, 0x20, 0x8C, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
| }; | ||||
| 
 | ||||
| // Boot request enum
 | ||||
| #define BOOT_REQUEST_TAINTED 0x00000000 | ||||
| #define BOOT_REQUEST_CLEAN 0xDADEDADE | ||||
| #define BOOT_REQUEST_DFU 0xDF00B000 | ||||
| // Boot to DFU pin
 | ||||
| #define BOOT_DFU_PORT GPIOB | ||||
| #define BOOT_DFU_PIN LL_GPIO_PIN_11 | ||||
| // USB pins
 | ||||
| #define BOOT_USB_PORT GPIOA | ||||
| #define BOOT_USB_DM_PIN LL_GPIO_PIN_11 | ||||
| #define BOOT_USB_DP_PIN LL_GPIO_PIN_12 | ||||
| #define BOOT_USB_PIN (BOOT_USB_DM_PIN | BOOT_USB_DP_PIN) | ||||
| 
 | ||||
| #define RTC_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) | ||||
| 
 | ||||
| uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr); | ||||
| uint8_t u8x8_hw_spi_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr); | ||||
| 
 | ||||
| void target_led_control(char* c) { | ||||
|     furi_hal_light_set(LightRed, 0x00); | ||||
|     furi_hal_light_set(LightGreen, 0x00); | ||||
|     furi_hal_light_set(LightBlue, 0x00); | ||||
|     do { | ||||
|         if(*c == 'R') { | ||||
|             furi_hal_light_set(LightRed, 0xFF); | ||||
|         } else if(*c == 'G') { | ||||
|             furi_hal_light_set(LightGreen, 0xFF); | ||||
|         } else if(*c == 'B') { | ||||
|             furi_hal_light_set(LightBlue, 0xFF); | ||||
|         } else if(*c == '.') { | ||||
|             LL_mDelay(125); | ||||
|             furi_hal_light_set(LightRed, 0x00); | ||||
|             furi_hal_light_set(LightGreen, 0x00); | ||||
|             furi_hal_light_set(LightBlue, 0x00); | ||||
|             LL_mDelay(125); | ||||
|         } else if(*c == '-') { | ||||
|             LL_mDelay(250); | ||||
|             furi_hal_light_set(LightRed, 0x00); | ||||
|             furi_hal_light_set(LightGreen, 0x00); | ||||
|             furi_hal_light_set(LightBlue, 0x00); | ||||
|             LL_mDelay(250); | ||||
|         } else if(*c == '|') { | ||||
|             furi_hal_light_set(LightRed, 0x00); | ||||
|             furi_hal_light_set(LightGreen, 0x00); | ||||
|             furi_hal_light_set(LightBlue, 0x00); | ||||
|         } | ||||
|         c++; | ||||
|     } while(*c != 0); | ||||
| } | ||||
| 
 | ||||
| void target_clock_init() { | ||||
|     LL_Init1msTick(4000000); | ||||
|     LL_SetSystemCoreClock(4000000); | ||||
| 
 | ||||
|     LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); | ||||
|     LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); | ||||
|     LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); | ||||
|     LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOD); | ||||
|     LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOE); | ||||
|     LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOH); | ||||
| 
 | ||||
|     LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1); | ||||
|     LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2); | ||||
| } | ||||
| 
 | ||||
| void target_gpio_init() { | ||||
|     // USB D+
 | ||||
|     LL_GPIO_SetPinMode(BOOT_USB_PORT, BOOT_USB_DP_PIN, LL_GPIO_MODE_OUTPUT); | ||||
|     LL_GPIO_SetPinSpeed(BOOT_USB_PORT, BOOT_USB_DP_PIN, LL_GPIO_SPEED_FREQ_VERY_HIGH); | ||||
|     LL_GPIO_SetPinOutputType(BOOT_USB_PORT, BOOT_USB_DP_PIN, LL_GPIO_OUTPUT_OPENDRAIN); | ||||
|     // USB D-
 | ||||
|     LL_GPIO_SetPinMode(BOOT_USB_PORT, BOOT_USB_DM_PIN, LL_GPIO_MODE_OUTPUT); | ||||
|     LL_GPIO_SetPinSpeed(BOOT_USB_PORT, BOOT_USB_DM_PIN, LL_GPIO_SPEED_FREQ_VERY_HIGH); | ||||
|     LL_GPIO_SetPinOutputType(BOOT_USB_PORT, BOOT_USB_DM_PIN, LL_GPIO_OUTPUT_OPENDRAIN); | ||||
|     // Button: back
 | ||||
|     LL_GPIO_SetPinMode(BOOT_DFU_PORT, BOOT_DFU_PIN, LL_GPIO_MODE_INPUT); | ||||
|     LL_GPIO_SetPinPull(BOOT_DFU_PORT, BOOT_DFU_PIN, LL_GPIO_PULL_UP); | ||||
| } | ||||
| 
 | ||||
| void target_rtc_init() { | ||||
|     // LSE and RTC
 | ||||
|     LL_PWR_EnableBkUpAccess(); | ||||
|     if(!RTC_CLOCK_IS_READY()) { | ||||
|         // Start LSI1 needed for CSS
 | ||||
|         LL_RCC_LSI1_Enable(); | ||||
|         // Try to start LSE normal way
 | ||||
|         LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH); | ||||
|         LL_RCC_LSE_Enable(); | ||||
|         uint32_t c = 0; | ||||
|         while(!RTC_CLOCK_IS_READY() && c < 200) { | ||||
|             LL_mDelay(10); | ||||
|             c++; | ||||
|         } | ||||
|         // Plan B: reset backup domain
 | ||||
|         if(!RTC_CLOCK_IS_READY()) { | ||||
|             target_led_control("-R.R.R."); | ||||
|             LL_RCC_ForceBackupDomainReset(); | ||||
|             LL_RCC_ReleaseBackupDomainReset(); | ||||
|             NVIC_SystemReset(); | ||||
|         } | ||||
|         // Set RTC domain clock to LSE
 | ||||
|         LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); | ||||
|         // Enable LSE CSS
 | ||||
|         LL_RCC_LSE_EnableCSS(); | ||||
|     } | ||||
|     // Enable clocking
 | ||||
|     LL_RCC_EnableRTC(); | ||||
|     LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); | ||||
| } | ||||
| 
 | ||||
| void target_version_save(void) { | ||||
|     LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR1, (uint32_t)version_get()); | ||||
| } | ||||
| 
 | ||||
| void target_usb_wire_reset() { | ||||
|     LL_GPIO_ResetOutputPin(BOOT_USB_PORT, BOOT_USB_PIN); | ||||
| } | ||||
| 
 | ||||
| void target_display_init() { | ||||
|     // Prepare gpio
 | ||||
|     hal_gpio_init_simple(&gpio_display_rst, GpioModeOutputPushPull); | ||||
|     hal_gpio_init_simple(&gpio_display_di, GpioModeOutputPushPull); | ||||
|     // Initialize
 | ||||
|     u8g2_t fb; | ||||
|     u8g2_Setup_st7565_erc12864_alt_f(&fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); | ||||
|     u8g2_InitDisplay(&fb); | ||||
|     u8g2_SetContrast(&fb, 36); | ||||
|     // Create payload
 | ||||
|     u8g2_ClearBuffer(&fb); | ||||
|     u8g2_SetDrawColor(&fb, 0x01); | ||||
|     u8g2_SetFont(&fb, u8g2_font_helvB08_tf); | ||||
|     u8g2_DrawXBM(&fb, 0, 64 - 50, 128, 50, I_DFU_128x50); | ||||
|     u8g2_DrawStr(&fb, 2, 8, "Update & Recovery Mode"); | ||||
|     u8g2_DrawStr(&fb, 2, 21, "DFU started"); | ||||
|     // Send buffer
 | ||||
|     u8g2_SetPowerSave(&fb, 0); | ||||
|     u8g2_SendBuffer(&fb); | ||||
| } | ||||
| 
 | ||||
| void target_init() { | ||||
|     target_clock_init(); | ||||
|     target_gpio_init(); | ||||
|     furi_hal_init(); | ||||
|     target_led_control("RGB"); | ||||
|     target_rtc_init(); | ||||
|     target_version_save(); | ||||
|     target_usb_wire_reset(); | ||||
| 
 | ||||
|     // Errata 2.2.9, Flash OPTVERR flag is always set after system reset
 | ||||
|     __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); | ||||
| } | ||||
| 
 | ||||
| int target_is_dfu_requested() { | ||||
|     if(LL_RTC_BAK_GetRegister(RTC, LL_RTC_BKP_DR0) == BOOT_REQUEST_TAINTED) { | ||||
|         // Default system state is tainted
 | ||||
|         // We must ensure that MCU is cleanly booted
 | ||||
|         LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_CLEAN); | ||||
|         NVIC_SystemReset(); | ||||
|     } else if(LL_RTC_BAK_GetRegister(RTC, LL_RTC_BKP_DR0) == BOOT_REQUEST_DFU) { | ||||
|         return 1; | ||||
|     } | ||||
|     LL_mDelay(100); | ||||
|     if(!LL_GPIO_IsInputPinSet(BOOT_DFU_PORT, BOOT_DFU_PIN)) { | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| void target_switch(void* offset) { | ||||
|     asm volatile("ldr    r3, [%0]    \n" | ||||
|                  "msr    msp, r3     \n" | ||||
|                  "ldr    r3, [%1]    \n" | ||||
|                  "mov    pc, r3      \n" | ||||
|                  : | ||||
|                  : "r"(offset), "r"(offset + 0x4) | ||||
|                  : "r3"); | ||||
| } | ||||
| 
 | ||||
| void target_switch2dfu() { | ||||
|     target_led_control("B"); | ||||
|     furi_hal_light_set(LightBacklight, 0xFF); | ||||
|     target_display_init(); | ||||
|     // Mark system as tainted, it will be soon
 | ||||
|     LL_RTC_BAK_SetRegister(RTC, LL_RTC_BKP_DR0, BOOT_REQUEST_TAINTED); | ||||
|     // Remap memory to system bootloader
 | ||||
|     LL_SYSCFG_SetRemapMemory(LL_SYSCFG_REMAP_SYSTEMFLASH); | ||||
|     // Jump
 | ||||
|     target_switch(0x0); | ||||
| } | ||||
| 
 | ||||
| void target_switch2os() { | ||||
|     target_led_control("G"); | ||||
|     SCB->VTOR = OS_OFFSET; | ||||
|     target_switch((void*)(BOOT_ADDRESS + OS_OFFSET)); | ||||
| } | ||||
							
								
								
									
										48
									
								
								bootloader/targets/f7/target.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								bootloader/targets/f7/target.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| TOOLCHAIN = arm | ||||
| 
 | ||||
| BOOT_ADDRESS	= 0x08000000 | ||||
| FW_ADDRESS		= 0x08008000 | ||||
| OS_OFFSET		= 0x00008000 | ||||
| FLASH_ADDRESS	= 0x08000000 | ||||
| 
 | ||||
| OPENOCD_OPTS	= -f interface/stlink.cfg -c "transport select hla_swd" -f ../debug/stm32wbx.cfg -c "init" | ||||
| BOOT_CFLAGS		= -DBOOT_ADDRESS=$(BOOT_ADDRESS) -DFW_ADDRESS=$(FW_ADDRESS) -DOS_OFFSET=$(OS_OFFSET) | ||||
| MCU_FLAGS		= -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard | ||||
| 
 | ||||
| CFLAGS			+= $(MCU_FLAGS) $(BOOT_CFLAGS) -DSTM32WB55xx -Wall -fdata-sections -ffunction-sections | ||||
| LDFLAGS			+= $(MCU_FLAGS) -specs=nosys.specs -specs=nano.specs  | ||||
| 
 | ||||
| CUBE_DIR		= $(PROJECT_ROOT)/lib/STM32CubeWB | ||||
| 
 | ||||
| # ST HAL
 | ||||
| CFLAGS			+=  -DUSE_FULL_LL_DRIVER | ||||
| ASM_SOURCES		+= $(CUBE_DIR)/Drivers/CMSIS/Device/ST/STM32WBxx/Source/Templates/gcc/startup_stm32wb55xx_cm4.s | ||||
| C_SOURCES		+= $(CUBE_DIR)/Drivers/CMSIS/Device/ST/STM32WBxx/Source/Templates/system_stm32wbxx.c | ||||
| C_SOURCES		+= $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_ll_utils.c | ||||
| C_SOURCES		+= $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_ll_gpio.c | ||||
| C_SOURCES		+= $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_ll_i2c.c | ||||
| C_SOURCES		+= $(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Src/stm32wbxx_ll_spi.c | ||||
| 
 | ||||
| CFLAGS			+= -I$(CUBE_DIR)/Drivers/CMSIS/Include | ||||
| CFLAGS			+= -I$(CUBE_DIR)/Drivers/CMSIS/Device/ST/STM32WBxx/Include | ||||
| CFLAGS			+= -I$(CUBE_DIR)/Drivers/STM32WBxx_HAL_Driver/Inc | ||||
| 
 | ||||
| LDFLAGS			+= -T$(TARGET_DIR)/stm32wb55xx_flash_cm4.ld | ||||
| 
 | ||||
| # Drivers
 | ||||
| DRIVERS_DIR		= $(PROJECT_ROOT)//lib/drivers | ||||
| CFLAGS			+= -I$(DRIVERS_DIR) | ||||
| C_SOURCES		+= $(DRIVERS_DIR)/lp5562.c | ||||
| 
 | ||||
| # API-HAL
 | ||||
| CFLAGS			+= -I$(TARGET_DIR)/furi-hal | ||||
| C_SOURCES		+= $(wildcard $(TARGET_DIR)/furi-hal/*.c) | ||||
| 
 | ||||
| # Version generation
 | ||||
| C_SOURCES		+= $(PROJECT_ROOT)/lib/toolbox/version.c | ||||
| 
 | ||||
| ASM_SOURCES		+= $(wildcard $(TARGET_DIR)/*.s) | ||||
| C_SOURCES		+= $(wildcard $(TARGET_DIR)/*.c) | ||||
| CPP_SOURCES		+= $(wildcard $(TARGET_DIR)/*.cpp) | ||||
| 
 | ||||
| SVD_FILE		= $(PROJECT_ROOT)/debug/STM32WB55_CM4.svd | ||||
| @ -19,6 +19,15 @@ | ||||
|     }) | ||||
| #endif | ||||
| 
 | ||||
| #ifndef ROUND_UP_TO | ||||
| #define ROUND_UP_TO(a, b)       \ | ||||
|     ({                          \ | ||||
|         __typeof__(a) _a = (a); \ | ||||
|         __typeof__(b) _b = (b); \ | ||||
|         _a / _b + !!(_a % _b);  \ | ||||
|     }) | ||||
| #endif | ||||
| 
 | ||||
| #ifndef CLAMP | ||||
| #define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower))) | ||||
| #endif | ||||
| @ -40,3 +49,11 @@ | ||||
|         y = SWAP;           \ | ||||
|     } while(0) | ||||
| #endif | ||||
| 
 | ||||
| #ifndef PLACE_IN_SECTION | ||||
| #define PLACE_IN_SECTION(x) __attribute__((section(x))) | ||||
| #endif | ||||
| 
 | ||||
| #ifndef ALIGN | ||||
| #define ALIGN(n) __attribute__((aligned(n))) | ||||
| #endif | ||||
|  | ||||
| @ -6,7 +6,7 @@ CLANG_FORMAT_BIN="/usr/bin/clang-format-12" | ||||
| 
 | ||||
| PROJECT_DIR=$(pwd) | ||||
| 
 | ||||
| cd $PROJECT_DIR | ||||
| cd "$PROJECT_DIR" || exit  | ||||
| 
 | ||||
| echo "RUN C\C++ SYNTAX CHECK" | ||||
| C_FILES=$(find . \ | ||||
| @ -34,7 +34,7 @@ fi | ||||
| 
 | ||||
| read -p "Do you want fix syntax? (y/n): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1 | ||||
| 
 | ||||
| cd $PROJECT_DIR | ||||
| cd "$PROJECT_DIR" || exit  | ||||
| 
 | ||||
| # We use root in container and clang-format rewriting files. We'll need change owner to original | ||||
| local_user=$(stat -c '%u' .clang-format) | ||||
|  | ||||
| @ -118,6 +118,14 @@ to exclude the API function. */ | ||||
| #define INCLUDE_xTaskGetSchedulerState       1 | ||||
| #define INCLUDE_xTimerPendFunctionCall       1 | ||||
| 
 | ||||
| /* CMSIS-RTOS V2 flags */ | ||||
| #define configUSE_OS2_THREAD_SUSPEND_RESUME  1 | ||||
| #define configUSE_OS2_THREAD_ENUMERATE       1 | ||||
| #define configUSE_OS2_EVENTFLAGS_FROM_ISR    1 | ||||
| #define configUSE_OS2_THREAD_FLAGS           1 | ||||
| #define configUSE_OS2_TIMER                  1 | ||||
| #define configUSE_OS2_MUTEX                  1 | ||||
| 
 | ||||
| /*
 | ||||
|  * The CMSIS-RTOS V2 FreeRTOS wrapper is dependent on the heap implementation used | ||||
|  * by the application thus the correct define need to be enabled below | ||||
|  | ||||
| @ -6,105 +6,20 @@ | ||||
| #include "ble.h" | ||||
| #include "tl.h" | ||||
| #include "app_ble.h" | ||||
| 
 | ||||
| #include "cmsis_os.h" | ||||
| #include "shci.h" | ||||
| #include "otp.h" | ||||
| #include "dis_app.h" | ||||
| #include "hrs_app.h" | ||||
| #include "cmsis_os.h" | ||||
| 
 | ||||
| #include <furi-hal.h> | ||||
| 
 | ||||
| typedef struct _tSecurityParams { | ||||
|   uint8_t ioCapability; | ||||
|   uint8_t mitm_mode; | ||||
|   uint8_t bonding_mode; | ||||
|   uint8_t Use_Fixed_Pin; | ||||
|   uint8_t encryptionKeySizeMin; | ||||
|   uint8_t encryptionKeySizeMax; | ||||
|   uint32_t Fixed_Pin; | ||||
|   uint8_t initiateSecurity; | ||||
| } tSecurityParams; | ||||
| 
 | ||||
| typedef struct _tBLEProfileGlobalContext { | ||||
|   tSecurityParams bleSecurityParam; | ||||
|   uint16_t gapServiceHandle; | ||||
|   uint16_t devNameCharHandle; | ||||
|   uint16_t appearanceCharHandle; | ||||
|   uint16_t connectionHandle; | ||||
|   uint8_t advtServUUIDlen; | ||||
|   uint8_t advtServUUID[100]; | ||||
| } BleGlobalContext_t; | ||||
| 
 | ||||
| typedef struct { | ||||
|   BleGlobalContext_t BleApplicationContext_legacy; | ||||
|   APP_BLE_ConnStatus_t Device_Connection_Status; | ||||
|   uint8_t Advertising_mgr_timer_Id; | ||||
| } BleApplicationContext_t; | ||||
| 
 | ||||
| 
 | ||||
| #define FAST_ADV_TIMEOUT               (30*1000*1000/CFG_TS_TICK_VAL) /**< 30s */ | ||||
| #define INITIAL_ADV_TIMEOUT            (60*1000*1000/CFG_TS_TICK_VAL) /**< 60s */ | ||||
| 
 | ||||
| #define BD_ADDR_SIZE_LOCAL    6 | ||||
| 
 | ||||
| #define LED_ON_TIMEOUT                 (0.005*1000*1000/CFG_TS_TICK_VAL) /**< 5ms */ | ||||
| 
 | ||||
| PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t BleCmdBuffer; | ||||
| 
 | ||||
| static const uint8_t M_bd_addr[BD_ADDR_SIZE_LOCAL] = | ||||
|     { | ||||
|         (uint8_t)((CFG_ADV_BD_ADDRESS & 0x0000000000FF)), | ||||
|         (uint8_t)((CFG_ADV_BD_ADDRESS & 0x00000000FF00) >> 8), | ||||
|         (uint8_t)((CFG_ADV_BD_ADDRESS & 0x000000FF0000) >> 16), | ||||
|         (uint8_t)((CFG_ADV_BD_ADDRESS & 0x0000FF000000) >> 24), | ||||
|         (uint8_t)((CFG_ADV_BD_ADDRESS & 0x00FF00000000) >> 32), | ||||
|         (uint8_t)((CFG_ADV_BD_ADDRESS & 0xFF0000000000) >> 40) | ||||
|     }; | ||||
| 
 | ||||
| static uint8_t bd_addr_udn[BD_ADDR_SIZE_LOCAL]; | ||||
| 
 | ||||
| static const uint8_t BLE_CFG_IR_VALUE[16] = CFG_BLE_IRK; | ||||
| static const uint8_t BLE_CFG_ER_VALUE[16] = CFG_BLE_ERK; | ||||
| 
 | ||||
| PLACE_IN_SECTION("TAG_OTA_END") const uint32_t MagicKeywordValue = 0x94448A29 ; | ||||
| PLACE_IN_SECTION("TAG_OTA_START") const uint32_t MagicKeywordAddress = (uint32_t)&MagicKeywordValue; | ||||
| 
 | ||||
| PLACE_IN_SECTION("BLE_APP_CONTEXT") static BleApplicationContext_t BleApplicationContext; | ||||
| PLACE_IN_SECTION("BLE_APP_CONTEXT") static uint16_t AdvIntervalMin, AdvIntervalMax; | ||||
| 
 | ||||
| uint8_t  manuf_data[14] = { | ||||
|     sizeof(manuf_data)-1, AD_TYPE_MANUFACTURER_SPECIFIC_DATA, | ||||
|     0x01/*SKD version */, | ||||
|     0x00 /* Generic*/, | ||||
|     0x00 /* GROUP A Feature  */, | ||||
|     0x00 /* GROUP A Feature */, | ||||
|     0x00 /* GROUP B Feature */, | ||||
|     0x00 /* GROUP B Feature */, | ||||
|     0x00, /* BLE MAC start -MSB */ | ||||
|     0x00, | ||||
|     0x00, | ||||
|     0x00, | ||||
|     0x00, | ||||
|     0x00, /* BLE MAC stop */ | ||||
| 
 | ||||
| }; | ||||
| // PLACE_IN_SECTION("TAG_OTA_END") const uint32_t MagicKeywordValue = 0x94448A29 ;
 | ||||
| // PLACE_IN_SECTION("TAG_OTA_START") const uint32_t MagicKeywordAddress = (uint32_t)&MagicKeywordValue;
 | ||||
| 
 | ||||
| osMutexId_t MtxHciId; | ||||
| osSemaphoreId_t SemHciId; | ||||
| osThreadId_t AdvUpdateProcessId; | ||||
| osThreadId_t HciUserEvtProcessId; | ||||
| 
 | ||||
| const osThreadAttr_t AdvUpdateProcess_attr = { | ||||
|     .name = CFG_ADV_UPDATE_PROCESS_NAME, | ||||
|     .attr_bits = CFG_ADV_UPDATE_PROCESS_ATTR_BITS, | ||||
|     .cb_mem = CFG_ADV_UPDATE_PROCESS_CB_MEM, | ||||
|     .cb_size = CFG_ADV_UPDATE_PROCESS_CB_SIZE, | ||||
|     .stack_mem = CFG_ADV_UPDATE_PROCESS_STACK_MEM, | ||||
|     .priority = CFG_ADV_UPDATE_PROCESS_PRIORITY, | ||||
|     .stack_size = CFG_ADV_UPDATE_PROCESS_STACK_SIZE | ||||
| }; | ||||
| 
 | ||||
| const osThreadAttr_t HciUserEvtProcess_attr = { | ||||
|     .name = CFG_HCI_USER_EVT_PROCESS_NAME, | ||||
|     .attr_bits = CFG_HCI_USER_EVT_PROCESS_ATTR_BITS, | ||||
| @ -120,14 +35,6 @@ static void HciUserEvtProcess(void *argument); | ||||
| static void BLE_UserEvtRx( void * pPayload ); | ||||
| static void BLE_StatusNot( HCI_TL_CmdStatus_t status ); | ||||
| static void Ble_Tl_Init( void ); | ||||
| static void Ble_Hci_Gap_Gatt_Init(); | ||||
| static const uint8_t* BleGetBdAddress( void ); | ||||
| static void Adv_Request( APP_BLE_ConnStatus_t New_Status ); | ||||
| static void Add_Advertisment_Service_UUID( uint16_t servUUID ); | ||||
| static void Adv_Mgr( void ); | ||||
| static void AdvUpdateProcess(void *argument); | ||||
| static void Adv_Update( void ); | ||||
| 
 | ||||
| 
 | ||||
| bool APP_BLE_Init() { | ||||
|   SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { | ||||
| @ -160,278 +67,6 @@ bool APP_BLE_Init() { | ||||
|   return (SHCI_C2_BLE_Init( &ble_init_cmd_packet ) == SHCI_Success); | ||||
| } | ||||
| 
 | ||||
| bool APP_BLE_Start() { | ||||
|   if (APPE_Status() != BleGlueStatusStarted) { | ||||
|     return false; | ||||
|   } | ||||
|   // Initialization of HCI & GATT & GAP layer
 | ||||
|   Ble_Hci_Gap_Gatt_Init(); | ||||
|   // Initialization of the BLE Services
 | ||||
|   SVCCTL_Init(); | ||||
|   // Initialization of the BLE App Context
 | ||||
|   BleApplicationContext.Device_Connection_Status = APP_BLE_IDLE; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.connectionHandle = 0xFFFF; | ||||
|   // From here, all initialization are BLE application specific
 | ||||
|   AdvUpdateProcessId = osThreadNew(AdvUpdateProcess, NULL, &AdvUpdateProcess_attr); | ||||
| 
 | ||||
|   // Initialization of ADV - Ad Manufacturer Element - Support OTA Bit Masks
 | ||||
| #if(BLE_CFG_OTA_REBOOT_CHAR != 0) | ||||
|   manuf_data[sizeof(manuf_data)-8] = CFG_FEATURE_OTA_REBOOT; | ||||
| #endif | ||||
| 
 | ||||
|   // Initialize DIS Application
 | ||||
|   DISAPP_Init(); | ||||
|   // Initialize HRS Application
 | ||||
|   HRSAPP_Init(); | ||||
|   // Create timer to handle the connection state machine
 | ||||
|   HW_TS_Create(CFG_TIM_PROC_ID_ISR, &(BleApplicationContext.Advertising_mgr_timer_Id), hw_ts_SingleShot, Adv_Mgr); | ||||
| 
 | ||||
|   // Make device discoverable
 | ||||
|   BleApplicationContext.BleApplicationContext_legacy.advtServUUID[0] = AD_TYPE_16_BIT_SERV_UUID; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.advtServUUIDlen = 1; | ||||
|   Add_Advertisment_Service_UUID(HEART_RATE_SERVICE_UUID); | ||||
|   /* Initialize intervals for reconnexion without intervals update */ | ||||
|   AdvIntervalMin = CFG_FAST_CONN_ADV_INTERVAL_MIN; | ||||
|   AdvIntervalMax = CFG_FAST_CONN_ADV_INTERVAL_MAX; | ||||
| 
 | ||||
|   Adv_Request(APP_BLE_FAST_ADV); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | ||||
| { | ||||
|   hci_event_pckt *event_pckt; | ||||
|   evt_le_meta_event *meta_evt; | ||||
|   evt_blue_aci *blue_evt; | ||||
|   hci_le_phy_update_complete_event_rp0 *evt_le_phy_update_complete; | ||||
|   uint8_t TX_PHY, RX_PHY; | ||||
|   tBleStatus ret = BLE_STATUS_INVALID_PARAMS; | ||||
| 
 | ||||
|   event_pckt = (hci_event_pckt*) ((hci_uart_pckt *) pckt)->data; | ||||
| 
 | ||||
|   switch (event_pckt->evt) { | ||||
|     case EVT_DISCONN_COMPLETE: | ||||
|     { | ||||
|       hci_disconnection_complete_event_rp0 *disconnection_complete_event; | ||||
|       disconnection_complete_event = (hci_disconnection_complete_event_rp0 *) event_pckt->data; | ||||
| 
 | ||||
|       if (disconnection_complete_event->Connection_Handle == BleApplicationContext.BleApplicationContext_legacy.connectionHandle) { | ||||
|         BleApplicationContext.BleApplicationContext_legacy.connectionHandle = 0; | ||||
|         BleApplicationContext.Device_Connection_Status = APP_BLE_IDLE; | ||||
|         APP_DBG_MSG("\r\n\r** DISCONNECTION EVENT WITH CLIENT \r\n"); | ||||
|       } | ||||
|       /* restart advertising */ | ||||
|       Adv_Request(APP_BLE_FAST_ADV); | ||||
|       furi_hal_power_insomnia_exit(); | ||||
|     } | ||||
|     break; /* EVT_DISCONN_COMPLETE */ | ||||
| 
 | ||||
|     case EVT_LE_META_EVENT: | ||||
|     { | ||||
|       meta_evt = (evt_le_meta_event*) event_pckt->data; | ||||
|       switch (meta_evt->subevent) | ||||
|       { | ||||
|         case EVT_LE_CONN_UPDATE_COMPLETE: | ||||
|           APP_DBG_MSG("\r\n\r** CONNECTION UPDATE EVENT WITH CLIENT \r\n"); | ||||
| 
 | ||||
|           /* USER CODE BEGIN EVT_LE_CONN_UPDATE_COMPLETE */ | ||||
| 
 | ||||
|           /* USER CODE END EVT_LE_CONN_UPDATE_COMPLETE */ | ||||
|           break; | ||||
|         case EVT_LE_PHY_UPDATE_COMPLETE: | ||||
|           APP_DBG_MSG("EVT_UPDATE_PHY_COMPLETE \r\n"); | ||||
|           evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*)meta_evt->data; | ||||
|           if (evt_le_phy_update_complete->Status == 0) | ||||
|           { | ||||
|             APP_DBG_MSG("EVT_UPDATE_PHY_COMPLETE, status ok \r\n"); | ||||
|           } | ||||
|           else | ||||
|           { | ||||
|             APP_DBG_MSG("EVT_UPDATE_PHY_COMPLETE, status nok \r\n"); | ||||
|           } | ||||
| 
 | ||||
|           ret = hci_le_read_phy(BleApplicationContext.BleApplicationContext_legacy.connectionHandle,&TX_PHY,&RX_PHY); | ||||
|           if (ret == BLE_STATUS_SUCCESS) | ||||
|           { | ||||
|             APP_DBG_MSG("Read_PHY success \r\n"); | ||||
| 
 | ||||
|             if ((TX_PHY == TX_2M) && (RX_PHY == RX_2M)) | ||||
|             { | ||||
|               APP_DBG_MSG("PHY Param  TX= %d, RX= %d \r\n", TX_PHY, RX_PHY); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|               APP_DBG_MSG("PHY Param  TX= %d, RX= %d \r\n", TX_PHY, RX_PHY); | ||||
|             } | ||||
|           } | ||||
|           else | ||||
|           { | ||||
|             APP_DBG_MSG("Read conf not succeess \r\n"); | ||||
|           } | ||||
|           break; | ||||
|         case EVT_LE_CONN_COMPLETE: | ||||
|         { | ||||
|           furi_hal_power_insomnia_enter(); | ||||
|           hci_le_connection_complete_event_rp0 *connection_complete_event; | ||||
| 
 | ||||
|           /**
 | ||||
|            * The connection is done, there is no need anymore to schedule the LP ADV | ||||
|            */ | ||||
|           connection_complete_event = (hci_le_connection_complete_event_rp0 *) meta_evt->data; | ||||
| 
 | ||||
|           HW_TS_Stop(BleApplicationContext.Advertising_mgr_timer_Id); | ||||
| 
 | ||||
|           APP_DBG_MSG("EVT_LE_CONN_COMPLETE for connection handle 0x%x\r\n", connection_complete_event->Connection_Handle); | ||||
|           if (BleApplicationContext.Device_Connection_Status == APP_BLE_LP_CONNECTING) | ||||
|           { | ||||
|             /* Connection as client */ | ||||
|             BleApplicationContext.Device_Connection_Status = APP_BLE_CONNECTED_CLIENT; | ||||
|           } | ||||
|           else | ||||
|           { | ||||
|             /* Connection as server */ | ||||
|             BleApplicationContext.Device_Connection_Status = APP_BLE_CONNECTED_SERVER; | ||||
|           } | ||||
|           BleApplicationContext.BleApplicationContext_legacy.connectionHandle = connection_complete_event->Connection_Handle; | ||||
|         } | ||||
|         break; /* HCI_EVT_LE_CONN_COMPLETE */ | ||||
|         default: | ||||
|           break; | ||||
|       } | ||||
|     } | ||||
|     break; /* HCI_EVT_LE_META_EVENT */ | ||||
| 
 | ||||
|     case EVT_VENDOR: | ||||
|       blue_evt = (evt_blue_aci*) event_pckt->data; | ||||
|       switch (blue_evt->ecode) { | ||||
|         aci_gap_pairing_complete_event_rp0 *pairing_complete; | ||||
| 
 | ||||
|       case EVT_BLUE_GAP_LIMITED_DISCOVERABLE:  | ||||
|         APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_LIMITED_DISCOVERABLE \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_LIMITED_DISCOVERABLE */ | ||||
|            | ||||
|       case EVT_BLUE_GAP_PASS_KEY_REQUEST:   | ||||
|         APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_PASS_KEY_REQUEST \r\n"); | ||||
| 
 | ||||
|         aci_gap_pass_key_resp(BleApplicationContext.BleApplicationContext_legacy.connectionHandle,123456); | ||||
| 
 | ||||
|         APP_DBG_MSG("\r\n\r** aci_gap_pass_key_resp \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_PASS_KEY_REQUEST */ | ||||
| 
 | ||||
|       case EVT_BLUE_GAP_AUTHORIZATION_REQUEST:     | ||||
|         APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_AUTHORIZATION_REQUEST \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_AUTHORIZATION_REQUEST */ | ||||
| 
 | ||||
|       case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED:    | ||||
|         APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED */ | ||||
| 
 | ||||
|       case EVT_BLUE_GAP_BOND_LOST:     | ||||
|         APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_BOND_LOST \r\n"); | ||||
|           aci_gap_allow_rebond(BleApplicationContext.BleApplicationContext_legacy.connectionHandle); | ||||
|         APP_DBG_MSG("\r\n\r** Send allow rebond \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_BOND_LOST */ | ||||
| 
 | ||||
|       case EVT_BLUE_GAP_DEVICE_FOUND:   | ||||
|         APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_DEVICE_FOUND \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_DEVICE_FOUND */ | ||||
| 
 | ||||
|       case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: | ||||
|          APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_DEVICE_FOUND \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_DEVICE_FOUND */ | ||||
|        | ||||
|       case (EVT_BLUE_GAP_KEYPRESS_NOTIFICATION): | ||||
|          APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_KEYPRESS_NOTIFICATION \r\n"); | ||||
|           break; /* EVT_BLUE_GAP_KEY_PRESS_NOTIFICATION */     | ||||
| 
 | ||||
|        case (EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE): | ||||
|           APP_DBG_MSG("numeric_value = %ld\r\n", | ||||
|                       ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value); | ||||
| 
 | ||||
|           APP_DBG_MSG("Hex_value = %lx\r\n", | ||||
|                       ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value); | ||||
| 
 | ||||
|           aci_gap_numeric_comparison_value_confirm_yesno(BleApplicationContext.BleApplicationContext_legacy.connectionHandle, 1); /* CONFIRM_YES = 1 */ | ||||
| 
 | ||||
|           APP_DBG_MSG("\r\n\r** aci_gap_numeric_comparison_value_confirm_yesno-->YES \r\n"); | ||||
|           break; | ||||
| 
 | ||||
|         case (EVT_BLUE_GAP_PAIRING_CMPLT): | ||||
|           { | ||||
|             pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data; | ||||
| 
 | ||||
|             APP_DBG_MSG("BLE_CTRL_App_Notification: EVT_BLUE_GAP_PAIRING_CMPLT, pairing_complete->Status = %d\r\n",pairing_complete->Status); | ||||
|             if (pairing_complete->Status == 0) { | ||||
|               APP_DBG_MSG("\r\n\r** Pairing OK \r\n"); | ||||
|             } else { | ||||
|               APP_DBG_MSG("\r\n\r** Pairing KO \r\n"); | ||||
|             } | ||||
|           } | ||||
|           break; | ||||
| 
 | ||||
|       /* USER CODE END ecode */ | ||||
|         case EVT_BLUE_GAP_PROCEDURE_COMPLETE: | ||||
|           APP_DBG_MSG("\r\n\r** EVT_BLUE_GAP_PROCEDURE_COMPLETE \r\n"); | ||||
|           break; | ||||
|       } | ||||
|       break; /* EVT_VENDOR */ | ||||
|       default: | ||||
|         break; | ||||
|   } | ||||
| 
 | ||||
|   return (SVCCTL_UserEvtFlowEnable); | ||||
| } | ||||
| 
 | ||||
| APP_BLE_ConnStatus_t APP_BLE_Get_Server_Connection_Status() { | ||||
|     return BleApplicationContext.Device_Connection_Status; | ||||
| } | ||||
| 
 | ||||
| /* USER CODE BEGIN FD*/ | ||||
| void APP_BLE_Key_Button1_Action() { | ||||
|   tBleStatus ret = BLE_STATUS_INVALID_PARAMS; | ||||
|   ret = aci_gap_clear_security_db(); | ||||
|   if (ret == BLE_STATUS_SUCCESS) { | ||||
|     APP_DBG_MSG("Successfully aci_gap_clear_security_db()\r\n"); | ||||
|   } else { | ||||
|     APP_DBG_MSG("aci_gap_clear_security_db() Failed , result: %d \r\n", ret); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void APP_BLE_Key_Button2_Action() { | ||||
|   tBleStatus ret = BLE_STATUS_INVALID_PARAMS; | ||||
|   ret = aci_gap_slave_security_req(BleApplicationContext.BleApplicationContext_legacy.connectionHandle);  | ||||
|   if (ret == BLE_STATUS_SUCCESS) { | ||||
|     APP_DBG_MSG("Successfully aci_gap_slave_security_req()"); | ||||
|   } else { | ||||
|     APP_DBG_MSG("aci_gap_slave_security_req() Failed , result: %d \r\n", ret); | ||||
|   } | ||||
| } | ||||
|    | ||||
| void APP_BLE_Key_Button3_Action() { | ||||
|   uint8_t TX_PHY, RX_PHY; | ||||
|   tBleStatus ret = BLE_STATUS_INVALID_PARAMS; | ||||
|   ret = hci_le_read_phy(BleApplicationContext.BleApplicationContext_legacy.connectionHandle,&TX_PHY,&RX_PHY); | ||||
|   if (ret == BLE_STATUS_SUCCESS) { | ||||
|     APP_DBG_MSG("Read_PHY success \r\n"); | ||||
|     APP_DBG_MSG("PHY Param  TX= %d, RX= %d \r\n", TX_PHY, RX_PHY); | ||||
|     if ((TX_PHY == TX_2M) && (RX_PHY == RX_2M)) { | ||||
|       APP_DBG_MSG("hci_le_set_phy PHY Param  TX= %d, RX= %d \r\n", TX_1M, RX_1M); | ||||
|       ret = hci_le_set_phy(BleApplicationContext.BleApplicationContext_legacy.connectionHandle,ALL_PHYS_PREFERENCE,TX_1M,RX_1M,0); | ||||
|     } else { | ||||
|       APP_DBG_MSG("hci_le_set_phy PHY Param  TX= %d, RX= %d \r\n", TX_2M_PREFERRED, RX_2M_PREFERRED); | ||||
|       ret = hci_le_set_phy(BleApplicationContext.BleApplicationContext_legacy.connectionHandle,ALL_PHYS_PREFERENCE,TX_2M_PREFERRED,RX_2M_PREFERRED,0); | ||||
|     }  | ||||
|   } else { | ||||
|     APP_DBG_MSG("Read conf not succeess \r\n"); | ||||
|   } | ||||
| 
 | ||||
|   if (ret == BLE_STATUS_SUCCESS) { | ||||
|     APP_DBG_MSG("set PHY cmd ok\r\n"); | ||||
|   } else { | ||||
|     APP_DBG_MSG("set PHY cmd NOK\r\n"); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void Ble_Tl_Init( void ) { | ||||
|   HCI_TL_HciInitConf_t Hci_Tl_Init_Conf; | ||||
| 
 | ||||
| @ -443,308 +78,6 @@ static void Ble_Tl_Init( void ) { | ||||
|   hci_init(BLE_UserEvtRx, (void*) &Hci_Tl_Init_Conf); | ||||
| } | ||||
| 
 | ||||
| static void Ble_Hci_Gap_Gatt_Init() { | ||||
|   uint8_t role; | ||||
|   uint16_t gap_service_handle, gap_dev_name_char_handle, gap_appearance_char_handle; | ||||
|   const uint8_t *bd_addr; | ||||
|   uint32_t srd_bd_addr[2]; | ||||
|   uint16_t appearance[1] = { BLE_CFG_GAP_APPEARANCE }; | ||||
| 
 | ||||
|   /*HCI Reset to synchronise BLE Stack*/ | ||||
|   hci_reset(); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Write the BD Address | ||||
|    */ | ||||
|   bd_addr = BleGetBdAddress(); | ||||
|   aci_hal_write_config_data(CONFIG_DATA_PUBADDR_OFFSET, | ||||
|                             CONFIG_DATA_PUBADDR_LEN, | ||||
|                             (uint8_t*) bd_addr); | ||||
| 
 | ||||
|   /* BLE MAC in ADV Packet */ | ||||
|   manuf_data[ sizeof(manuf_data)-6] = bd_addr[5]; | ||||
|   manuf_data[ sizeof(manuf_data)-5] = bd_addr[4]; | ||||
|   manuf_data[ sizeof(manuf_data)-4] = bd_addr[3]; | ||||
|   manuf_data[ sizeof(manuf_data)-3] = bd_addr[2]; | ||||
|   manuf_data[ sizeof(manuf_data)-2] = bd_addr[1]; | ||||
|   manuf_data[ sizeof(manuf_data)-1] = bd_addr[0]; | ||||
| 
 | ||||
|   /**
 | ||||
|    * Write Identity root key used to derive LTK and CSRK | ||||
|    */ | ||||
|     aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, | ||||
|     CONFIG_DATA_IR_LEN, | ||||
|                             (uint8_t*) BLE_CFG_IR_VALUE); | ||||
| 
 | ||||
|    /**
 | ||||
|    * Write Encryption root key used to derive LTK and CSRK | ||||
|    */ | ||||
|     aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, | ||||
|     CONFIG_DATA_ER_LEN, | ||||
|                             (uint8_t*) BLE_CFG_ER_VALUE); | ||||
| 
 | ||||
|    /**
 | ||||
|    * Write random bd_address | ||||
|    */ | ||||
|    /* random_bd_address = R_bd_address;
 | ||||
|     aci_hal_write_config_data(CONFIG_DATA_RANDOM_ADDRESS_WR, | ||||
|     CONFIG_DATA_RANDOM_ADDRESS_LEN, | ||||
|                             (uint8_t*) random_bd_address); | ||||
|   */ | ||||
| 
 | ||||
|   /**
 | ||||
|    * Static random Address | ||||
|    * The two upper bits shall be set to 1 | ||||
|    * The lowest 32bits is read from the UDN to differentiate between devices | ||||
|    * The RNG may be used to provide a random number on each power on | ||||
|    */ | ||||
|   srd_bd_addr[1] =  0x0000ED6E; | ||||
|   srd_bd_addr[0] =  LL_FLASH_GetUDN( ); | ||||
|   aci_hal_write_config_data( CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr ); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Write Identity root key used to derive LTK and CSRK | ||||
|    */ | ||||
|     aci_hal_write_config_data( CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*)BLE_CFG_IR_VALUE ); | ||||
| 
 | ||||
|    /**
 | ||||
|    * Write Encryption root key used to derive LTK and CSRK | ||||
|    */ | ||||
|     aci_hal_write_config_data( CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*)BLE_CFG_ER_VALUE ); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Set TX Power to 0dBm. | ||||
|    */ | ||||
|   aci_hal_set_tx_power_level(1, CFG_TX_POWER); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Initialize GATT interface | ||||
|    */ | ||||
|   aci_gatt_init(); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Initialize GAP interface | ||||
|    */ | ||||
|   role = 0; | ||||
| 
 | ||||
| #if (BLE_CFG_PERIPHERAL == 1) | ||||
|   role |= GAP_PERIPHERAL_ROLE; | ||||
| #endif | ||||
| 
 | ||||
| #if (BLE_CFG_CENTRAL == 1) | ||||
|   role |= GAP_CENTRAL_ROLE; | ||||
| #endif | ||||
| 
 | ||||
|   if (role > 0) | ||||
|   { | ||||
|     const char *name = furi_hal_version_get_device_name_ptr(); | ||||
|     aci_gap_init(role, 0, | ||||
|                  strlen(name), | ||||
|                  &gap_service_handle, &gap_dev_name_char_handle, &gap_appearance_char_handle); | ||||
| 
 | ||||
|     if (aci_gatt_update_char_value(gap_service_handle, gap_dev_name_char_handle, 0, strlen(name), (uint8_t *) name)) | ||||
|     { | ||||
|       BLE_DBG_SVCCTL_MSG("Device Name aci_gatt_update_char_value failed.\r\n"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if(aci_gatt_update_char_value(gap_service_handle, | ||||
|                                 gap_appearance_char_handle, | ||||
|                                 0, | ||||
|                                 2, | ||||
|                                 (uint8_t *)&appearance)) | ||||
|   { | ||||
|     BLE_DBG_SVCCTL_MSG("Appearance aci_gatt_update_char_value failed.\r\n"); | ||||
|   } | ||||
|   /**
 | ||||
|    * Initialize Default PHY | ||||
|    */ | ||||
|   hci_le_set_default_phy(ALL_PHYS_PREFERENCE,TX_2M_PREFERRED,RX_2M_PREFERRED); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Initialize IO capability | ||||
|    */ | ||||
|   BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.ioCapability = CFG_IO_CAPABILITY; | ||||
|   aci_gap_set_io_capability(BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.ioCapability); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Initialize authentication | ||||
|    */ | ||||
|   BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.mitm_mode = CFG_MITM_PROTECTION; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.encryptionKeySizeMin = CFG_ENCRYPTION_KEY_SIZE_MIN; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.encryptionKeySizeMax = CFG_ENCRYPTION_KEY_SIZE_MAX; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.Use_Fixed_Pin = CFG_USED_FIXED_PIN; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.Fixed_Pin = CFG_FIXED_PIN; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.bonding_mode = CFG_BONDING_MODE; | ||||
| 
 | ||||
|   aci_gap_set_authentication_requirement(BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.bonding_mode, | ||||
|                                          BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.mitm_mode, | ||||
|                                          CFG_SC_SUPPORT, | ||||
|                                          CFG_KEYPRESS_NOTIFICATION_SUPPORT, | ||||
|                                          BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.encryptionKeySizeMin, | ||||
|                                          BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.encryptionKeySizeMax, | ||||
|                                          BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.Use_Fixed_Pin, | ||||
|                                          BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.Fixed_Pin, | ||||
|                                          PUBLIC_ADDR | ||||
|                                          ); | ||||
| 
 | ||||
|   /**
 | ||||
|    * Initialize whitelist | ||||
|    */ | ||||
|    if (BleApplicationContext.BleApplicationContext_legacy.bleSecurityParam.bonding_mode) | ||||
|    { | ||||
|      aci_gap_configure_whitelist(); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| static void Adv_Request(APP_BLE_ConnStatus_t New_Status) | ||||
| { | ||||
|   tBleStatus ret = BLE_STATUS_INVALID_PARAMS; | ||||
|   uint16_t Min_Inter, Max_Inter; | ||||
| 
 | ||||
|   if (New_Status == APP_BLE_FAST_ADV) | ||||
|   { | ||||
|     Min_Inter = AdvIntervalMin; | ||||
|     Max_Inter = AdvIntervalMax; | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     Min_Inter = CFG_LP_CONN_ADV_INTERVAL_MIN; | ||||
|     Max_Inter = CFG_LP_CONN_ADV_INTERVAL_MAX; | ||||
|   } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Stop the timer, it will be restarted for a new shot | ||||
|      * It does not hurt if the timer was not running | ||||
|      */ | ||||
|     HW_TS_Stop(BleApplicationContext.Advertising_mgr_timer_Id); | ||||
| 
 | ||||
|     APP_DBG_MSG("First index in %d state \r\n", BleApplicationContext.Device_Connection_Status); | ||||
| 
 | ||||
|     if ((New_Status == APP_BLE_LP_ADV) | ||||
|         && ((BleApplicationContext.Device_Connection_Status == APP_BLE_FAST_ADV) | ||||
|             || (BleApplicationContext.Device_Connection_Status == APP_BLE_LP_ADV))) | ||||
|     { | ||||
|       /* Connection in ADVERTISE mode have to stop the current advertising */ | ||||
|       ret = aci_gap_set_non_discoverable(); | ||||
|       if (ret == BLE_STATUS_SUCCESS) | ||||
|       { | ||||
|         APP_DBG_MSG("Successfully Stopped Advertising \r\n"); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         APP_DBG_MSG("Stop Advertising Failed , result: %d \r\n", ret); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     BleApplicationContext.Device_Connection_Status = New_Status; | ||||
| 
 | ||||
|     const char* name = furi_hal_version_get_ble_local_device_name_ptr(); | ||||
| 
 | ||||
|     /* Start Fast or Low Power Advertising */ | ||||
|     ret = aci_gap_set_discoverable( | ||||
|         ADV_IND, | ||||
|         Min_Inter, | ||||
|         Max_Inter, | ||||
|         PUBLIC_ADDR, | ||||
|         NO_WHITE_LIST_USE, /* use white list */ | ||||
|         strlen(name), | ||||
|         (uint8_t*)name, | ||||
|         BleApplicationContext.BleApplicationContext_legacy.advtServUUIDlen, | ||||
|         BleApplicationContext.BleApplicationContext_legacy.advtServUUID, | ||||
|         0, | ||||
|         0); | ||||
| 
 | ||||
|     /* Update Advertising data */ | ||||
|     ret = aci_gap_update_adv_data(sizeof(manuf_data), (uint8_t*) manuf_data); | ||||
|     if (ret == BLE_STATUS_SUCCESS) { | ||||
|       if (New_Status == APP_BLE_FAST_ADV) { | ||||
|         APP_DBG_MSG("Successfully Start Fast Advertising \r\n" ); | ||||
|         /* Start Timer to STOP ADV - TIMEOUT */ | ||||
|         HW_TS_Start(BleApplicationContext.Advertising_mgr_timer_Id, INITIAL_ADV_TIMEOUT); | ||||
|       } else { | ||||
|         APP_DBG_MSG("Successfully Start Low Power Advertising \r\n"); | ||||
|       } | ||||
|     } else { | ||||
|       if (New_Status == APP_BLE_FAST_ADV) { | ||||
|         APP_DBG_MSG("Start Fast Advertising Failed , result: %d \r\n", ret); | ||||
|       } else { | ||||
|         APP_DBG_MSG("Start Low Power Advertising Failed , result: %d \r\n", ret); | ||||
|       } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const uint8_t* BleGetBdAddress( void ) { | ||||
|   uint8_t *otp_addr; | ||||
|   const uint8_t *bd_addr; | ||||
|   uint32_t udn; | ||||
|   uint32_t company_id; | ||||
|   uint32_t device_id; | ||||
| 
 | ||||
|   udn = LL_FLASH_GetUDN(); | ||||
| 
 | ||||
|   if(udn != 0xFFFFFFFF) { | ||||
|     company_id = LL_FLASH_GetSTCompanyID(); | ||||
|     device_id = LL_FLASH_GetDeviceID(); | ||||
| 
 | ||||
|     bd_addr_udn[0] = (uint8_t)(udn & 0x000000FF); | ||||
|     bd_addr_udn[1] = (uint8_t)( (udn & 0x0000FF00) >> 8 ); | ||||
|     bd_addr_udn[2] = (uint8_t)( (udn & 0x00FF0000) >> 16 ); | ||||
|     bd_addr_udn[3] = (uint8_t)device_id; | ||||
|     bd_addr_udn[4] = (uint8_t)(company_id & 0x000000FF);; | ||||
|     bd_addr_udn[5] = (uint8_t)( (company_id & 0x0000FF00) >> 8 ); | ||||
| 
 | ||||
|     bd_addr = (const uint8_t *)bd_addr_udn; | ||||
|   } else { | ||||
|     otp_addr = OTP_Read(0); | ||||
|     if(otp_addr) { | ||||
|       bd_addr = ((OTP_ID0_t*)otp_addr)->bd_address; | ||||
|     } else { | ||||
|       bd_addr = M_bd_addr; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return bd_addr; | ||||
| } | ||||
| 
 | ||||
| /*************************************************************
 | ||||
|  * | ||||
|  *SPECIFIC FUNCTIONS | ||||
|  * | ||||
|  *************************************************************/ | ||||
| static void Add_Advertisment_Service_UUID( uint16_t servUUID ) { | ||||
|   BleApplicationContext.BleApplicationContext_legacy.advtServUUID[BleApplicationContext.BleApplicationContext_legacy.advtServUUIDlen] = | ||||
|       (uint8_t) (servUUID & 0xFF); | ||||
|   BleApplicationContext.BleApplicationContext_legacy.advtServUUIDlen++; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.advtServUUID[BleApplicationContext.BleApplicationContext_legacy.advtServUUIDlen] = | ||||
|       (uint8_t) (servUUID >> 8) & 0xFF; | ||||
|   BleApplicationContext.BleApplicationContext_legacy.advtServUUIDlen++; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| static void Adv_Mgr( void ) { | ||||
|   /**
 | ||||
|    * The code shall be executed in the background as an aci command may be sent | ||||
|    * The background is the only place where the application can make sure a new aci command | ||||
|    * is not sent if there is a pending one | ||||
|    */ | ||||
|   osThreadFlagsSet( AdvUpdateProcessId, 1 ); | ||||
| } | ||||
| 
 | ||||
| static void AdvUpdateProcess(void *argument) { | ||||
|   UNUSED(argument); | ||||
| 
 | ||||
|   for(;;) { | ||||
|     osThreadFlagsWait( 1, osFlagsWaitAny, osWaitForever); | ||||
|     Adv_Update( ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void Adv_Update( void ) { | ||||
|   Adv_Request(APP_BLE_LP_ADV); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| static void HciUserEvtProcess(void *argument) { | ||||
|   UNUSED(argument); | ||||
| 
 | ||||
|  | ||||
| @ -7,24 +7,7 @@ extern "C" { | ||||
| #include <stdbool.h> | ||||
| #include "hci_tl.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     APP_BLE_IDLE, | ||||
|     APP_BLE_FAST_ADV, | ||||
|     APP_BLE_LP_ADV, | ||||
|     APP_BLE_SCAN, | ||||
|     APP_BLE_LP_CONNECTING, | ||||
|     APP_BLE_CONNECTED_SERVER, | ||||
|     APP_BLE_CONNECTED_CLIENT | ||||
| } APP_BLE_ConnStatus_t; | ||||
| 
 | ||||
| bool APP_BLE_Init(); | ||||
| bool APP_BLE_Start(); | ||||
| 
 | ||||
| APP_BLE_ConnStatus_t APP_BLE_Get_Server_Connection_Status(); | ||||
| 
 | ||||
| void APP_BLE_Key_Button1_Action(); | ||||
| void APP_BLE_Key_Button2_Action(); | ||||
| void APP_BLE_Key_Button3_Action(); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|  | ||||
| @ -32,81 +32,12 @@ extern "C"{ | ||||
| #include <stdlib.h> | ||||
| #include <stdarg.h> | ||||
| 
 | ||||
| #include <furi/common_defines.h> | ||||
| 
 | ||||
| #include "app_conf.h" | ||||
| 
 | ||||
|   /* -------------------------------- *
 | ||||
|    *  Basic definitions               * | ||||
|    * -------------------------------- */ | ||||
| 
 | ||||
| #undef NULL | ||||
| #define NULL                    0 | ||||
| 
 | ||||
| #undef FALSE | ||||
| #define FALSE                   0 | ||||
| 
 | ||||
| #undef TRUE | ||||
| #define TRUE                    (!0) | ||||
| 
 | ||||
|   /* -------------------------------- *
 | ||||
|    *  Critical Section definition     * | ||||
|    * -------------------------------- */ | ||||
| #define BACKUP_PRIMASK()    uint32_t primask_bit= __get_PRIMASK() | ||||
| #define DISABLE_IRQ()       __disable_irq() | ||||
| #define RESTORE_PRIMASK()   __set_PRIMASK(primask_bit) | ||||
| 
 | ||||
|   /* -------------------------------- *
 | ||||
|    *  Macro delimiters                * | ||||
|    * -------------------------------- */ | ||||
| 
 | ||||
| #define M_BEGIN     do { | ||||
| 
 | ||||
| #define M_END       } while(0) | ||||
| 
 | ||||
|   /* -------------------------------- *
 | ||||
|    *  Some useful macro definitions   * | ||||
|    * -------------------------------- */ | ||||
| 
 | ||||
| 
 | ||||
| #define MODINC( a, m )       M_BEGIN  (a)++;  if ((a)>=(m)) (a)=0;  M_END | ||||
| 
 | ||||
| #define MODDEC( a, m )       M_BEGIN  if ((a)==0) (a)=(m);  (a)--;  M_END | ||||
| 
 | ||||
| #define MODADD( a, b, m )    M_BEGIN  (a)+=(b);  if ((a)>=(m)) (a)-=(m);  M_END | ||||
| 
 | ||||
| #define MODSUB( a, b, m )    MODADD( a, (m)-(b), m ) | ||||
| 
 | ||||
| #define PAUSE( t )           M_BEGIN \ | ||||
|                                __IO int _i; \ | ||||
|                                for ( _i = t; _i > 0; _i -- ); \ | ||||
|                              M_END | ||||
| 
 | ||||
| #define DIVF( x, y )         ((x)/(y)) | ||||
| 
 | ||||
| #define DIVC( x, y )         (((x)+(y)-1)/(y)) | ||||
| 
 | ||||
| #define DIVR( x, y )         (((x)+((y)/2))/(y)) | ||||
| 
 | ||||
| #define SHRR( x, n )         ((((x)>>((n)-1))+1)>>1) | ||||
| 
 | ||||
| #define BITN( w, n )         (((w)[(n)/32] >> ((n)%32)) & 1) | ||||
| 
 | ||||
| #define BITNSET( w, n, b )   M_BEGIN (w)[(n)/32] |= ((U32)(b))<<((n)%32); M_END | ||||
| 
 | ||||
|   /* -------------------------------- *
 | ||||
|    *  Compiler                         * | ||||
|    * -------------------------------- */ | ||||
| #define PLACE_IN_SECTION( __x__ )  __attribute__((section (__x__))) | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
| #define ALIGN(n) | ||||
| #else | ||||
| #define ALIGN(n)             __attribute__((aligned(n))) | ||||
| #endif | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } /* extern "C" */ | ||||
| #endif | ||||
| 
 | ||||
| #endif /*APP_COMMON_H */ | ||||
| 
 | ||||
| /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ | ||||
|  | ||||
| @ -139,7 +139,7 @@ | ||||
| /**
 | ||||
|  * Maximum supported ATT_MTU size | ||||
|  */ | ||||
| #define CFG_BLE_MAX_ATT_MTU             (156) | ||||
| #define CFG_BLE_MAX_ATT_MTU             (251) | ||||
| 
 | ||||
| /**
 | ||||
|  * Size of the storage area for Attribute values | ||||
|  | ||||
| @ -1,367 +0,0 @@ | ||||
| /* USER CODE BEGIN Header */ | ||||
| /**
 | ||||
|  ****************************************************************************** | ||||
|   * File Name          : app_debug.c | ||||
|   * Description        : Debug capabilities source file for STM32WPAN Middleware | ||||
|  ****************************************************************************** | ||||
|   * @attention | ||||
|   * | ||||
|   * <h2><center>© Copyright (c) 2020 STMicroelectronics. | ||||
|   * All rights reserved.</center></h2> | ||||
|   * | ||||
|   * This software component is licensed by ST under Ultimate Liberty license | ||||
|   * SLA0044, the "License"; You may not use this file except in compliance with | ||||
|   * the License. You may obtain a copy of the License at: | ||||
|   *                             www.st.com/SLA0044 | ||||
|   * | ||||
|  ****************************************************************************** | ||||
|  */ | ||||
| /* USER CODE END Header */ | ||||
| 
 | ||||
| /* Includes ------------------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN Includes */ | ||||
| #include "app_common.h" | ||||
| 
 | ||||
| #include "app_debug.h" | ||||
| #include "utilities_common.h" | ||||
| #include "shci.h" | ||||
| #include "tl.h" | ||||
| #include "dbg_trace.h" | ||||
| #include <furi-hal.h> | ||||
| /* USER CODE END Includes */ | ||||
| 
 | ||||
| /* Private typedef -----------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PTD */ | ||||
| typedef PACKED_STRUCT | ||||
| { | ||||
|   GPIO_TypeDef* port; | ||||
|   uint16_t pin; | ||||
|   uint8_t enable; | ||||
|   uint8_t reserved; | ||||
| } APPD_GpioConfig_t; | ||||
| /* USER CODE END PTD */ | ||||
| 
 | ||||
| /* Private defines -----------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PD */ | ||||
| #define GPIO_NBR_OF_RF_SIGNALS                  9 | ||||
| #define GPIO_CFG_NBR_OF_FEATURES                34 | ||||
| #define NBR_OF_TRACES_CONFIG_PARAMETERS         4 | ||||
| #define NBR_OF_GENERAL_CONFIG_PARAMETERS        4 | ||||
| 
 | ||||
| /**
 | ||||
|  * THIS SHALL BE SET TO A VALUE DIFFERENT FROM 0 ONLY ON REQUEST FROM ST SUPPORT | ||||
|  */ | ||||
| #define BLE_DTB_CFG     0 | ||||
| #define SYS_DBG_CFG1  (SHCI_C2_DEBUG_OPTIONS_IPCORE_LP | SHCI_C2_DEBUG_OPTIONS_CPU2_STOP_EN)  | ||||
| /* USER CODE END PD */ | ||||
| 
 | ||||
| /* Private variables ---------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PV */ | ||||
| PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static SHCI_C2_DEBUG_TracesConfig_t APPD_TracesConfig={0, 0, 0, 0}; | ||||
| PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static SHCI_C2_DEBUG_GeneralConfig_t APPD_GeneralConfig={BLE_DTB_CFG, SYS_DBG_CFG1, {0, 0}}; | ||||
| 
 | ||||
| /**
 | ||||
|  * THE DEBUG ON GPIO FOR CPU2 IS INTENDED TO BE USED ONLY ON REQUEST FROM ST SUPPORT | ||||
|  * It provides timing information on the CPU2 activity. | ||||
|  * All configuration of (port, pin) is supported for each features and can be selected by the user | ||||
|  * depending on the availability | ||||
|  */ | ||||
| static const APPD_GpioConfig_t aGpioConfigList[GPIO_CFG_NBR_OF_FEATURES] = | ||||
| { | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* BLE_ISR - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* BLE_STACK_TICK - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* BLE_CMD_PROCESS - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* BLE_ACL_DATA_PROCESS - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* SYS_CMD_PROCESS - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* RNG_PROCESS - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* NVM_PROCESS - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_GENERAL - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_BLE_CMD_RX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_BLE_EVT_TX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_BLE_ACL_DATA_RX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_SYS_CMD_RX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_SYS_EVT_TX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_CLI_CMD_RX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_OT_CMD_RX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_OT_ACK_TX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_CLI_ACK_TX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_MEM_MANAGER_RX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IPCC_TRACES_TX - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* HARD_FAULT - Set on Entry / Reset on Exit */ | ||||
| /* From v1.1.1 */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* IP_CORE_LP_STATUS - Set on Entry / Reset on Exit */ | ||||
| /* From v1.2.0 */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* END_OF_CONNECTION_EVENT - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* TIMER_SERVER_CALLBACK - Toggle on Entry */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* PES_ACTIVITY - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* MB_BLE_SEND_EVT - Set on Entry / Reset on Exit */ | ||||
| /* From v1.3.0 */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* BLE_NO_DELAY - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* BLE_STACK_STORE_NVM_CB - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* NVMA_WRITE_ONGOING - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* NVMA_WRITE_COMPLETE - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* NVMA_CLEANUP - Set on Entry / Reset on Exit */ | ||||
| /* From v1.4.0 */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* NVMA_START - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* FLASH_EOP - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* FLASH_WRITE - Set on Entry / Reset on Exit */ | ||||
|     { GPIOA, LL_GPIO_PIN_0, 0, 0},  /* FLASH_ERASE - Set on Entry / Reset on Exit */ | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * THE DEBUG ON GPIO FOR CPU2 IS INTENDED TO BE USED ONLY ON REQUEST FROM ST SUPPORT | ||||
|  * This table is relevant only for BLE | ||||
|  * It provides timing information on BLE RF activity. | ||||
|  * New signals may be allocated at any location when requested by ST | ||||
|  * The GPIO allocated to each signal depend on the BLE_DTB_CFG value and cannot be changed | ||||
|  */ | ||||
| #if( BLE_DTB_CFG == 7) | ||||
| static const APPD_GpioConfig_t aRfConfigList[GPIO_NBR_OF_RF_SIGNALS] = | ||||
| { | ||||
|     { GPIOB, LL_GPIO_PIN_2, 0, 0},      /* DTB10 - Tx/Rx SPI */ | ||||
|     { GPIOB, LL_GPIO_PIN_7, 0, 0},      /* DTB11 - Tx/Tx SPI Clk */ | ||||
|     { GPIOA, LL_GPIO_PIN_8, 0, 0},      /* DTB12 - Tx/Rx Ready & SPI Select */ | ||||
|     { GPIOA, LL_GPIO_PIN_9, 0, 0},      /* DTB13 - Tx/Rx Start */ | ||||
|     { GPIOA, LL_GPIO_PIN_10, 0, 0},     /* DTB14 - FSM0 */ | ||||
|     { GPIOA, LL_GPIO_PIN_11, 0, 0},     /* DTB15 - FSM1 */ | ||||
|     { GPIOB, LL_GPIO_PIN_8, 0, 0},      /* DTB16 - FSM2 */ | ||||
|     { GPIOB, LL_GPIO_PIN_11, 0, 0},     /* DTB17 - FSM3 */ | ||||
|     { GPIOB, LL_GPIO_PIN_10, 0, 0},     /* DTB18 - FSM4 */ | ||||
| }; | ||||
| #endif | ||||
| /* USER CODE END PV */ | ||||
| 
 | ||||
| /* Global variables ----------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN GV */ | ||||
| /* USER CODE END GV */ | ||||
| 
 | ||||
| /* Private function prototypes -----------------------------------------------*/ | ||||
| /* USER CODE BEGIN PFP */ | ||||
| static void APPD_SetCPU2GpioConfig( void ); | ||||
| static void APPD_BleDtbCfg( void ); | ||||
| /* USER CODE END PFP */ | ||||
| 
 | ||||
| /* Functions Definition ------------------------------------------------------*/ | ||||
| void APPD_Init( void ) | ||||
| { | ||||
| /* USER CODE BEGIN APPD_Init */ | ||||
| #if (CFG_DEBUGGER_SUPPORTED == 1) | ||||
|   /**
 | ||||
|    * Keep debugger enabled while in any low power mode | ||||
|    */ | ||||
|   HAL_DBGMCU_EnableDBGSleepMode(); | ||||
|   HAL_DBGMCU_EnableDBGStopMode(); | ||||
| 
 | ||||
|   /***************** ENABLE DEBUGGER *************************************/ | ||||
|   LL_EXTI_EnableIT_32_63(LL_EXTI_LINE_48); | ||||
| 
 | ||||
| #else | ||||
|   GPIO_InitTypeDef gpio_config = {0}; | ||||
| 
 | ||||
|   gpio_config.Pull = GPIO_NOPULL; | ||||
|   gpio_config.Mode = GPIO_MODE_ANALOG; | ||||
| 
 | ||||
|   gpio_config.Pin = GPIO_PIN_15 | GPIO_PIN_14 | GPIO_PIN_13; | ||||
|   __HAL_RCC_GPIOA_CLK_ENABLE(); | ||||
|   HAL_GPIO_Init(GPIOA, &gpio_config); | ||||
|   __HAL_RCC_GPIOA_CLK_DISABLE(); | ||||
| 
 | ||||
|   gpio_config.Pin = GPIO_PIN_4 | GPIO_PIN_3; | ||||
|   __HAL_RCC_GPIOB_CLK_ENABLE(); | ||||
|   HAL_GPIO_Init(GPIOB, &gpio_config); | ||||
|   __HAL_RCC_GPIOB_CLK_DISABLE(); | ||||
| 
 | ||||
|   HAL_DBGMCU_DisableDBGSleepMode(); | ||||
|   HAL_DBGMCU_DisableDBGStopMode(); | ||||
|   HAL_DBGMCU_DisableDBGStandbyMode(); | ||||
| 
 | ||||
| #endif /* (CFG_DEBUGGER_SUPPORTED == 1) */ | ||||
| 
 | ||||
| #if(CFG_DEBUG_TRACE != 0) | ||||
|   DbgTraceInit(); | ||||
| #endif | ||||
| 
 | ||||
|   APPD_SetCPU2GpioConfig( ); | ||||
|   APPD_BleDtbCfg( ); | ||||
| 
 | ||||
| /* USER CODE END APPD_Init */ | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| void APPD_EnableCPU2( void ) | ||||
| { | ||||
| /* USER CODE BEGIN APPD_EnableCPU2 */ | ||||
|   SHCI_C2_DEBUG_Init_Cmd_Packet_t DebugCmdPacket = | ||||
|   { | ||||
|     {{0,0,0}},                            /**< Does not need to be initialized */ | ||||
|     {(uint8_t *)aGpioConfigList, | ||||
|     (uint8_t *)&APPD_TracesConfig, | ||||
|     (uint8_t *)&APPD_GeneralConfig, | ||||
|     GPIO_CFG_NBR_OF_FEATURES, | ||||
|     NBR_OF_TRACES_CONFIG_PARAMETERS, | ||||
|     NBR_OF_GENERAL_CONFIG_PARAMETERS} | ||||
|   }; | ||||
| 
 | ||||
|   /**< Traces channel initialization */ | ||||
|   TL_TRACES_Init( ); | ||||
| 
 | ||||
|   /** GPIO DEBUG Initialization */ | ||||
|   SHCI_C2_DEBUG_Init( &DebugCmdPacket  ); | ||||
| 
 | ||||
| /* USER CODE END APPD_EnableCPU2 */ | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| /*************************************************************
 | ||||
|  * | ||||
|  * LOCAL FUNCTIONS | ||||
|  * | ||||
|  *************************************************************/ | ||||
| static void APPD_SetCPU2GpioConfig( void ) | ||||
| { | ||||
| /* USER CODE BEGIN APPD_SetCPU2GpioConfig */ | ||||
|   GPIO_InitTypeDef gpio_config = {0}; | ||||
|   uint8_t local_loop; | ||||
|   uint16_t gpioa_pin_list; | ||||
|   uint16_t gpiob_pin_list; | ||||
|   uint16_t gpioc_pin_list; | ||||
| 
 | ||||
|   gpioa_pin_list = 0; | ||||
|   gpiob_pin_list = 0; | ||||
|   gpioc_pin_list = 0; | ||||
| 
 | ||||
|   for(local_loop = 0 ; local_loop < GPIO_CFG_NBR_OF_FEATURES; local_loop++) | ||||
|   { | ||||
|     if( aGpioConfigList[local_loop].enable != 0) | ||||
|     { | ||||
|       switch((uint32_t)aGpioConfigList[local_loop].port) | ||||
|       { | ||||
|         case (uint32_t)GPIOA: | ||||
|             gpioa_pin_list |= aGpioConfigList[local_loop].pin; | ||||
|           break; | ||||
| 
 | ||||
|         case (uint32_t)GPIOB: | ||||
|             gpiob_pin_list |= aGpioConfigList[local_loop].pin; | ||||
|           break; | ||||
| 
 | ||||
|         case (uint32_t)GPIOC: | ||||
|             gpioc_pin_list |= aGpioConfigList[local_loop].pin; | ||||
|           break; | ||||
| 
 | ||||
|         default: | ||||
|           break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   gpio_config.Pull = GPIO_NOPULL; | ||||
|   gpio_config.Mode = GPIO_MODE_OUTPUT_PP; | ||||
|   gpio_config.Speed = GPIO_SPEED_FREQ_VERY_HIGH; | ||||
| 
 | ||||
|   if(gpioa_pin_list != 0) | ||||
|   { | ||||
|     gpio_config.Pin = gpioa_pin_list; | ||||
|     __HAL_RCC_GPIOA_CLK_ENABLE(); | ||||
|     __HAL_RCC_C2GPIOA_CLK_ENABLE(); | ||||
|     HAL_GPIO_Init(GPIOA, &gpio_config); | ||||
|     HAL_GPIO_WritePin(GPIOA, gpioa_pin_list, GPIO_PIN_RESET); | ||||
|   } | ||||
| 
 | ||||
|   if(gpiob_pin_list != 0) | ||||
|   { | ||||
|     gpio_config.Pin = gpiob_pin_list; | ||||
|     __HAL_RCC_GPIOB_CLK_ENABLE(); | ||||
|     __HAL_RCC_C2GPIOB_CLK_ENABLE(); | ||||
|     HAL_GPIO_Init(GPIOB, &gpio_config); | ||||
|     HAL_GPIO_WritePin(GPIOB, gpiob_pin_list, GPIO_PIN_RESET); | ||||
|   } | ||||
| 
 | ||||
|   if(gpioc_pin_list != 0) | ||||
|   { | ||||
|     gpio_config.Pin = gpioc_pin_list; | ||||
|     __HAL_RCC_GPIOC_CLK_ENABLE(); | ||||
|     __HAL_RCC_C2GPIOC_CLK_ENABLE(); | ||||
|     HAL_GPIO_Init(GPIOC, &gpio_config); | ||||
|     HAL_GPIO_WritePin(GPIOC, gpioc_pin_list, GPIO_PIN_RESET); | ||||
|   } | ||||
|    | ||||
| /* USER CODE END APPD_SetCPU2GpioConfig */ | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| static void APPD_BleDtbCfg( void ) | ||||
| { | ||||
| /* USER CODE BEGIN APPD_BleDtbCfg */ | ||||
| #if (BLE_DTB_CFG != 0) | ||||
|   GPIO_InitTypeDef gpio_config = {0}; | ||||
|   uint8_t local_loop; | ||||
|   uint16_t gpioa_pin_list; | ||||
|   uint16_t gpiob_pin_list; | ||||
| 
 | ||||
|   gpioa_pin_list = 0; | ||||
|   gpiob_pin_list = 0; | ||||
| 
 | ||||
|   for(local_loop = 0 ; local_loop < GPIO_NBR_OF_RF_SIGNALS; local_loop++) | ||||
|   { | ||||
|     if( aRfConfigList[local_loop].enable != 0) | ||||
|     { | ||||
|       switch((uint32_t)aRfConfigList[local_loop].port) | ||||
|       { | ||||
|         case (uint32_t)GPIOA: | ||||
|             gpioa_pin_list |= aRfConfigList[local_loop].pin; | ||||
|           break; | ||||
| 
 | ||||
|         case (uint32_t)GPIOB: | ||||
|             gpiob_pin_list |= aRfConfigList[local_loop].pin; | ||||
|           break; | ||||
| 
 | ||||
|         default: | ||||
|           break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   gpio_config.Pull = GPIO_NOPULL; | ||||
|   gpio_config.Mode = GPIO_MODE_AF_PP; | ||||
|   gpio_config.Speed = GPIO_SPEED_FREQ_VERY_HIGH; | ||||
|   gpio_config.Alternate = GPIO_AF6_RF_DTB7; | ||||
| 
 | ||||
|   if(gpioa_pin_list != 0) | ||||
|   { | ||||
|     gpio_config.Pin = gpioa_pin_list; | ||||
|     __HAL_RCC_GPIOA_CLK_ENABLE(); | ||||
|     __HAL_RCC_C2GPIOA_CLK_ENABLE(); | ||||
|     HAL_GPIO_Init(GPIOA, &gpio_config); | ||||
|   } | ||||
| 
 | ||||
|   if(gpiob_pin_list != 0) | ||||
|   { | ||||
|     gpio_config.Pin = gpiob_pin_list; | ||||
|     __HAL_RCC_GPIOB_CLK_ENABLE(); | ||||
|     __HAL_RCC_C2GPIOB_CLK_ENABLE(); | ||||
|     HAL_GPIO_Init(GPIOB, &gpio_config); | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
| /* USER CODE END APPD_BleDtbCfg */ | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| /*************************************************************
 | ||||
|  * | ||||
|  * WRAP FUNCTIONS | ||||
|  * | ||||
| *************************************************************/ | ||||
| #if(CFG_DEBUG_TRACE != 0) | ||||
| void DbgOutputInit( void ) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void DbgOutputTraces(  uint8_t *p_data, uint16_t size, void (*cb)(void) ) | ||||
| { | ||||
|   furi_hal_console_tx(p_data, size); | ||||
|   cb(); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ | ||||
| @ -6,7 +6,6 @@ | ||||
| #include "tl.h" | ||||
| #include "cmsis_os.h" | ||||
| #include "shci_tl.h" | ||||
| #include "app_debug.h" | ||||
| #include <furi-hal.h> | ||||
| 
 | ||||
| extern RTC_HandleTypeDef hrtc; | ||||
| @ -139,8 +138,10 @@ static void APPE_SysUserEvtRx( void * pPayload ) { | ||||
|   // APPD_EnableCPU2( );
 | ||||
|    | ||||
|   if (APP_BLE_Init()) { | ||||
|     FURI_LOG_I("Core2", "BLE stack started"); | ||||
|     ble_glue_status = BleGlueStatusStarted; | ||||
|   } else { | ||||
|     FURI_LOG_E("Core2", "BLE stack startup failed"); | ||||
|     ble_glue_status = BleGlueStatusBroken; | ||||
|   } | ||||
|   furi_hal_power_insomnia_exit(); | ||||
| @ -178,3 +179,16 @@ void shci_cmd_resp_wait(uint32_t timeout) { | ||||
|   UNUSED(timeout); | ||||
|   osSemaphoreAcquire( SemShciId, osWaitForever ); | ||||
| } | ||||
| 
 | ||||
| #if(CFG_DEBUG_TRACE != 0) | ||||
| void DbgOutputInit( void ) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| void DbgOutputTraces(  uint8_t *p_data, uint16_t size, void (*cb)(void) ) | ||||
| { | ||||
|   furi_hal_console_tx(p_data, size); | ||||
|   cb(); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										78
									
								
								firmware/targets/f6/ble-glue/battery_service.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								firmware/targets/f6/ble-glue/battery_service.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| #include "battery_service.h" | ||||
| #include "app_common.h" | ||||
| #include "ble.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define BATTERY_SERVICE_TAG "battery service" | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint16_t svc_handle; | ||||
|     uint16_t char_level_handle; | ||||
| } BatterySvc; | ||||
| 
 | ||||
| static BatterySvc* battery_svc = NULL; | ||||
| 
 | ||||
| static const uint16_t service_uuid = BATTERY_SERVICE_UUID; | ||||
| static const uint16_t char_battery_level_uuid = BATTERY_LEVEL_CHAR_UUID; | ||||
| 
 | ||||
| void battery_svc_start() { | ||||
|     battery_svc = furi_alloc(sizeof(BatterySvc)); | ||||
|     tBleStatus status; | ||||
| 
 | ||||
|     // Add Battery service
 | ||||
|     status = aci_gatt_add_service(UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 4, &battery_svc->svc_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to add Battery service: %d", status); | ||||
|     } | ||||
|     // Add Battery level characteristic
 | ||||
|     status = aci_gatt_add_char(battery_svc->svc_handle, | ||||
|                                 UUID_TYPE_16, | ||||
|                                 (Char_UUID_t *) &char_battery_level_uuid, | ||||
|                                 1, | ||||
|                                 CHAR_PROP_READ | CHAR_PROP_NOTIFY, | ||||
|                                 ATTR_PERMISSION_NONE, | ||||
|                                 GATT_DONT_NOTIFY_EVENTS, | ||||
|                                 10, | ||||
|                                 CHAR_VALUE_LEN_CONSTANT, | ||||
|                                 &battery_svc->char_level_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to add Battery level characteristic: %d", status); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void battery_svc_stop() { | ||||
|     tBleStatus status; | ||||
|     if(battery_svc) { | ||||
|         // Delete Battery level characteristic
 | ||||
|         status = aci_gatt_del_char(battery_svc->svc_handle, battery_svc->char_level_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to delete Battery level characteristic: %d", status); | ||||
|         } | ||||
|         // Delete Battery service
 | ||||
|         status = aci_gatt_del_service(battery_svc->svc_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed to delete Battery service: %d", status); | ||||
|         } | ||||
|         free(battery_svc); | ||||
|         battery_svc = NULL; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool battery_svc_update_level(uint8_t battery_charge) { | ||||
|     // Check if service was started
 | ||||
|     if(battery_svc == NULL) { | ||||
|         return false; | ||||
|     } | ||||
|     // Update battery level characteristic
 | ||||
|     FURI_LOG_I(BATTERY_SERVICE_TAG, "Updating battery level characteristic"); | ||||
|     tBleStatus result = aci_gatt_update_char_value(battery_svc->svc_handle, | ||||
|                                           battery_svc->char_level_handle, | ||||
|                                           0, | ||||
|                                           1, | ||||
|                                           &battery_charge); | ||||
|     if(result) { | ||||
|         FURI_LOG_E(BATTERY_SERVICE_TAG, "Failed updating RX characteristic: %d", result); | ||||
|     } | ||||
|     return result != BLE_STATUS_SUCCESS; | ||||
| } | ||||
							
								
								
									
										18
									
								
								firmware/targets/f6/ble-glue/battery_service.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								firmware/targets/f6/ble-glue/battery_service.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void battery_svc_start(); | ||||
| 
 | ||||
| void battery_svc_stop(); | ||||
| 
 | ||||
| bool battery_svc_update_level(uint8_t battery_level); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -53,47 +53,12 @@ | ||||
| 
 | ||||
| #define BLE_CFG_CLT_MAX_NBR_CB                                                 0 | ||||
| 
 | ||||
| /******************************************************************************
 | ||||
|  * Device Information Service (DIS) | ||||
|  ******************************************************************************/ | ||||
| /**< Options: Supported(1) or Not Supported(0) */ | ||||
| #define BLE_CFG_DIS_MANUFACTURER_NAME_STRING                                   1 | ||||
| #define BLE_CFG_DIS_MODEL_NUMBER_STRING                                        1 | ||||
| #define BLE_CFG_DIS_SERIAL_NUMBER_STRING                                       0 | ||||
| #define BLE_CFG_DIS_HARDWARE_REVISION_STRING                                   0 | ||||
| #define BLE_CFG_DIS_FIRMWARE_REVISION_STRING                                   1 | ||||
| #define BLE_CFG_DIS_SOFTWARE_REVISION_STRING                                   1 | ||||
| #define BLE_CFG_DIS_SYSTEM_ID                                                  0 | ||||
| #define BLE_CFG_DIS_IEEE_CERTIFICATION                                         0 | ||||
| #define BLE_CFG_DIS_PNP_ID                                                     0 | ||||
| 
 | ||||
| /**
 | ||||
|  * device information service characteristic lengths | ||||
|  */ | ||||
| #define BLE_CFG_DIS_SYSTEM_ID_LEN_MAX                                        (8) | ||||
| #define BLE_CFG_DIS_MODEL_NUMBER_STRING_LEN_MAX                              (32) | ||||
| #define BLE_CFG_DIS_SERIAL_NUMBER_STRING_LEN_MAX                             (32) | ||||
| #define BLE_CFG_DIS_FIRMWARE_REVISION_STRING_LEN_MAX                         (32) | ||||
| #define BLE_CFG_DIS_HARDWARE_REVISION_STRING_LEN_MAX                         (32) | ||||
| #define BLE_CFG_DIS_SOFTWARE_REVISION_STRING_LEN_MAX                         (64) | ||||
| #define BLE_CFG_DIS_MANUFACTURER_NAME_STRING_LEN_MAX                         (32) | ||||
| #define BLE_CFG_DIS_IEEE_CERTIFICATION_LEN_MAX                               (32) | ||||
| #define BLE_CFG_DIS_PNP_ID_LEN_MAX                                           (7) | ||||
| 
 | ||||
| /******************************************************************************
 | ||||
|  * Heart Rate Service (HRS) | ||||
|  ******************************************************************************/ | ||||
| #define BLE_CFG_HRS_BODY_SENSOR_LOCATION_CHAR               1/**< BODY SENSOR LOCATION CHARACTERISTIC */ | ||||
| #define BLE_CFG_HRS_ENERGY_EXPENDED_INFO_FLAG               1/**< ENERGY EXTENDED INFO FLAG */ | ||||
| #define BLE_CFG_HRS_ENERGY_RR_INTERVAL_FLAG                 1/**< Max number of RR interval values - Shall not be greater than 9 */ | ||||
| 
 | ||||
| /******************************************************************************
 | ||||
|  * GAP Service - Apprearance | ||||
|  ******************************************************************************/ | ||||
| 
 | ||||
| #define BLE_CFG_UNKNOWN_APPEARANCE                  (0) | ||||
| #define BLE_CFG_HR_SENSOR_APPEARANCE                (832) | ||||
| #define BLE_CFG_GAP_APPEARANCE                      (BLE_CFG_HR_SENSOR_APPEARANCE) | ||||
| #define BLE_CFG_GAP_APPEARANCE                      (0x0086) | ||||
| 
 | ||||
| /******************************************************************************
 | ||||
|  * Over The Air Feature (OTA) - STM Proprietary | ||||
|  | ||||
							
								
								
									
										156
									
								
								firmware/targets/f6/ble-glue/dev_info_service.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								firmware/targets/f6/ble-glue/dev_info_service.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,156 @@ | ||||
| #include "dev_info_service.h" | ||||
| #include "app_common.h" | ||||
| #include "ble.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define DEV_INFO_SVC_TAG "dev info service" | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint16_t service_handle; | ||||
|     uint16_t man_name_char_handle; | ||||
|     uint16_t serial_num_char_handle; | ||||
|     uint16_t firmware_rev_char_handle; | ||||
|     uint16_t software_rev_char_handle; | ||||
| } DevInfoSvc; | ||||
| 
 | ||||
| static DevInfoSvc* dev_info_svc = NULL; | ||||
| 
 | ||||
| static const char dev_info_man_name[] = "Flipper Devices Inc."; | ||||
| static const char dev_info_serial_num[] = "1.0"; | ||||
| static const char dev_info_firmware_rev_num[] = TARGET; | ||||
| static const char dev_info_software_rev_num[] = GIT_COMMIT " " GIT_BRANCH " " GIT_BRANCH_NUM " " BUILD_DATE; | ||||
| 
 | ||||
| void dev_info_svc_start() { | ||||
|     dev_info_svc = furi_alloc(sizeof(DevInfoSvc)); | ||||
|     tBleStatus status; | ||||
| 
 | ||||
|     // Add Device Information Service
 | ||||
|     uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID; | ||||
|     status = aci_gatt_add_service(UUID_TYPE_16, (Service_UUID_t*)&uuid, PRIMARY_SERVICE, 9, &dev_info_svc->service_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add Device Information Service: %d", status); | ||||
|     } | ||||
| 
 | ||||
|     // Add characteristics
 | ||||
|     uuid = MANUFACTURER_NAME_UUID; | ||||
|     status = aci_gatt_add_char(dev_info_svc->service_handle, | ||||
|                                 UUID_TYPE_16, | ||||
|                                 (Char_UUID_t*)&uuid, | ||||
|                                 strlen(dev_info_man_name), | ||||
|                                 CHAR_PROP_READ, | ||||
|                                 ATTR_PERMISSION_NONE, | ||||
|                                 GATT_DONT_NOTIFY_EVENTS, | ||||
|                                 10, | ||||
|                                 CHAR_VALUE_LEN_CONSTANT, | ||||
|                                 &dev_info_svc->man_name_char_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add manufacturer name char: %d", status); | ||||
|     } | ||||
|     uuid = SERIAL_NUMBER_UUID; | ||||
|     status = aci_gatt_add_char(dev_info_svc->service_handle, | ||||
|                                 UUID_TYPE_16, | ||||
|                                 (Char_UUID_t*)&uuid, | ||||
|                                 strlen(dev_info_serial_num), | ||||
|                                 CHAR_PROP_READ, | ||||
|                                 ATTR_PERMISSION_NONE, | ||||
|                                 GATT_DONT_NOTIFY_EVENTS, | ||||
|                                 10, | ||||
|                                 CHAR_VALUE_LEN_CONSTANT, | ||||
|                                 &dev_info_svc->serial_num_char_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add serial number char: %d", status); | ||||
|     } | ||||
|     uuid = FIRMWARE_REVISION_UUID; | ||||
|     status = aci_gatt_add_char(dev_info_svc->service_handle, | ||||
|                                 UUID_TYPE_16, | ||||
|                                 (Char_UUID_t*)&uuid, | ||||
|                                 strlen(dev_info_firmware_rev_num), | ||||
|                                 CHAR_PROP_READ, | ||||
|                                 ATTR_PERMISSION_NONE, | ||||
|                                 GATT_DONT_NOTIFY_EVENTS, | ||||
|                                 10, | ||||
|                                 CHAR_VALUE_LEN_CONSTANT, | ||||
|                                 &dev_info_svc->firmware_rev_char_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add firmware revision char: %d", status); | ||||
|     } | ||||
|     uuid = SOFTWARE_REVISION_UUID; | ||||
|     status = aci_gatt_add_char(dev_info_svc->service_handle, | ||||
|                                 UUID_TYPE_16, | ||||
|                                 (Char_UUID_t*)&uuid, | ||||
|                                 strlen(dev_info_software_rev_num), | ||||
|                                 CHAR_PROP_READ, | ||||
|                                 ATTR_PERMISSION_NONE, | ||||
|                                 GATT_DONT_NOTIFY_EVENTS, | ||||
|                                 10, | ||||
|                                 CHAR_VALUE_LEN_CONSTANT, | ||||
|                                 &dev_info_svc->software_rev_char_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to add software revision char: %d", status); | ||||
|     } | ||||
| 
 | ||||
|     // Update characteristics
 | ||||
|     status = aci_gatt_update_char_value(dev_info_svc->service_handle, | ||||
|                                         dev_info_svc->man_name_char_handle, | ||||
|                                         0, | ||||
|                                         strlen(dev_info_man_name), | ||||
|                                         (uint8_t*)dev_info_man_name); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update manufacturer name char: %d", status); | ||||
|     } | ||||
|     status = aci_gatt_update_char_value(dev_info_svc->service_handle, | ||||
|                                         dev_info_svc->serial_num_char_handle, | ||||
|                                         0, | ||||
|                                         strlen(dev_info_serial_num), | ||||
|                                         (uint8_t*)dev_info_serial_num); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update serial number char: %d", status); | ||||
|     } | ||||
|     status = aci_gatt_update_char_value(dev_info_svc->service_handle, | ||||
|                                         dev_info_svc->firmware_rev_char_handle, | ||||
|                                         0, | ||||
|                                         strlen(dev_info_firmware_rev_num), | ||||
|                                         (uint8_t*)dev_info_firmware_rev_num); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update firmware revision char: %d", status); | ||||
|     } | ||||
|     status = aci_gatt_update_char_value(dev_info_svc->service_handle, | ||||
|                                         dev_info_svc->software_rev_char_handle, | ||||
|                                         0, | ||||
|                                         strlen(dev_info_software_rev_num), | ||||
|                                         (uint8_t*)dev_info_software_rev_num); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to update software revision char: %d", status); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void dev_info_svc_stop() { | ||||
|     tBleStatus status; | ||||
|     if(dev_info_svc) { | ||||
|         // Delete service characteristics
 | ||||
|         status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->man_name_char_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete manufacturer name char: %d", status); | ||||
|         } | ||||
|         status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->serial_num_char_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete serial number char: %d", status); | ||||
|         } | ||||
|         status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->firmware_rev_char_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete firmware revision char: %d", status); | ||||
|         } | ||||
|         status = aci_gatt_del_char(dev_info_svc->service_handle, dev_info_svc->software_rev_char_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete software revision char: %d", status); | ||||
|         } | ||||
|         // Delete service
 | ||||
|         status = aci_gatt_del_service(dev_info_svc->service_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(DEV_INFO_SVC_TAG, "Failed to delete device info service: %d", status); | ||||
|         } | ||||
|         free(dev_info_svc); | ||||
|         dev_info_svc = NULL; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								firmware/targets/f6/ble-glue/dev_info_service.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								firmware/targets/f6/ble-glue/dev_info_service.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #define DEV_INFO_MANUFACTURER_NAME              "Flipper Devices Inc." | ||||
| #define DEV_INFO_SERIAL_NUMBER                  "1.0" | ||||
| #define DEV_INFO_FIRMWARE_REVISION_NUMBER       TARGET | ||||
| #define DEV_INFO_SOFTWARE_REVISION_NUMBER       GIT_COMMIT " " GIT_BRANCH " " GIT_BRANCH_NUM " " BUILD_DATE | ||||
| 
 | ||||
| void dev_info_svc_start(); | ||||
| 
 | ||||
| void dev_info_svc_stop(); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -1,157 +0,0 @@ | ||||
| #include "app_common.h" | ||||
| #include "ble.h" | ||||
| #include "dis_app.h" | ||||
| #include <furi-hal-version.h> | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_SYSTEM_ID != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
| static const uint8_t system_id[BLE_CFG_DIS_SYSTEM_ID_LEN_MAX] = { | ||||
|   (uint8_t)((DISAPP_MANUFACTURER_ID & 0xFF0000) >> 16), | ||||
|   (uint8_t)((DISAPP_MANUFACTURER_ID & 0x00FF00) >> 8), | ||||
|   (uint8_t)(DISAPP_MANUFACTURER_ID & 0x0000FF), | ||||
|   0xFE, | ||||
|   0xFF, | ||||
|   (uint8_t)((DISAPP_OUI & 0xFF0000) >> 16), | ||||
|   (uint8_t)((DISAPP_OUI & 0x00FF00) >> 8), | ||||
|   (uint8_t)(DISAPP_OUI & 0x0000FF) | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_IEEE_CERTIFICATION != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
| static const uint8_t ieee_id[BLE_CFG_DIS_IEEE_CERTIFICATION_LEN_MAX] = { | ||||
|   0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, | ||||
|   0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, | ||||
|   0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, | ||||
|   0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, 0xFE, 0xCA, | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_PNP_ID != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
| static const uint8_t pnp_id[BLE_CFG_DIS_PNP_ID_LEN_MAX] = { | ||||
|   0x1, | ||||
|   0xAD, 0xDE, | ||||
|   0xDE, 0xDA, | ||||
|   0x01, 0x00 | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
| void DISAPP_Init(void) { | ||||
|   DIS_Data_t dis_information_data; | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_MANUFACTURER_NAME_STRING != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update MANUFACTURER NAME Information | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t*)DISAPP_MANUFACTURER_NAME; | ||||
|   dis_information_data.Length = sizeof(DISAPP_MANUFACTURER_NAME); | ||||
|   DIS_UpdateChar(MANUFACTURER_NAME_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_MODEL_NUMBER_STRING != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update MODEL NUMBERInformation | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   const char* name = furi_hal_version_get_device_name_ptr(); | ||||
|   dis_information_data.pPayload = (uint8_t*)name; | ||||
|   dis_information_data.Length = strlen(name) + 1; | ||||
|   DIS_UpdateChar(MODEL_NUMBER_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_SERIAL_NUMBER_STRING != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update SERIAL NUMBERInformation | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t*)DISAPP_SERIAL_NUMBER; | ||||
|   dis_information_data.Length = sizeof(DISAPP_SERIAL_NUMBER); | ||||
|   DIS_UpdateChar(SERIAL_NUMBER_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_HARDWARE_REVISION_STRING != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update HARDWARE REVISION NUMBERInformation | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t*)DISAPP_HARDWARE_REVISION_NUMBER; | ||||
|   dis_information_data.Length = sizeof(DISAPP_HARDWARE_REVISION_NUMBER); | ||||
|   DIS_UpdateChar(HARDWARE_REVISION_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_FIRMWARE_REVISION_STRING != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update FIRMWARE REVISION NUMBERInformation | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t*)DISAPP_FIRMWARE_REVISION_NUMBER; | ||||
|   dis_information_data.Length = sizeof(DISAPP_FIRMWARE_REVISION_NUMBER); | ||||
|   DIS_UpdateChar(FIRMWARE_REVISION_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_SOFTWARE_REVISION_STRING != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update SOFTWARE REVISION NUMBERInformation | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t*)DISAPP_SOFTWARE_REVISION_NUMBER; | ||||
|   dis_information_data.Length = sizeof(DISAPP_SOFTWARE_REVISION_NUMBER); | ||||
|   DIS_UpdateChar(SOFTWARE_REVISION_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_SYSTEM_ID != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update SYSTEM ID Information | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t *)system_id; | ||||
|   dis_information_data.Length = BLE_CFG_DIS_SYSTEM_ID_LEN_MAX; | ||||
|   DIS_UpdateChar(SYSTEM_ID_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_IEEE_CERTIFICATION != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update IEEE CERTIFICATION ID Information | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t *)ieee_id; | ||||
|   dis_information_data.Length = BLE_CFG_DIS_IEEE_CERTIFICATION_LEN_MAX; | ||||
|   DIS_UpdateChar(IEEE_CERTIFICATION_UUID, &dis_information_data); | ||||
| #endif | ||||
| 
 | ||||
| #if ((BLE_CFG_DIS_PNP_ID != 0) || (CFG_MENU_DEVICE_INFORMATION != 0)) | ||||
|   /**
 | ||||
|    * Update PNP ID Information | ||||
|    * | ||||
|    * @param UUID | ||||
|    * @param pPData | ||||
|    * @return | ||||
|    */ | ||||
|   dis_information_data.pPayload = (uint8_t *)pnp_id; | ||||
|   dis_information_data.Length = BLE_CFG_DIS_PNP_ID_LEN_MAX; | ||||
|   DIS_UpdateChar(PNP_ID_UUID, &dis_information_data); | ||||
| #endif | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #define DISAPP_MANUFACTURER_NAME              "Flipperdevice Inc." | ||||
| //#define DISAPP_MODEL_NUMBER                   "FlipperZero"
 | ||||
| #define DISAPP_SERIAL_NUMBER                  "1.0" | ||||
| #define DISAPP_HARDWARE_REVISION_NUMBER       "1.0" | ||||
| #define DISAPP_FIRMWARE_REVISION_NUMBER       TARGET | ||||
| #define DISAPP_SOFTWARE_REVISION_NUMBER       GIT_COMMIT " " GIT_BRANCH " " GIT_BRANCH_NUM " " BUILD_DATE | ||||
| #define DISAPP_OUI                            0x123456 | ||||
| #define DISAPP_MANUFACTURER_ID                0x9ABCDE | ||||
| 
 | ||||
| void DISAPP_Init(void); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										387
									
								
								firmware/targets/f6/ble-glue/gap.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								firmware/targets/f6/ble-glue/gap.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,387 @@ | ||||
| #include "gap.h" | ||||
| 
 | ||||
| #include "app_entry.h" | ||||
| #include "ble.h" | ||||
| 
 | ||||
| #include "cmsis_os.h" | ||||
| #include "otp.h" | ||||
| #include "dev_info_service.h" | ||||
| #include "battery_service.h" | ||||
| #include "serial_service.h" | ||||
| 
 | ||||
| #include <applications/bt/bt_service/bt.h> | ||||
| #include <furi-hal.h> | ||||
| 
 | ||||
| #define GAP_TAG "BLE" | ||||
| 
 | ||||
| #define FAST_ADV_TIMEOUT 30000 | ||||
| #define INITIAL_ADV_TIMEOUT 60000 | ||||
| 
 | ||||
| #define BD_ADDR_SIZE_LOCAL 6 | ||||
| 
 | ||||
| typedef struct { | ||||
|   uint16_t gap_svc_handle; | ||||
|   uint16_t dev_name_char_handle; | ||||
|   uint16_t appearance_char_handle; | ||||
|   uint16_t connection_handle; | ||||
|   uint8_t adv_svc_uuid_len; | ||||
|   uint8_t adv_svc_uuid[20]; | ||||
| } GapSvc; | ||||
| 
 | ||||
| typedef struct { | ||||
|   GapSvc gap_svc; | ||||
|   GapState state; | ||||
|   uint8_t mac_address[BD_ADDR_SIZE_LOCAL]; | ||||
|   Bt* bt; | ||||
|   osTimerId advertise_timer; | ||||
|   osThreadAttr_t thread_attr; | ||||
|   osThreadId_t thread_id; | ||||
| } Gap; | ||||
| 
 | ||||
| // Identity root key
 | ||||
| static const uint8_t gap_irk[16] = {0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0}; | ||||
| // Encryption root key
 | ||||
| static const uint8_t gap_erk[16] = {0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21,0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21}; | ||||
| // Appearence characteristic UUID
 | ||||
| static const uint8_t gap_appearence_char_uuid[] = {0x00, 0x86}; | ||||
| // Default MAC address
 | ||||
| static const uint8_t gap_default_mac_addr[] = {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}; | ||||
| 
 | ||||
| static Gap* gap = NULL; | ||||
| 
 | ||||
| static void gap_advertise(GapState new_state); | ||||
| static void gap_app(void *arg); | ||||
| 
 | ||||
| SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | ||||
| { | ||||
|   hci_event_pckt *event_pckt; | ||||
|   evt_le_meta_event *meta_evt; | ||||
|   evt_blue_aci *blue_evt; | ||||
|   hci_le_phy_update_complete_event_rp0 *evt_le_phy_update_complete; | ||||
|   uint8_t tx_phy; | ||||
|   uint8_t rx_phy; | ||||
|   tBleStatus ret = BLE_STATUS_INVALID_PARAMS; | ||||
| 
 | ||||
|   event_pckt = (hci_event_pckt*) ((hci_uart_pckt *) pckt)->data; | ||||
| 
 | ||||
|   switch (event_pckt->evt) { | ||||
|     case EVT_DISCONN_COMPLETE: | ||||
|     { | ||||
|         hci_disconnection_complete_event_rp0 *disconnection_complete_event = (hci_disconnection_complete_event_rp0 *) event_pckt->data; | ||||
|         if (disconnection_complete_event->Connection_Handle == gap->gap_svc.connection_handle) { | ||||
|             gap->gap_svc.connection_handle = 0; | ||||
|             gap->state = GapStateIdle; | ||||
|             FURI_LOG_I(GAP_TAG, "Disconnect from client"); | ||||
|         } | ||||
|         // Restart advertising
 | ||||
|         gap_advertise(GapStateAdvFast); | ||||
|         furi_hal_power_insomnia_exit(); | ||||
|     } | ||||
|     break; | ||||
| 
 | ||||
|     case EVT_LE_META_EVENT: | ||||
|         meta_evt = (evt_le_meta_event*) event_pckt->data; | ||||
|         switch (meta_evt->subevent) { | ||||
|             case EVT_LE_CONN_UPDATE_COMPLETE: | ||||
|             FURI_LOG_D(GAP_TAG, "Connection update event"); | ||||
|             break; | ||||
| 
 | ||||
|             case EVT_LE_PHY_UPDATE_COMPLETE: | ||||
|             evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*)meta_evt->data; | ||||
|             if(evt_le_phy_update_complete->Status) { | ||||
|                 FURI_LOG_E(GAP_TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status); | ||||
|             } else { | ||||
|                 FURI_LOG_I(GAP_TAG, "Update PHY succeed"); | ||||
|             } | ||||
|             ret = hci_le_read_phy(gap->gap_svc.connection_handle,&tx_phy,&rx_phy); | ||||
|             if(ret) { | ||||
|                 FURI_LOG_E(GAP_TAG, "Read PHY failed, status: %d", ret); | ||||
|             } else { | ||||
|                 FURI_LOG_I(GAP_TAG, "PHY Params TX= %d, RX= %d ", tx_phy, rx_phy); | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|             case EVT_LE_CONN_COMPLETE: | ||||
|             furi_hal_power_insomnia_enter(); | ||||
|             hci_le_connection_complete_event_rp0* connection_complete_event = (hci_le_connection_complete_event_rp0 *) meta_evt->data; | ||||
|             FURI_LOG_I(GAP_TAG, "Connection complete for connection handle 0x%x", connection_complete_event->Connection_Handle); | ||||
| 
 | ||||
|             // Stop advertising as connection completed
 | ||||
|             osTimerStop(gap->advertise_timer); | ||||
| 
 | ||||
|             // Update connection status and handle
 | ||||
|             gap->state = GapStateConnected; | ||||
|             gap->gap_svc.connection_handle = connection_complete_event->Connection_Handle; | ||||
| 
 | ||||
|             // Start pairing by sending security request
 | ||||
|             aci_gap_slave_security_req(connection_complete_event->Connection_Handle); | ||||
|             break; | ||||
| 
 | ||||
|             default: | ||||
|             break; | ||||
|         } | ||||
|     break; | ||||
| 
 | ||||
|     case EVT_VENDOR: | ||||
|         blue_evt = (evt_blue_aci*) event_pckt->data; | ||||
|         switch (blue_evt->ecode) { | ||||
|             aci_gap_pairing_complete_event_rp0 *pairing_complete; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_LIMITED_DISCOVERABLE: | ||||
|             FURI_LOG_I(GAP_TAG, "Limited discoverable event"); | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_PASS_KEY_REQUEST: | ||||
|         { | ||||
|             // Generate random PIN code
 | ||||
|             uint32_t pin = rand() % 999999; | ||||
|             aci_gap_pass_key_resp(gap->gap_svc.connection_handle, pin); | ||||
|             FURI_LOG_I(GAP_TAG, "Pass key request event. Pin: %d", pin); | ||||
|             bt_pin_code_show(gap->bt, pin); | ||||
|         } | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_AUTHORIZATION_REQUEST: | ||||
|             FURI_LOG_I(GAP_TAG, "Authorization request event"); | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_SLAVE_SECURITY_INITIATED: | ||||
|             FURI_LOG_I(GAP_TAG, "Slave security initiated"); | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_BOND_LOST: | ||||
|             FURI_LOG_I(GAP_TAG, "Bond lost event. Start rebonding"); | ||||
|             aci_gap_allow_rebond(gap->gap_svc.connection_handle); | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_DEVICE_FOUND: | ||||
|             FURI_LOG_I(GAP_TAG, "Device found event"); | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_ADDR_NOT_RESOLVED: | ||||
|             FURI_LOG_I(GAP_TAG, "Address not resolved event"); | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_KEYPRESS_NOTIFICATION: | ||||
|             FURI_LOG_I(GAP_TAG, "Key press notification event"); | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: | ||||
|             FURI_LOG_I(GAP_TAG, "Hex_value = %lx", | ||||
|                         ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value); | ||||
|             aci_gap_numeric_comparison_value_confirm_yesno(gap->gap_svc.connection_handle, 1); | ||||
|             break; | ||||
| 
 | ||||
|         case (EVT_BLUE_GAP_PAIRING_CMPLT): | ||||
|         { | ||||
|             pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data; | ||||
|             if (pairing_complete->Status) { | ||||
|             FURI_LOG_E(GAP_TAG, "Pairing failed with status: %d. Terminating connection", pairing_complete->Status); | ||||
|             aci_gap_terminate(gap->gap_svc.connection_handle, 5); | ||||
|             } else { | ||||
|             FURI_LOG_I(GAP_TAG, "Pairing complete"); | ||||
|             } | ||||
|         } | ||||
|             break; | ||||
| 
 | ||||
|         case EVT_BLUE_GAP_PROCEDURE_COMPLETE: | ||||
|             FURI_LOG_I(GAP_TAG, "Procedure complete event"); | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             break; | ||||
|   } | ||||
| 
 | ||||
|   return SVCCTL_UserEvtFlowEnable; | ||||
| } | ||||
| 
 | ||||
| void SVCCTL_SvcInit() { | ||||
|     // Dummy function to prevent unused services initialization
 | ||||
|     // TODO refactor (disable all services in WPAN config)
 | ||||
| } | ||||
| 
 | ||||
| static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) { | ||||
|     gap->gap_svc.adv_svc_uuid_len = 1; | ||||
|     if(uid_len == 2) { | ||||
|         gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; | ||||
|     } else if (uid_len == 4) { | ||||
|         gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; | ||||
|     } else if(uid_len == 16) { | ||||
|         gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; | ||||
|     } | ||||
|     memcpy(&gap->gap_svc.adv_svc_uuid[1], uid, uid_len); | ||||
|     gap->gap_svc.adv_svc_uuid_len += uid_len; | ||||
| } | ||||
| 
 | ||||
| GapState gap_get_status() { | ||||
|     return gap->state; | ||||
| } | ||||
| 
 | ||||
| void gap_init_mac_address(Gap* gap) { | ||||
|     uint8_t *otp_addr; | ||||
|     uint32_t udn; | ||||
|     uint32_t company_id; | ||||
|     uint32_t device_id; | ||||
| 
 | ||||
|     udn = LL_FLASH_GetUDN(); | ||||
|     if(udn != 0xFFFFFFFF) { | ||||
|         company_id = LL_FLASH_GetSTCompanyID(); | ||||
|         device_id = LL_FLASH_GetDeviceID(); | ||||
|         gap->mac_address[0] = (uint8_t)(udn & 0x000000FF); | ||||
|         gap->mac_address[1] = (uint8_t)( (udn & 0x0000FF00) >> 8 ); | ||||
|         gap->mac_address[2] = (uint8_t)( (udn & 0x00FF0000) >> 16 ); | ||||
|         gap->mac_address[3] = (uint8_t)device_id; | ||||
|         gap->mac_address[4] = (uint8_t)(company_id & 0x000000FF);; | ||||
|         gap->mac_address[5] = (uint8_t)( (company_id & 0x0000FF00) >> 8 ); | ||||
|     } else { | ||||
|         otp_addr = OTP_Read(0); | ||||
|         if(otp_addr) { | ||||
|             memcpy(gap->mac_address, ((OTP_ID0_t*)otp_addr)->bd_address, sizeof(gap->mac_address)); | ||||
|         } else { | ||||
|             memcpy(gap->mac_address, gap_default_mac_addr, sizeof(gap->mac_address)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void gap_init_svc(Gap* gap) { | ||||
|     tBleStatus status; | ||||
|     uint32_t srd_bd_addr[2]; | ||||
| 
 | ||||
|     //HCI Reset to synchronise BLE Stack*/
 | ||||
|     hci_reset(); | ||||
|     // Configure mac address
 | ||||
|     gap_init_mac_address(gap); | ||||
|     aci_hal_write_config_data(CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, (uint8_t*)gap->mac_address); | ||||
| 
 | ||||
|     /* Static random Address
 | ||||
|      * The two upper bits shall be set to 1 | ||||
|      * The lowest 32bits is read from the UDN to differentiate between devices | ||||
|      * The RNG may be used to provide a random number on each power on | ||||
|      */ | ||||
|     srd_bd_addr[1] = 0x0000ED6E; | ||||
|     srd_bd_addr[0] = LL_FLASH_GetUDN(); | ||||
|     aci_hal_write_config_data( CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr ); | ||||
|     // Set Identity root key used to derive LTK and CSRK
 | ||||
|     aci_hal_write_config_data( CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*)gap_irk ); | ||||
|     // Set Encryption root key used to derive LTK and CSRK
 | ||||
|     aci_hal_write_config_data( CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*)gap_erk ); | ||||
|     // Set TX Power to 0 dBm
 | ||||
|     aci_hal_set_tx_power_level(1, 0x19); | ||||
|     // Initialize GATT interface
 | ||||
|     aci_gatt_init(); | ||||
|     // Initialize GAP interface
 | ||||
|     const char *name = furi_hal_version_get_device_name_ptr(); | ||||
|     aci_gap_init(GAP_PERIPHERAL_ROLE, 0, strlen(name), | ||||
|                 &gap->gap_svc.gap_svc_handle, &gap->gap_svc.dev_name_char_handle, &gap->gap_svc.appearance_char_handle); | ||||
| 
 | ||||
|     // Set GAP characteristics
 | ||||
|     status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.dev_name_char_handle, 0, strlen(name), (uint8_t *) name); | ||||
|     if (status) { | ||||
|         FURI_LOG_E(GAP_TAG, "Failed updating name characteristic: %d", status); | ||||
|     } | ||||
|     status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.appearance_char_handle, 0, 2, gap_appearence_char_uuid); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(GAP_TAG, "Failed updating appearence characteristic: %d", status); | ||||
|     } | ||||
|     // Set default PHY
 | ||||
|     hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); | ||||
|     // Set I/O capability
 | ||||
|     aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); | ||||
|     // Setup  authentication
 | ||||
|     aci_gap_set_authentication_requirement(1, 1, 1, 0, 8, 16, 1, 0, PUBLIC_ADDR); | ||||
|     // Configure whitelist
 | ||||
|     aci_gap_configure_whitelist(); | ||||
| } | ||||
| 
 | ||||
| static void gap_advertise(GapState new_state) | ||||
| { | ||||
|     tBleStatus status; | ||||
|     uint16_t min_interval; | ||||
|     uint16_t max_interval; | ||||
| 
 | ||||
|     if (new_state == GapStateAdvFast) { | ||||
|         min_interval = 0x80; // 80 ms
 | ||||
|         max_interval = 0xa0; // 100 ms
 | ||||
|     } else { | ||||
|         min_interval = 0x0640; // 1 s
 | ||||
|         max_interval = 0x0fa0; // 2.5 s
 | ||||
|     } | ||||
|     // Stop advertising timer
 | ||||
|     osTimerStop(gap->advertise_timer); | ||||
| 
 | ||||
|     if ((new_state == GapStateAdvLowPower) && ((gap->state == GapStateAdvFast) || (gap->state == GapStateAdvLowPower))) { | ||||
|         // Stop advertising
 | ||||
|         status = aci_gap_set_non_discoverable(); | ||||
|         if (status) { | ||||
|             FURI_LOG_E(GAP_TAG, "Stop Advertising Failed, result: %d", status); | ||||
|         } | ||||
|     } | ||||
|     // Configure advertising
 | ||||
|     gap->state = new_state; | ||||
|     const char* name = furi_hal_version_get_ble_local_device_name_ptr(); | ||||
|     status = aci_gap_set_discoverable(ADV_IND, min_interval, max_interval, PUBLIC_ADDR, 0, | ||||
|                                         strlen(name), (uint8_t*)name, | ||||
|                                         gap->gap_svc.adv_svc_uuid_len, gap->gap_svc.adv_svc_uuid, 0, 0); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(GAP_TAG, "Set discoverable err: %d", status); | ||||
|     } | ||||
|     osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT); | ||||
| } | ||||
| 
 | ||||
| static void gap_advertise_request(Gap* gap) { | ||||
|     osThreadFlagsSet(gap->thread_id, 1); | ||||
| } | ||||
| 
 | ||||
| static void gap_advetise_timer_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     Gap* gap = context; | ||||
|     gap_advertise_request(gap); | ||||
| } | ||||
| 
 | ||||
| bool gap_init() { | ||||
|     if (APPE_Status() != BleGlueStatusStarted) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     gap = furi_alloc(sizeof(Gap)); | ||||
|     srand(DWT->CYCCNT); | ||||
|     // Open Bt record
 | ||||
|     gap->bt = furi_record_open("bt"); | ||||
|     // Create advertising timer
 | ||||
|     gap->advertise_timer = osTimerNew(gap_advetise_timer_callback, osTimerOnce, &gap, NULL); | ||||
|     // Initialization of HCI & GATT & GAP layer
 | ||||
|     gap_init_svc(gap); | ||||
|     // Initialization of the BLE Services
 | ||||
|     SVCCTL_Init(); | ||||
|     // Initialization of the BLE App Context
 | ||||
|     gap->state = GapStateIdle; | ||||
|     gap->gap_svc.connection_handle = 0xFFFF; | ||||
| 
 | ||||
|     // Thread configuration
 | ||||
|     gap->thread_attr.name = "BLE advertising"; | ||||
|     gap->thread_attr.stack_size = 512; | ||||
|     gap->thread_id = osThreadNew(gap_app, NULL, &gap->thread_attr); | ||||
| 
 | ||||
|     // Start Device Information service
 | ||||
|     dev_info_svc_start(); | ||||
|     // Start Battery service
 | ||||
|     battery_svc_start(); | ||||
|     // Start Serial application
 | ||||
|     serial_svc_start(); | ||||
|     // Configure advirtise service UUID
 | ||||
|     uint8_t adv_service_uid[2]; | ||||
|     adv_service_uid[0] = 0x80 | furi_hal_version_get_hw_color(); | ||||
|     adv_service_uid[1] = 0x30; | ||||
| 
 | ||||
|     set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); | ||||
|     gap_advertise(GapStateAdvFast); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void gap_app(void *arg) { | ||||
|     // TODO Exit from app, stop service, clean memory
 | ||||
|     while(1) { | ||||
|         osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); | ||||
|         gap_advertise(GapStateAdvLowPower); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										22
									
								
								firmware/targets/f6/ble-glue/gap.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								firmware/targets/f6/ble-glue/gap.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef enum { | ||||
|     GapStateIdle, | ||||
|     GapStateAdvFast, | ||||
|     GapStateAdvLowPower, | ||||
|     GapStateConnected, | ||||
| } GapState; | ||||
| 
 | ||||
| bool gap_init(); | ||||
| 
 | ||||
| GapState gap_get_status(); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -1,256 +0,0 @@ | ||||
| /* USER CODE BEGIN Header */ | ||||
| /**
 | ||||
|   ****************************************************************************** | ||||
|   * @file    hrs_app.c | ||||
|   * @author  MCD Application Team | ||||
|   * @brief   Heart Rate Service Application | ||||
|   ****************************************************************************** | ||||
|   * @attention | ||||
|  * | ||||
|   * <h2><center>© Copyright (c) 2019 STMicroelectronics.  | ||||
|  * All rights reserved.</center></h2> | ||||
|  * | ||||
|   * This software component is licensed by ST under Ultimate Liberty license  | ||||
|   * SLA0044, the "License"; You may not use this file except in compliance with  | ||||
|  * the License. You may obtain a copy of the License at: | ||||
|  *                             www.st.com/SLA0044 | ||||
|  * | ||||
|  ****************************************************************************** | ||||
|  */ | ||||
| /* USER CODE END Header */ | ||||
| 
 | ||||
| /* Includes ------------------------------------------------------------------*/ | ||||
| #include "app_common.h" | ||||
| 
 | ||||
| #include "ble.h" | ||||
| #include "hrs_app.h" | ||||
| #include "cmsis_os.h" | ||||
| 
 | ||||
| /* Private includes ----------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN Includes */ | ||||
| 
 | ||||
| /* USER CODE END Includes */ | ||||
| 
 | ||||
| /* Private typedef -----------------------------------------------------------*/ | ||||
| typedef struct | ||||
| { | ||||
|   HRS_BodySensorLocation_t BodySensorLocationChar; | ||||
|   HRS_MeasVal_t MeasurementvalueChar; | ||||
|   uint8_t ResetEnergyExpended; | ||||
|   uint8_t TimerMeasurement_Id; | ||||
| 
 | ||||
| } HRSAPP_Context_t; | ||||
| /* USER CODE BEGIN PTD */ | ||||
| 
 | ||||
| /* USER CODE END PTD */ | ||||
| 
 | ||||
| /* Private defines ------------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PD */ | ||||
| 
 | ||||
| /* USER CODE END PD */ | ||||
| 
 | ||||
| /* Private macros ------------------------------------------------------------*/ | ||||
| #define HRSAPP_MEASUREMENT_INTERVAL   (1000000/CFG_TS_TICK_VAL)  /**< 1s */ | ||||
| /* USER CODE BEGIN PM */ | ||||
| 
 | ||||
| /* USER CODE END PM */ | ||||
| 
 | ||||
| /* Private variables ---------------------------------------------------------*/ | ||||
| /**
 | ||||
|  * START of Section BLE_APP_CONTEXT | ||||
|  */ | ||||
| 
 | ||||
| PLACE_IN_SECTION("BLE_APP_CONTEXT") static HRSAPP_Context_t HRSAPP_Context; | ||||
| 
 | ||||
| /**
 | ||||
|  * END of Section BLE_APP_CONTEXT | ||||
|  */ | ||||
| 
 | ||||
| osThreadId_t HrsProcessId; | ||||
| 
 | ||||
| const osThreadAttr_t HrsProcess_attr = { | ||||
|     .name = CFG_HRS_PROCESS_NAME, | ||||
|     .attr_bits = CFG_HRS_PROCESS_ATTR_BITS, | ||||
|     .cb_mem = CFG_HRS_PROCESS_CB_MEM, | ||||
|     .cb_size = CFG_HRS_PROCESS_CB_SIZE, | ||||
|     .stack_mem = CFG_HRS_PROCESS_STACK_MEM, | ||||
|     .priority = CFG_HRS_PROCESS_PRIORITY, | ||||
|     .stack_size = CFG_HRS_PROCESS_STACK_SIZE | ||||
| }; | ||||
| 
 | ||||
| /* USER CODE BEGIN PV */ | ||||
| 
 | ||||
| /* USER CODE END PV */ | ||||
| 
 | ||||
| /* Private functions prototypes-----------------------------------------------*/ | ||||
| static void HrMeas( void ); | ||||
| static void HrsProcess(void *argument); | ||||
| static void HRSAPP_Measurement(void); | ||||
| static uint32_t HRSAPP_Read_RTC_SSR_SS ( void ); | ||||
| /* USER CODE BEGIN PFP */ | ||||
| 
 | ||||
| /* USER CODE END PFP */ | ||||
| 
 | ||||
| /* Functions Definition ------------------------------------------------------*/ | ||||
| void HRS_Notification(HRS_App_Notification_evt_t *pNotification) | ||||
| { | ||||
| /* USER CODE BEGIN HRS_Notification_1 */ | ||||
| 
 | ||||
| /* USER CODE END HRS_Notification_1 */ | ||||
|   switch(pNotification->HRS_Evt_Opcode) | ||||
|   { | ||||
| /* USER CODE BEGIN HRS_Notification_HRS_Evt_Opcode */ | ||||
| 
 | ||||
| /* USER CODE END HRS_Notification_HRS_Evt_Opcode */ | ||||
| #if (BLE_CFG_HRS_ENERGY_EXPENDED_INFO_FLAG != 0) | ||||
|     case HRS_RESET_ENERGY_EXPENDED_EVT: | ||||
| /* USER CODE BEGIN HRS_RESET_ENERGY_EXPENDED_EVT */ | ||||
|       HRSAPP_Context.MeasurementvalueChar.EnergyExpended = 0; | ||||
|       HRSAPP_Context.ResetEnergyExpended = 1; | ||||
| /* USER CODE END HRS_RESET_ENERGY_EXPENDED_EVT */ | ||||
|       break; | ||||
| #endif | ||||
| 
 | ||||
|     case HRS_NOTIFICATION_ENABLED: | ||||
| /* USER CODE BEGIN HRS_NOTIFICATION_ENABLED */ | ||||
|       /**
 | ||||
|        * It could be the enable notification is received twice without the disable notification in between | ||||
|        */ | ||||
|       HW_TS_Stop(HRSAPP_Context.TimerMeasurement_Id); | ||||
|       HW_TS_Start(HRSAPP_Context.TimerMeasurement_Id, HRSAPP_MEASUREMENT_INTERVAL); | ||||
| /* USER CODE END HRS_NOTIFICATION_ENABLED */ | ||||
|       break; | ||||
| 
 | ||||
|     case HRS_NOTIFICATION_DISABLED: | ||||
| /* USER CODE BEGIN HRS_NOTIFICATION_DISABLED */ | ||||
|       HW_TS_Stop(HRSAPP_Context.TimerMeasurement_Id); | ||||
| /* USER CODE END HRS_NOTIFICATION_DISABLED */ | ||||
|       break; | ||||
| 
 | ||||
| #if (BLE_CFG_OTA_REBOOT_CHAR != 0) | ||||
|     case HRS_STM_BOOT_REQUEST_EVT: | ||||
| /* USER CODE BEGIN HRS_STM_BOOT_REQUEST_EVT */ | ||||
|       *(uint32_t*)SRAM1_BASE = *(uint32_t*)pNotification->DataTransfered.pPayload; | ||||
|       NVIC_SystemReset(); | ||||
| /* USER CODE END HRS_STM_BOOT_REQUEST_EVT */ | ||||
|       break; | ||||
| #endif | ||||
| 
 | ||||
|    default: | ||||
| /* USER CODE BEGIN HRS_Notification_Default */ | ||||
| 
 | ||||
| /* USER CODE END HRS_Notification_Default */ | ||||
|       break; | ||||
|   } | ||||
| /* USER CODE BEGIN HRS_Notification_2 */ | ||||
| 
 | ||||
| /* USER CODE END HRS_Notification_2 */ | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| void HRSAPP_Init(void) | ||||
| { | ||||
|   HrsProcessId = osThreadNew(HrsProcess, NULL, &HrsProcess_attr); | ||||
| /* USER CODE BEGIN HRSAPP_Init */ | ||||
|   /**
 | ||||
|    * Set Body Sensor Location | ||||
|    */ | ||||
|   HRSAPP_Context.ResetEnergyExpended = 0; | ||||
|   HRSAPP_Context.BodySensorLocationChar = HRS_BODY_SENSOR_LOCATION_HAND; | ||||
|   HRS_UpdateChar(SENSOR_LOCATION_UUID, (uint8_t *)&HRSAPP_Context.BodySensorLocationChar); | ||||
| 
 | ||||
| 
 | ||||
|   /**
 | ||||
|    * Set Flags for measurement value | ||||
|    */ | ||||
| 
 | ||||
|   HRSAPP_Context.MeasurementvalueChar.Flags = ( HRS_HRM_VALUE_FORMAT_UINT16      |  | ||||
|                                                   HRS_HRM_SENSOR_CONTACTS_PRESENT   |  | ||||
|                                                   HRS_HRM_SENSOR_CONTACTS_SUPPORTED | | ||||
|                                                   HRS_HRM_ENERGY_EXPENDED_PRESENT  | | ||||
|                                                   HRS_HRM_RR_INTERVAL_PRESENT ); | ||||
| 
 | ||||
| #if (BLE_CFG_HRS_ENERGY_EXPENDED_INFO_FLAG != 0) | ||||
|   if(HRSAPP_Context.MeasurementvalueChar.Flags & HRS_HRM_ENERGY_EXPENDED_PRESENT) | ||||
|     HRSAPP_Context.MeasurementvalueChar.EnergyExpended = 10; | ||||
| #endif | ||||
|    | ||||
| #if (BLE_CFG_HRS_ENERGY_RR_INTERVAL_FLAG != 0) | ||||
|   if(HRSAPP_Context.MeasurementvalueChar.Flags & HRS_HRM_RR_INTERVAL_PRESENT) | ||||
|   { | ||||
|     uint8_t i; | ||||
|      | ||||
|     HRSAPP_Context.MeasurementvalueChar.NbreOfValidRRIntervalValues = BLE_CFG_HRS_ENERGY_RR_INTERVAL_FLAG; | ||||
|     for(i = 0; i < BLE_CFG_HRS_ENERGY_RR_INTERVAL_FLAG; i++) | ||||
|       HRSAPP_Context.MeasurementvalueChar.aRRIntervalValues[i] = 1024; | ||||
|   } | ||||
| #endif | ||||
|    | ||||
|   /**
 | ||||
|    * Create timer for Heart Rate Measurement | ||||
|    */ | ||||
|   HW_TS_Create(CFG_TIM_PROC_ID_ISR, &(HRSAPP_Context.TimerMeasurement_Id), hw_ts_Repeated, HrMeas); | ||||
| 
 | ||||
| /* USER CODE END HRSAPP_Init */ | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| static void HrsProcess(void *argument) | ||||
| { | ||||
|   UNUSED(argument); | ||||
| 
 | ||||
|   for(;;) | ||||
|   { | ||||
|     osThreadFlagsWait( 1, osFlagsWaitAny, osWaitForever); | ||||
|     HRSAPP_Measurement( ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| static void HRSAPP_Measurement(void) | ||||
| { | ||||
| /* USER CODE BEGIN HRSAPP_Measurement */ | ||||
|   uint32_t measurement; | ||||
| 
 | ||||
|   measurement = ((HRSAPP_Read_RTC_SSR_SS()) & 0x07) + 65; | ||||
| 
 | ||||
|   HRSAPP_Context.MeasurementvalueChar.MeasurementValue = measurement; | ||||
| #if (BLE_CFG_HRS_ENERGY_EXPENDED_INFO_FLAG != 0) | ||||
|   if((HRSAPP_Context.MeasurementvalueChar.Flags & HRS_HRM_ENERGY_EXPENDED_PRESENT) && | ||||
|      (HRSAPP_Context.ResetEnergyExpended == 0)) | ||||
|     HRSAPP_Context.MeasurementvalueChar.EnergyExpended += 5; | ||||
|   else if(HRSAPP_Context.ResetEnergyExpended == 1) | ||||
|     HRSAPP_Context.ResetEnergyExpended = 0; | ||||
| #endif | ||||
| 
 | ||||
|   HRS_UpdateChar(HEART_RATE_MEASURMENT_UUID, (uint8_t *)&HRSAPP_Context.MeasurementvalueChar); | ||||
| 
 | ||||
| /* USER CODE END HRSAPP_Measurement */ | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| static void HrMeas( void ) | ||||
| { | ||||
|   /**
 | ||||
|    * The code shall be executed in the background as aci command may be sent | ||||
|    * The background is the only place where the application can make sure a new aci command | ||||
|    * is not sent if there is a pending one | ||||
|    */ | ||||
|   osThreadFlagsSet( HrsProcessId, 1 ); | ||||
| 
 | ||||
| /* USER CODE BEGIN HrMeas */ | ||||
| 
 | ||||
| /* USER CODE END HrMeas */ | ||||
| 
 | ||||
|   return; | ||||
| } | ||||
| 
 | ||||
| static uint32_t HRSAPP_Read_RTC_SSR_SS ( void ) | ||||
| { | ||||
|   return ((uint32_t)(READ_BIT(RTC->SSR, RTC_SSR_SS))); | ||||
| } | ||||
| 
 | ||||
| /* USER CODE BEGIN FD */ | ||||
| 
 | ||||
| /* USER CODE END FD */ | ||||
| 
 | ||||
| /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ | ||||
							
								
								
									
										122
									
								
								firmware/targets/f6/ble-glue/serial_service.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								firmware/targets/f6/ble-glue/serial_service.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| #include "serial_service.h" | ||||
| #include "app_common.h" | ||||
| #include "ble.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define SERIAL_SERVICE_TAG "serial service" | ||||
| 
 | ||||
| #define SERIAL_SVC_DATA_LEN_MAX 245 | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint16_t svc_handle; | ||||
|     uint16_t rx_char_handle; | ||||
|     uint16_t tx_char_handle; | ||||
| } SerialSvc; | ||||
| 
 | ||||
| static SerialSvc* serial_svc; | ||||
| 
 | ||||
| static const uint8_t service_uuid[] = {0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f}; | ||||
| static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; | ||||
| static const uint8_t char_tx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; | ||||
| 
 | ||||
| static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { | ||||
|     SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; | ||||
|     hci_event_pckt* event_pckt = (hci_event_pckt *)(((hci_uart_pckt*)event)->data); | ||||
|     evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; | ||||
|     aci_gatt_attribute_modified_event_rp0* attribute_modified; | ||||
|     if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { | ||||
|         if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { | ||||
|             attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blecore_evt->data; | ||||
|             if(attribute_modified->Attr_Handle == serial_svc->tx_char_handle + 2) { | ||||
|                 // Descriptor handle
 | ||||
|                 ret = SVCCTL_EvtAckFlowEnable; | ||||
|                 FURI_LOG_D(SERIAL_SERVICE_TAG, "TX descriptor event"); | ||||
|             } else if(attribute_modified->Attr_Handle == serial_svc->tx_char_handle + 1) { | ||||
|                 FURI_LOG_D(SERIAL_SERVICE_TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); | ||||
|                 serial_svc_update_rx(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length); | ||||
|                 ret = SVCCTL_EvtAckFlowEnable; | ||||
|             } | ||||
|         } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { | ||||
|             FURI_LOG_D(SERIAL_SERVICE_TAG, "Ack received", blecore_evt->ecode); | ||||
|             ret = SVCCTL_EvtAckFlowEnable; | ||||
|         } | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void serial_svc_start() { | ||||
|     tBleStatus status; | ||||
|     serial_svc = furi_alloc(sizeof(SerialSvc)); | ||||
|     // Register event handler
 | ||||
|     SVCCTL_RegisterSvcHandler(serial_svc_event_handler); | ||||
| 
 | ||||
|     // Add service
 | ||||
|     status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 6, &serial_svc->svc_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add Serial service: %d", status); | ||||
|     } | ||||
| 
 | ||||
|     // Add TX characteristics
 | ||||
|     status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)char_tx_uuid, | ||||
|                                 SERIAL_SVC_DATA_LEN_MAX, | ||||
|                                 CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_READ, | ||||
|                                 ATTR_PERMISSION_NONE, | ||||
|                                 GATT_NOTIFY_ATTRIBUTE_WRITE, | ||||
|                                 10, | ||||
|                                 CHAR_VALUE_LEN_VARIABLE, | ||||
|                                 &serial_svc->tx_char_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add TX characteristic: %d", status); | ||||
|     } | ||||
| 
 | ||||
|     // Add RX characteristic
 | ||||
|     status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)char_rx_uuid, | ||||
|                                 SERIAL_SVC_DATA_LEN_MAX,                                   | ||||
|                                 CHAR_PROP_READ | CHAR_PROP_INDICATE, | ||||
|                                 ATTR_PERMISSION_NONE, | ||||
|                                 GATT_DONT_NOTIFY_EVENTS, | ||||
|                                 10, | ||||
|                                 CHAR_VALUE_LEN_VARIABLE, | ||||
|                                 &serial_svc->rx_char_handle); | ||||
|     if(status) { | ||||
|         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add RX characteristic: %d", status); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void serial_svc_stop() { | ||||
|     tBleStatus status; | ||||
|     if(serial_svc) { | ||||
|         // Delete characteristics
 | ||||
|         status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->tx_char_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete TX characteristic: %d", status); | ||||
|         } | ||||
|         status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->rx_char_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete RX characteristic: %d", status); | ||||
|         } | ||||
|         // Delete service
 | ||||
|         status = aci_gatt_del_service(serial_svc->svc_handle); | ||||
|         if(status) { | ||||
|             FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete Serial service: %d", status); | ||||
|         } | ||||
|         free(serial_svc); | ||||
|         serial_svc = NULL; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool serial_svc_update_rx(uint8_t* data, uint8_t data_len) { | ||||
|     furi_assert(data_len < SERIAL_SVC_DATA_LEN_MAX); | ||||
| 
 | ||||
|     tBleStatus result = aci_gatt_update_char_value(serial_svc->svc_handle, | ||||
|                                           serial_svc->rx_char_handle, | ||||
|                                           0, | ||||
|                                           data_len, | ||||
|                                           data); | ||||
|     if(result) { | ||||
|         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed updating RX characteristic: %d", result); | ||||
|     } | ||||
|     return result != BLE_STATUS_SUCCESS; | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aleksandr Kutuzov
						Aleksandr Kutuzov