├── assets ├── Apps.png ├── data.png ├── misc.png ├── lora_tx.png ├── receive.png ├── settings.png ├── sniffer.png ├── writelog.png ├── lora_relay.png ├── addonsubghz.png ├── ec_wki_button.png ├── lora_sender.png ├── lora_settings.png ├── select_file.png └── start_sniff.png ├── applications_user └── lora_app │ ├── app.png │ ├── cat.png │ ├── assets │ ├── log.png │ ├── paper.png │ ├── signal.png │ ├── write.png │ ├── kitty_tx.png │ ├── no_write.png │ ├── no_signal.png │ ├── sam_flipper.png │ ├── flippers_cat.png │ └── glyph_1_14x40.png │ ├── application.fam │ ├── modules │ ├── validators.h │ ├── validators.c │ ├── text_input.h │ └── text_input.c │ ├── assets_icons.h │ ├── lora.c │ └── lora_relay.c ├── .github └── workflows │ ├── pre-commit.yml │ └── build.yml ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── .clang-format └── meshtasticDashboard.py /assets/Apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/Apps.png -------------------------------------------------------------------------------- /assets/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/data.png -------------------------------------------------------------------------------- /assets/misc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/misc.png -------------------------------------------------------------------------------- /assets/lora_tx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/lora_tx.png -------------------------------------------------------------------------------- /assets/receive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/receive.png -------------------------------------------------------------------------------- /assets/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/settings.png -------------------------------------------------------------------------------- /assets/sniffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/sniffer.png -------------------------------------------------------------------------------- /assets/writelog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/writelog.png -------------------------------------------------------------------------------- /assets/lora_relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/lora_relay.png -------------------------------------------------------------------------------- /assets/addonsubghz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/addonsubghz.png -------------------------------------------------------------------------------- /assets/ec_wki_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/ec_wki_button.png -------------------------------------------------------------------------------- /assets/lora_sender.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/lora_sender.png -------------------------------------------------------------------------------- /assets/lora_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/lora_settings.png -------------------------------------------------------------------------------- /assets/select_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/select_file.png -------------------------------------------------------------------------------- /assets/start_sniff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/assets/start_sniff.png -------------------------------------------------------------------------------- /applications_user/lora_app/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/app.png -------------------------------------------------------------------------------- /applications_user/lora_app/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/cat.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/log.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/paper.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/signal.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/write.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/kitty_tx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/kitty_tx.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/no_write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/no_write.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/no_signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/no_signal.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/sam_flipper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/sam_flipper.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/flippers_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/flippers_cat.png -------------------------------------------------------------------------------- /applications_user/lora_app/assets/glyph_1_14x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElectronicCats/flipper-SX1262-LoRa/HEAD/applications_user/lora_app/assets/glyph_1_14x40.png -------------------------------------------------------------------------------- /applications_user/lora_app/application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="lora_app", 3 | name="LoRa Relay", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="main_lora_app", 6 | stack_size=4 * 1024, 7 | requires=[ 8 | "gui", 9 | ], 10 | order=10, 11 | fap_icon="cat.png", 12 | fap_category="ElectronicCats", 13 | fap_icon_assets="assets", 14 | fap_description="LoRa Sample App. This is intended to be used as a starting point for new applications with one primary screen.", 15 | ) 16 | -------------------------------------------------------------------------------- /applications_user/lora_app/modules/validators.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | typedef struct ValidatorIsFile ValidatorIsFile; 10 | 11 | ValidatorIsFile* validator_is_file_alloc_init( 12 | const char* app_path_folder, 13 | const char* app_extension, 14 | const char* current_name); 15 | 16 | void validator_is_file_free(ValidatorIsFile* instance); 17 | 18 | bool validator_is_file_callback(const char* text, FuriString* error, void* context); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Electronic Cats 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: pre-commit 6 | 7 | on: 8 | push: 9 | pull_request: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | pre-commit: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - name: Set up repository 20 | uses: actions/checkout@v4 21 | - name: Set up python 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: 3.x 25 | - name: Run pre-commit 26 | uses: pre-commit/action@v3.0.1 27 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.0.1 4 | hooks: 5 | - id: check-yaml 6 | #- id: no-commit-to-branch # This hook prevents direct commits to main branch 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | - repo: https://github.com/pre-commit/mirrors-clang-format 10 | rev: v18.1.2 # Use the sha / tag you want to point at 11 | hooks: 12 | - id: clang-format 13 | types_or: [c++, c] 14 | args: ['--style=file'] 15 | - repo: https://github.com/compilerla/conventional-pre-commit 16 | rev: v3.2.0 17 | hooks: 18 | - id: conventional-pre-commit 19 | stages: [commit-msg] 20 | args: [] 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "FAP: Build and lint" 2 | on: [push, pull_request] 3 | jobs: 4 | ufbt-build-action: 5 | runs-on: ubuntu-latest 6 | name: 'ufbt: Build for Dev branch' 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v4 10 | - name: Build with ufbt 11 | uses: flipperdevices/flipperzero-ufbt-action@v0.1.3 12 | id: build-app 13 | with: 14 | # Set to 'release' to build for latest published release version or dev for develop 15 | sdk-index-url: https://up.unleashedflip.com/directory.json 16 | sdk-channel: release 17 | app-dir: ./applications_user/lora_app 18 | - name: Upload app artifacts 19 | uses: actions/upload-artifact@v4 20 | with: 21 | name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} 22 | path: ${{ steps.build-app.outputs.fap-artifacts }} 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ElectronicCats 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /applications_user/lora_app/modules/validators.c: -------------------------------------------------------------------------------- 1 | #include "validators.h" 2 | #include 3 | #include 4 | 5 | struct ValidatorIsFile { 6 | char* app_path_folder; 7 | const char* app_extension; 8 | char* current_name; 9 | }; 10 | 11 | bool validator_is_file_callback(const char* text, FuriString* error, void* context) { 12 | furi_check(context); 13 | ValidatorIsFile* instance = context; 14 | 15 | if(instance->current_name != NULL) { 16 | if(strcmp(instance->current_name, text) == 0) { 17 | return true; 18 | } 19 | } 20 | 21 | FuriString* path = furi_string_alloc_printf( 22 | "%s/%s%s", instance->app_path_folder, text, instance->app_extension); 23 | Storage* storage = furi_record_open(RECORD_STORAGE); 24 | const bool ret = storage_common_stat(storage, furi_string_get_cstr(path), NULL) != FSE_OK; 25 | if(!ret) { 26 | furi_string_printf(error, "This name\nexists!\nChoose\nanother one."); 27 | } 28 | furi_string_free(path); 29 | furi_record_close(RECORD_STORAGE); 30 | 31 | return ret; 32 | } 33 | 34 | ValidatorIsFile* validator_is_file_alloc_init( 35 | const char* app_path_folder, 36 | const char* app_extension, 37 | const char* current_name) { 38 | ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); 39 | 40 | instance->app_path_folder = strdup(app_path_folder); 41 | instance->app_extension = app_extension; 42 | if(current_name != NULL) { 43 | instance->current_name = strdup(current_name); 44 | } 45 | 46 | return instance; 47 | } 48 | 49 | void validator_is_file_free(ValidatorIsFile* instance) { 50 | furi_check(instance); 51 | free(instance->app_path_folder); 52 | free(instance->current_name); 53 | free(instance); 54 | } 55 | -------------------------------------------------------------------------------- /applications_user/lora_app/modules/text_input.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file text_input.h 3 | * GUI: TextInput keyboard view module API 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include "validators.h" 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | /** Text input anonymous structure */ 16 | typedef struct TextInput TextInput; 17 | typedef void (*TextInputCallback)(void* context); 18 | typedef bool (*TextInputValidatorCallback)(const char* text, FuriString* error, void* context); 19 | 20 | /** Allocate and initialize text input 21 | * 22 | * This text input is used to enter string 23 | * 24 | * @return TextInput instance 25 | */ 26 | TextInput* text_input_alloc(void); 27 | 28 | /** Deinitialize and free text input 29 | * 30 | * @param text_input TextInput instance 31 | */ 32 | void text_input_free(TextInput* text_input); 33 | 34 | /** Clean text input view Note: this function does not free memory 35 | * 36 | * @param text_input Text input instance 37 | */ 38 | void text_input_reset(TextInput* text_input); 39 | 40 | /** Get text input view 41 | * 42 | * @param text_input TextInput instance 43 | * 44 | * @return View instance that can be used for embedding 45 | */ 46 | View* text_input_get_view(TextInput* text_input); 47 | 48 | /** Set text input result callback 49 | * 50 | * @param text_input TextInput instance 51 | * @param callback callback fn 52 | * @param callback_context callback context 53 | * @param text_buffer pointer to YOUR text buffer, that we going 54 | * to modify 55 | * @param text_buffer_size YOUR text buffer size in bytes. Max string 56 | * length will be text_buffer_size-1. 57 | * @param clear_default_text clear text from text_buffer on first OK 58 | * event 59 | */ 60 | void text_input_set_result_callback( 61 | TextInput* text_input, 62 | TextInputCallback callback, 63 | void* callback_context, 64 | char* text_buffer, 65 | size_t text_buffer_size, 66 | bool clear_default_text); 67 | 68 | /** 69 | * @brief Sets the minimum length of a TextInput 70 | * @param [in] text_input TextInput 71 | * @param [in] minimum_length Minimum input length 72 | */ 73 | void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length); 74 | 75 | void text_input_set_validator( 76 | TextInput* text_input, 77 | TextInputValidatorCallback callback, 78 | void* callback_context); 79 | 80 | TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input); 81 | 82 | void* text_input_get_validator_callback_context(TextInput* text_input); 83 | 84 | /** Set text input header text 85 | * 86 | * @param text_input TextInput instance 87 | * @param text text to be shown 88 | */ 89 | void text_input_set_header_text(TextInput* text_input, const char* text); 90 | 91 | #ifdef __cplusplus 92 | } 93 | #endif 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub release (with filter)](https://img.shields.io/github/v/release/ElectronicCats/flipper-rs485modbus?color=%23008000) 2 | ![GitHub actions](https://img.shields.io/github/actions/workflow/status/ElectronicCats/flipper-rs485modbus/build.yml) 3 | 4 | # Flipper LoRa Relay App :dolphin: 5 | 6 | Work with LoRa radio communication signals. Now you can interact with LoRa transmissions using the Flipper Zero. Basic tasks such as sniffing and injection are available, making it easy to perform activities such as analysis, error detection and configuration of new peripherals to the network. 7 | 8 |

9 | Sniffing Screen 10 | Send Screen 11 |

12 | 13 |

14 | 15 | Wiki redirection button 16 | 17 |

18 | 19 | Requires the [**Electronic Cats Flipper Add-On: Sub-GHz**](https://electroniccats.com/store/flipper-add-on-subghz/). 20 | 21 | ## Features 22 | 23 | * Customize the LoRa parameters. 24 | * Menu for LoRaWAN US915 and EU868 25 | * Read and display data sniffed from LoRa devices. 26 | 27 | * Export sniffing sessions in LOG files to the SD card. 28 | * Send LoRa packets from the LOG file. 29 | 30 | 31 | ## How to contribute Electronic Cats LogoGitHub Logo 32 | 33 | Contributions are welcome! 34 | 35 | Please read the document [**Contribution Manual**](https://github.com/ElectronicCats/electroniccats-cla/blob/main/electroniccats-contribution-manual.md) which will show you how to contribute your changes to the project. 36 | 37 | ✨ Thanks to all our [**contributors**](https://github.com/ElectronicCats/flipper-SX1262-LoRa/graphs/contributors)! ✨ 38 | 39 | See [**_Electronic Cats CLA_**](https://github.com/ElectronicCats/electroniccats-cla/blob/main/electroniccats-cla.md) for more information. 40 | 41 | See the [**community code of conduct**](https://github.com/ElectronicCats/electroniccats-cla/blob/main/electroniccats-community-code-of-conduct.md) for a vision of the community we want to build and what we expect from it. 42 | 43 | ## Maintainer 44 | 45 |

46 | 47 | Sponsor button 48 | 49 |

50 | 51 | Electronic Cats invests time and resources in providing this open-source design, please support Electronic Cats and open-source hardware by purchasing products from Electronic Cats! 52 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignArrayOfStructures: None 6 | AlignConsecutiveAssignments: 7 | Enabled: false 8 | AcrossEmptyLines: false 9 | AcrossComments: false 10 | AlignCompound: false 11 | AlignFunctionPointers: false 12 | PadOperators: true 13 | AlignConsecutiveBitFields: 14 | Enabled: true 15 | AcrossEmptyLines: true 16 | AcrossComments: true 17 | AlignCompound: false 18 | AlignFunctionPointers: false 19 | PadOperators: true 20 | AlignConsecutiveDeclarations: 21 | Enabled: false 22 | AcrossEmptyLines: false 23 | AcrossComments: false 24 | AlignCompound: false 25 | AlignFunctionPointers: false 26 | PadOperators: true 27 | AlignConsecutiveMacros: 28 | Enabled: true 29 | AcrossEmptyLines: false 30 | AcrossComments: true 31 | AlignCompound: true 32 | AlignFunctionPointers: false 33 | PadOperators: true 34 | AlignConsecutiveShortCaseStatements: 35 | Enabled: false 36 | AcrossEmptyLines: false 37 | AcrossComments: false 38 | AlignCaseColons: false 39 | AlignEscapedNewlines: Left 40 | AlignOperands: Align 41 | AlignTrailingComments: 42 | Kind: Never 43 | OverEmptyLines: 0 44 | AllowAllArgumentsOnNextLine: true 45 | AllowAllParametersOfDeclarationOnNextLine: false 46 | AllowBreakBeforeNoexceptSpecifier: Never 47 | AllowShortBlocksOnASingleLine: Never 48 | AllowShortCaseLabelsOnASingleLine: false 49 | AllowShortCompoundRequirementOnASingleLine: true 50 | AllowShortEnumsOnASingleLine: false 51 | AllowShortFunctionsOnASingleLine: None 52 | AllowShortIfStatementsOnASingleLine: WithoutElse 53 | AllowShortLambdasOnASingleLine: All 54 | AllowShortLoopsOnASingleLine: false 55 | AlwaysBreakAfterDefinitionReturnType: None 56 | AlwaysBreakAfterReturnType: None 57 | AlwaysBreakBeforeMultilineStrings: false 58 | AlwaysBreakTemplateDeclarations: Yes 59 | AttributeMacros: 60 | - __capability 61 | BinPackArguments: false 62 | BinPackParameters: false 63 | BitFieldColonSpacing: Both 64 | BraceWrapping: 65 | AfterCaseLabel: false 66 | AfterClass: false 67 | AfterControlStatement: Never 68 | AfterEnum: false 69 | AfterExternBlock: false 70 | AfterFunction: false 71 | AfterNamespace: false 72 | AfterObjCDeclaration: false 73 | AfterStruct: false 74 | AfterUnion: false 75 | BeforeCatch: false 76 | BeforeElse: false 77 | BeforeLambdaBody: false 78 | BeforeWhile: false 79 | IndentBraces: false 80 | SplitEmptyFunction: true 81 | SplitEmptyRecord: true 82 | SplitEmptyNamespace: true 83 | BreakAdjacentStringLiterals: true 84 | BreakAfterAttributes: Leave 85 | BreakAfterJavaFieldAnnotations: false 86 | BreakArrays: true 87 | BreakBeforeBinaryOperators: None 88 | BreakBeforeConceptDeclarations: Always 89 | BreakBeforeBraces: Attach 90 | BreakBeforeInlineASMColon: OnlyMultiline 91 | BreakBeforeTernaryOperators: false 92 | BreakConstructorInitializers: BeforeComma 93 | BreakInheritanceList: BeforeColon 94 | BreakStringLiterals: false 95 | ColumnLimit: 99 96 | CommentPragmas: '^ IWYU pragma:' 97 | CompactNamespaces: false 98 | ConstructorInitializerIndentWidth: 4 99 | ContinuationIndentWidth: 4 100 | Cpp11BracedListStyle: true 101 | DerivePointerAlignment: false 102 | DisableFormat: false 103 | EmptyLineAfterAccessModifier: Never 104 | EmptyLineBeforeAccessModifier: LogicalBlock 105 | ExperimentalAutoDetectBinPacking: false 106 | FixNamespaceComments: false 107 | ForEachMacros: 108 | - foreach 109 | - Q_FOREACH 110 | - BOOST_FOREACH 111 | - M_EACH 112 | IfMacros: 113 | - KJ_IF_MAYBE 114 | IncludeBlocks: Preserve 115 | IncludeCategories: 116 | - Regex: '.*' 117 | Priority: 1 118 | SortPriority: 0 119 | CaseSensitive: false 120 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 121 | Priority: 3 122 | SortPriority: 0 123 | CaseSensitive: false 124 | - Regex: '.*' 125 | Priority: 1 126 | SortPriority: 0 127 | CaseSensitive: false 128 | IncludeIsMainRegex: '(Test)?$' 129 | IncludeIsMainSourceRegex: '' 130 | IndentAccessModifiers: false 131 | IndentCaseBlocks: false 132 | IndentCaseLabels: false 133 | IndentExternBlock: AfterExternBlock 134 | IndentGotoLabels: true 135 | IndentPPDirectives: None 136 | IndentRequiresClause: false 137 | IndentWidth: 4 138 | IndentWrappedFunctionNames: true 139 | InsertBraces: false 140 | InsertNewlineAtEOF: true 141 | InsertTrailingCommas: None 142 | IntegerLiteralSeparator: 143 | Binary: 0 144 | BinaryMinDigits: 0 145 | Decimal: 0 146 | DecimalMinDigits: 0 147 | Hex: 0 148 | HexMinDigits: 0 149 | JavaScriptQuotes: Leave 150 | JavaScriptWrapImports: true 151 | KeepEmptyLinesAtTheStartOfBlocks: false 152 | KeepEmptyLinesAtEOF: false 153 | LambdaBodyIndentation: Signature 154 | LineEnding: DeriveLF 155 | MacroBlockBegin: '' 156 | MacroBlockEnd: '' 157 | MaxEmptyLinesToKeep: 1 158 | NamespaceIndentation: None 159 | ObjCBinPackProtocolList: Auto 160 | ObjCBlockIndentWidth: 4 161 | ObjCBreakBeforeNestedBlockParam: true 162 | ObjCSpaceAfterProperty: true 163 | ObjCSpaceBeforeProtocolList: true 164 | PackConstructorInitializers: BinPack 165 | PenaltyBreakAssignment: 10 166 | PenaltyBreakBeforeFirstCallParameter: 30 167 | PenaltyBreakComment: 10 168 | PenaltyBreakFirstLessLess: 0 169 | PenaltyBreakOpenParenthesis: 0 170 | PenaltyBreakScopeResolution: 500 171 | PenaltyBreakString: 10 172 | PenaltyBreakTemplateDeclaration: 10 173 | PenaltyExcessCharacter: 100 174 | PenaltyIndentedWhitespace: 0 175 | PenaltyReturnTypeOnItsOwnLine: 60 176 | PointerAlignment: Left 177 | PPIndentWidth: -1 178 | QualifierAlignment: Leave 179 | ReferenceAlignment: Pointer 180 | ReflowComments: false 181 | RemoveBracesLLVM: false 182 | RemoveParentheses: Leave 183 | RemoveSemicolon: true 184 | RequiresClausePosition: OwnLine 185 | RequiresExpressionIndentation: OuterScope 186 | SeparateDefinitionBlocks: Leave 187 | ShortNamespaceLines: 1 188 | SkipMacroDefinitionBody: false 189 | SortIncludes: Never 190 | SortJavaStaticImport: Before 191 | SortUsingDeclarations: Never 192 | SpaceAfterCStyleCast: false 193 | SpaceAfterLogicalNot: false 194 | SpaceAfterTemplateKeyword: true 195 | SpaceAroundPointerQualifiers: Default 196 | SpaceBeforeAssignmentOperators: true 197 | SpaceBeforeCaseColon: false 198 | SpaceBeforeCpp11BracedList: false 199 | SpaceBeforeCtorInitializerColon: true 200 | SpaceBeforeInheritanceColon: true 201 | SpaceBeforeJsonColon: false 202 | SpaceBeforeParens: Never 203 | SpaceBeforeParensOptions: 204 | AfterControlStatements: false 205 | AfterForeachMacros: false 206 | AfterFunctionDefinitionName: false 207 | AfterFunctionDeclarationName: false 208 | AfterIfMacros: false 209 | AfterOverloadedOperator: false 210 | AfterPlacementOperator: true 211 | AfterRequiresInClause: false 212 | AfterRequiresInExpression: false 213 | BeforeNonEmptyParentheses: false 214 | SpaceBeforeRangeBasedForLoopColon: true 215 | SpaceBeforeSquareBrackets: false 216 | SpaceInEmptyBlock: false 217 | SpacesBeforeTrailingComments: 1 218 | SpacesInAngles: Never 219 | SpacesInContainerLiterals: false 220 | SpacesInLineCommentPrefix: 221 | Minimum: 1 222 | Maximum: -1 223 | SpacesInParens: Never 224 | SpacesInParensOptions: 225 | InCStyleCasts: false 226 | InConditionalStatements: false 227 | InEmptyParentheses: false 228 | Other: false 229 | SpacesInSquareBrackets: false 230 | Standard: c++20 231 | StatementAttributeLikeMacros: 232 | - Q_EMIT 233 | StatementMacros: 234 | - Q_UNUSED 235 | - QT_REQUIRE_VERSION 236 | TabWidth: 4 237 | UseTab: Never 238 | VerilogBreakBetweenInstancePorts: true 239 | WhitespaceSensitiveMacros: 240 | - STRINGIZE 241 | - PP_STRINGIZE 242 | - BOOST_PP_STRINGIZE 243 | - NS_SWIFT_NAME 244 | - CF_SWIFT_NAME 245 | ... 246 | -------------------------------------------------------------------------------- /applications_user/lora_app/assets_icons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern const Icon I_Certification1_103x56; 6 | extern const Icon I_Certification2_46x33; 7 | extern const Icon I_CertificationChina0_121x41; 8 | extern const Icon I_CertificationChina1_124x47; 9 | extern const Icon I_CertificationMexico_98x41; 10 | extern const Icon I_CertificationTaiwan_33x32; 11 | extern const Icon A_Levelup1_128x64; 12 | extern const Icon A_Levelup2_128x64; 13 | extern const Icon I_125_10px; 14 | extern const Icon I_Nfc_10px; 15 | extern const Icon I_back_10px; 16 | extern const Icon I_badusb_10px; 17 | extern const Icon I_dir_10px; 18 | extern const Icon I_file_10px; 19 | extern const Icon I_ibutt_10px; 20 | extern const Icon I_ir_10px; 21 | extern const Icon I_js_script_10px; 22 | extern const Icon I_keyboard_10px; 23 | extern const Icon I_loading_10px; 24 | extern const Icon I_music_10px; 25 | extern const Icon I_settings_10px; 26 | extern const Icon I_sub1_10px; 27 | extern const Icon I_u2f_10px; 28 | extern const Icon I_unknown_10px; 29 | extern const Icon I_update_10px; 30 | extern const Icon I_BLE_Pairing_128x64; 31 | extern const Icon I_Ble_connected_15x15; 32 | extern const Icon I_Ble_disconnected_15x15; 33 | extern const Icon I_Button_18x18; 34 | extern const Icon I_Circles_47x47; 35 | extern const Icon I_Left_mouse_icon_9x9; 36 | extern const Icon I_Ok_btn_9x9; 37 | extern const Icon I_Ok_btn_pressed_13x13; 38 | extern const Icon I_Pressed_Button_13x13; 39 | extern const Icon I_Right_mouse_icon_9x9; 40 | extern const Icon I_Space_65x18; 41 | extern const Icon I_Voldwn_6x6; 42 | extern const Icon I_Volup_8x6; 43 | extern const Icon I_Bad_BLE_48x22; 44 | extern const Icon I_Clock_18x18; 45 | extern const Icon I_Error_18x18; 46 | extern const Icon I_EviSmile1_18x21; 47 | extern const Icon I_EviSmile2_18x21; 48 | extern const Icon I_EviWaiting1_18x21; 49 | extern const Icon I_EviWaiting2_18x21; 50 | extern const Icon I_Percent_10x14; 51 | extern const Icon I_Smile_18x18; 52 | extern const Icon I_UsbTree_48x22; 53 | extern const Icon I_ActiveConnection_50x64; 54 | extern const Icon I_ButtonCenter_7x7; 55 | extern const Icon I_ButtonDown_7x4; 56 | extern const Icon I_ButtonLeftSmall_3x5; 57 | extern const Icon I_ButtonLeft_4x7; 58 | extern const Icon I_ButtonRightSmall_3x5; 59 | extern const Icon I_ButtonRight_4x7; 60 | extern const Icon I_ButtonUp_7x4; 61 | extern const Icon I_DFU_128x50; 62 | extern const Icon I_Hashmark_7x7; 63 | extern const Icon I_More_data_placeholder_5x7; 64 | extern const Icon I_Warning_30x23; 65 | extern const Icon I_arrow_nano_down; 66 | extern const Icon I_arrow_nano_up; 67 | extern const Icon A_Loading_24; 68 | extern const Icon A_Round_loader_8x8; 69 | extern const Icon I_DolphinDone_80x58; 70 | extern const Icon I_DolphinMafia_119x62; 71 | extern const Icon I_DolphinReadingSuccess_59x63; 72 | extern const Icon I_DolphinSaved_92x58; 73 | extern const Icon I_DolphinSuccess_91x55; 74 | extern const Icon I_DolphinWait_59x54; 75 | extern const Icon I_WarningDolphinFlip_45x42; 76 | extern const Icon I_WarningDolphin_45x42; 77 | extern const Icon I_Erase_pin_128x64; 78 | extern const Icon I_ArrowUpEmpty_14x15; 79 | extern const Icon I_ArrowUpFilled_14x15; 80 | extern const Icon I_InfraredArrowDown_4x8; 81 | extern const Icon I_InfraredArrowUp_4x8; 82 | extern const Icon I_InfraredLearnShort_128x31; 83 | extern const Icon I_back_btn_10x8; 84 | extern const Icon I_celsius_24x23; 85 | extern const Icon I_celsius_hover_24x23; 86 | extern const Icon I_ch_down_24x21; 87 | extern const Icon I_ch_down_hover_24x21; 88 | extern const Icon I_ch_text_31x34; 89 | extern const Icon I_ch_up_24x21; 90 | extern const Icon I_ch_up_hover_24x21; 91 | extern const Icon I_cool_30x51; 92 | extern const Icon I_dry_19x20; 93 | extern const Icon I_dry_hover_19x20; 94 | extern const Icon I_dry_text_15x5; 95 | extern const Icon I_fahren_24x23; 96 | extern const Icon I_fahren_hover_24x23; 97 | extern const Icon I_heat_30x51; 98 | extern const Icon I_hourglass0_24x24; 99 | extern const Icon I_hourglass1_24x24; 100 | extern const Icon I_hourglass2_24x24; 101 | extern const Icon I_hourglass3_24x24; 102 | extern const Icon I_hourglass4_24x24; 103 | extern const Icon I_hourglass5_24x24; 104 | extern const Icon I_hourglass6_24x24; 105 | extern const Icon I_max_24x23; 106 | extern const Icon I_max_hover_24x23; 107 | extern const Icon I_mute_19x20; 108 | extern const Icon I_mute_hover_19x20; 109 | extern const Icon I_mute_text_19x5; 110 | extern const Icon I_next_19x20; 111 | extern const Icon I_next_hover_19x20; 112 | extern const Icon I_next_text_19x6; 113 | extern const Icon I_off_19x20; 114 | extern const Icon I_off_hover_19x20; 115 | extern const Icon I_off_text_12x5; 116 | extern const Icon I_pause_19x20; 117 | extern const Icon I_pause_hover_19x20; 118 | extern const Icon I_pause_text_23x5; 119 | extern const Icon I_play_19x20; 120 | extern const Icon I_play_hover_19x20; 121 | extern const Icon I_play_text_19x5; 122 | extern const Icon I_power_19x20; 123 | extern const Icon I_power_hover_19x20; 124 | extern const Icon I_power_text_24x5; 125 | extern const Icon I_prev_19x20; 126 | extern const Icon I_prev_hover_19x20; 127 | extern const Icon I_prev_text_19x5; 128 | extern const Icon I_vol_ac_text_30x30; 129 | extern const Icon I_vol_tv_text_29x34; 130 | extern const Icon I_voldown_24x21; 131 | extern const Icon I_voldown_hover_24x21; 132 | extern const Icon I_volup_24x21; 133 | extern const Icon I_volup_hover_24x21; 134 | extern const Icon I_DoorLeft_70x55; 135 | extern const Icon I_DoorRight_70x55; 136 | extern const Icon I_SmallArrowDown_3x5; 137 | extern const Icon I_SmallArrowDown_4x7; 138 | extern const Icon I_SmallArrowUp_3x5; 139 | extern const Icon I_SmallArrowUp_4x7; 140 | extern const Icon I_KeyBackspaceSelected_16x9; 141 | extern const Icon I_KeyBackspace_16x9; 142 | extern const Icon I_KeySaveBlockedSelected_24x11; 143 | extern const Icon I_KeySaveBlocked_24x11; 144 | extern const Icon I_KeySaveSelected_24x11; 145 | extern const Icon I_KeySave_24x11; 146 | extern const Icon I_KeySignSelected_21x11; 147 | extern const Icon I_KeySign_21x11; 148 | extern const Icon I_err_01; 149 | extern const Icon I_err_02; 150 | extern const Icon I_err_03; 151 | extern const Icon I_err_04; 152 | extern const Icon I_err_05; 153 | extern const Icon I_err_06; 154 | extern const Icon I_err_07; 155 | extern const Icon I_err_09; 156 | extern const Icon A_125khz_14; 157 | extern const Icon A_BadUsb_14; 158 | extern const Icon A_Debug_14; 159 | extern const Icon A_FileManager_14; 160 | extern const Icon A_GPIO_14; 161 | extern const Icon A_Infrared_14; 162 | extern const Icon A_NFC_14; 163 | extern const Icon A_Plugins_14; 164 | extern const Icon A_Settings_14; 165 | extern const Icon A_Sub1ghz_14; 166 | extern const Icon A_U2F_14; 167 | extern const Icon A_iButton_14; 168 | extern const Icon I_ArrowC_1_36x36; 169 | extern const Icon I_Detailed_chip_17x13; 170 | extern const Icon I_Keychain_39x36; 171 | extern const Icon I_MFKey_qr_25x25; 172 | extern const Icon I_Medium_chip_22x21; 173 | extern const Icon I_Modern_reader_18x34; 174 | extern const Icon I_Move_flipper_26x39; 175 | extern const Icon I_NFC_dolphin_emulation_51x64; 176 | extern const Icon I_NFC_manual_60x50; 177 | extern const Icon I_Release_arrow_18x15; 178 | extern const Icon I_check_big_20x17; 179 | extern const Icon I_Pin_arrow_up_7x9; 180 | extern const Icon I_Pin_attention_dpad_29x29; 181 | extern const Icon I_Pin_back_arrow_10x8; 182 | extern const Icon I_Pin_pointer_5x3; 183 | extern const Icon I_Pin_star_7x7; 184 | extern const Icon I_passport_bad1_46x49; 185 | extern const Icon I_passport_bad2_46x49; 186 | extern const Icon I_passport_bad3_46x49; 187 | extern const Icon I_passport_bottom_128x18; 188 | extern const Icon I_passport_happy1_46x49; 189 | extern const Icon I_passport_happy2_46x49; 190 | extern const Icon I_passport_happy3_46x49; 191 | extern const Icon I_passport_left_6x46; 192 | extern const Icon I_passport_okay1_46x49; 193 | extern const Icon I_passport_okay2_46x49; 194 | extern const Icon I_passport_okay3_46x49; 195 | extern const Icon I_BatteryBody_52x28; 196 | extern const Icon I_Battery_16x16; 197 | extern const Icon I_FaceCharging_29x14; 198 | extern const Icon I_FaceConfused_29x14; 199 | extern const Icon I_FaceNopower_29x14; 200 | extern const Icon I_FaceNormal_29x14; 201 | extern const Icon I_Health_16x16; 202 | extern const Icon I_Temperature_16x16; 203 | extern const Icon I_Unplug_bg_bottom_128x10; 204 | extern const Icon I_Unplug_bg_top_128x14; 205 | extern const Icon I_Voltage_16x16; 206 | extern const Icon I_RFIDBigChip_37x36; 207 | extern const Icon I_RFIDDolphinReceive_97x61; 208 | extern const Icon I_RFIDDolphinSend_97x61; 209 | extern const Icon I_SDQuestion_35x43; 210 | extern const Icon I_LoadingHourglass_24x24; 211 | extern const Icon I_dolph_cry_49x54; 212 | extern const Icon I_qr_benchmark_25x25; 213 | extern const Icon A_Alarm_47x39; 214 | extern const Icon I_Alert_9x8; 215 | extern const Icon I_Attention_5x8; 216 | extern const Icon I_BLE_beacon_7x8; 217 | extern const Icon I_Background_128x11; 218 | extern const Icon I_Battery_26x8; 219 | extern const Icon I_Bluetooth_Connected_16x8; 220 | extern const Icon I_Bluetooth_Idle_5x8; 221 | extern const Icon I_Charging_lightning_9x10; 222 | extern const Icon I_Charging_lightning_mask_9x10; 223 | extern const Icon I_Exp_module_connected_12x8; 224 | extern const Icon I_GameMode_11x8; 225 | extern const Icon I_Hidden_window_9x8; 226 | extern const Icon I_Muted_8x8; 227 | extern const Icon I_Rpc_active_7x8; 228 | extern const Icon I_SDcardFail_11x8; 229 | extern const Icon I_SDcardMounted_11x8; 230 | extern const Icon I_External_ant_1_9x11; 231 | extern const Icon I_Internal_ant_1_9x11; 232 | extern const Icon I_Lock_7x8; 233 | extern const Icon I_MHz_25x11; 234 | extern const Icon I_Quest_7x8; 235 | extern const Icon I_Scanning_short_96x52; 236 | extern const Icon I_Unlock_7x8; 237 | extern const Icon A_SubGhz_External_ant; 238 | extern const Icon A_SubGhz_Internal_ant; 239 | extern const Icon I_Auth_62x31; 240 | extern const Icon I_Connect_me_62x31; 241 | extern const Icon I_Connected_62x31; 242 | extern const Icon I_Drive_112x35; 243 | extern const Icon I_Error_62x31; 244 | extern const Icon I_Updating_32x40; 245 | extern const Icon I_iButtonDolphinVerySuccess_92x55; 246 | extern const Icon I_iButtonKey_49x44; 247 | -------------------------------------------------------------------------------- /meshtasticDashboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Meshtastic Flipper Zero Monitor 4 | 5 | Live TUI viewer for Meshtastic LoRa packets captured via Flipper Zero LoRa app. 6 | Decrypts and displays text messages (including default channel 0). 7 | 8 | Requirements: 9 | pip install textual rich cryptography meshtastic 10 | 11 | Usage: 12 | python meshtasticDashboard.py [-p PORT] [-baud BAUDRATE] 13 | 14 | Options: 15 | -p, --port Serial port (e.g. /dev/ttyACM0 or COM3) 16 | -baud, --baudrate Baud rate (default: 115200) 17 | 18 | Press 'q' to quit. 19 | 20 | Note: Use Flipper Zero LoRa app in "Sniffer" mode. 21 | """ 22 | import serial 23 | from serial.tools import list_ports 24 | import argparse 25 | import asyncio 26 | import base64 27 | import queue 28 | import sys 29 | import threading 30 | import time 31 | from dataclasses import dataclass 32 | from datetime import datetime 33 | from typing import Dict, Optional, Tuple 34 | 35 | from cryptography.hazmat.backends import default_backend 36 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 37 | from rich.markup import escape as rich_escape 38 | from rich.text import Text 39 | from textual.app import App, ComposeResult 40 | from textual.binding import Binding 41 | from textual.containers import Container 42 | from textual.widgets import DataTable, Static, Footer 43 | 44 | from meshtastic import mesh_pb2 45 | 46 | DEFAULT_BAUDRATE = 115200 47 | DEFAULT_CRLF = "\r\n" 48 | DEFAULT_COMPORT = "/dev/ttyACM0" 49 | 50 | 51 | def find_serial_port(): 52 | ports = list_ports.comports() 53 | for port in ports: 54 | return port.device 55 | return DEFAULT_COMPORT 56 | 57 | 58 | class SerialError(Exception): 59 | pass 60 | 61 | 62 | class serialDevice: 63 | def __init__(self, port=find_serial_port(), baudrate=DEFAULT_BAUDRATE): 64 | if port is None: 65 | raise SerialError("Serial port is None") 66 | if baudrate is None: 67 | baudrate = DEFAULT_BAUDRATE 68 | self.serial_device = serial.Serial(port=port, baudrate=baudrate) 69 | self.serial_line_control = DEFAULT_CRLF 70 | self.serial_alive = False 71 | 72 | def set_serial_port(self, port): 73 | self.serial_device.port = port 74 | 75 | def set_baudrate(self, baudrate): 76 | if baudrate not in serial.Serial.BAUDRATES: 77 | raise ValueError("Invalid baudrate") 78 | self.serial_device.baudrate = baudrate 79 | 80 | def get_port(self): 81 | return self.serial_device.port 82 | 83 | def is_connected(self): 84 | return self.serial_device.is_open 85 | 86 | def resetBuffer(self): 87 | self.serial_device.reset_input_buffer() 88 | self.serial_device.reset_output_buffer() 89 | 90 | def validate_connection(self): 91 | try: 92 | self.serial_device.open() 93 | self.close() 94 | return True 95 | except (serial.SerialException, FileNotFoundError): 96 | return False 97 | 98 | def open(self): 99 | try: 100 | if self.is_connected(): 101 | self.close() 102 | 103 | self.serial_device.open() 104 | 105 | self.resetBuffer() 106 | self.serial_alive = True 107 | except serial.SerialException as e: 108 | raise SerialError(e) 109 | 110 | def close(self): 111 | if self.is_connected(): 112 | try: 113 | self.serial_device.close() 114 | self.serial_alive = False 115 | except serial.SerialException as e: 116 | raise SerialError(e) 117 | 118 | def recv(self): 119 | try: 120 | if self.serial_device.in_waiting > 0: 121 | bytestream = self.serial_device.readline() 122 | return bytestream 123 | except (serial.SerialException, UnicodeDecodeError): 124 | self.serial_alive = False 125 | return None 126 | 127 | def transmit(self, data): 128 | try: 129 | message = f"{data}{self.serial_line_control}" 130 | self.serial_device.write(message.encode()) 131 | except serial.SerialException as e: 132 | print(f"Error: {e}") 133 | 134 | def reconnect(self, max_attempts=5): 135 | attempts = 0 136 | while not self.serial_alive and attempts < max_attempts: 137 | time.sleep(3) 138 | try: 139 | print("Reconnecting") 140 | self.open() 141 | return 142 | except SerialError: 143 | attempts += 1 144 | if not self.serial_alive: 145 | print("No connected") 146 | else: 147 | print("Connected") 148 | 149 | 150 | # ============================== 151 | # TUI Meshtastic 152 | # ============================== 153 | 154 | DEFAULT_KEYS = [ 155 | "1PG7OiApB1nwvP+rz05pAQ==", # Channel 0 (default) 156 | "OEu8wB3AItGBvza4YSHh+5a3LlW/dCJ+nWr7SNZMsaE=", 157 | "6IzsaoVhx1ETWeWuu0dUWMLqItvYJLbRzwgTAKCfvtY=", 158 | "TiIdi8MJG+IRnIkS8iUZXRU+MHuGtuzEasOWXp4QndU=", 159 | "pHRzd/zw0r5DLAQavv84XfU2iLzjk3HczbdIXkgt5Mk=", 160 | ] 161 | 162 | 163 | def extract_frame(raw: bytes) -> bytes: 164 | if not raw.startswith(b"@S") or not raw.endswith(b"@E\r\n"): 165 | raise ValueError("Invalid frame") 166 | length = int.from_bytes(raw[2:4], "big") 167 | return raw[4:4 + length] 168 | 169 | 170 | def extract_fields(data: bytes) -> Dict[str, bytes]: 171 | return { 172 | "dest": data[0:4], 173 | "sender": data[4:8], 174 | "packet_id": data[8:12], 175 | "payload": data[16:], 176 | } 177 | 178 | 179 | def decrypt(payload: bytes, key: bytes, sender: bytes, packet_id: bytes) -> bytes: 180 | nonce = packet_id + b"\x00\x00\x00\x00" + sender + b"\x00\x00\x00\x00" 181 | cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=default_backend()) 182 | return cipher.decryptor().update(payload) 183 | 184 | 185 | def sanitize_text(s: str) -> str: 186 | cleaned = [] 187 | for ch in s: 188 | if ch in "\r": 189 | continue 190 | if ch == "\n" or ch == "\t" or ord(ch) >= 32: 191 | cleaned.append(ch) 192 | else: 193 | cleaned.append("�") 194 | return rich_escape("".join(cleaned)) 195 | 196 | 197 | @dataclass 198 | class ChatMessage: 199 | ts: float 200 | sender_name: str 201 | text: str 202 | 203 | def as_row(self) -> Tuple[str, str, str, str]: 204 | return ( 205 | datetime.fromtimestamp(self.ts).strftime("%H:%M:%S"), 206 | self.sender_name, 207 | self.text, 208 | ) 209 | 210 | 211 | class NodeRegistry: 212 | def __init__(self) -> None: 213 | self.nodes: Dict[str, str] = {} 214 | 215 | def set_from_user_pb(self, user: mesh_pb2.User) -> None: 216 | node_id = user.id or "" 217 | name = user.long_name or user.short_name or node_id 218 | self.nodes[node_id] = sanitize_text(name) 219 | 220 | def resolve(self, node_id: str) -> str: 221 | return self.nodes.get(node_id, node_id) 222 | 223 | 224 | class Monitor(serialDevice): 225 | def __init__(self, port: str, baudrate: int, rx_queue: queue.Queue) -> None: 226 | super().__init__(port, baudrate) 227 | self.rx_queue = rx_queue 228 | self.running = True 229 | 230 | def start(self) -> None: 231 | self.open() 232 | threading.Thread(target=self._recv_worker, daemon=True).start() 233 | 234 | def _recv_worker(self) -> None: 235 | while self.running: 236 | try: 237 | data = self.recv() 238 | if data: 239 | self.rx_queue.put(data) 240 | except Exception: 241 | break 242 | 243 | def stop(self) -> None: 244 | self.running = False 245 | self.close() 246 | 247 | 248 | class ChatApp(App): 249 | CSS = "Screen { layout: vertical; }" 250 | BINDINGS = [Binding("q", "quit", "Quit")] 251 | 252 | def __init__(self, monitor: Monitor) -> None: 253 | super().__init__() 254 | self.monitor = monitor 255 | self.rx_queue = monitor.rx_queue 256 | self.keys = [base64.b64decode(k) for k in DEFAULT_KEYS] 257 | self.node_registry = NodeRegistry() 258 | self.table = DataTable() 259 | self.status = Static() 260 | 261 | def compose(self) -> ComposeResult: 262 | yield Container(self.table) 263 | yield Footer() 264 | yield self.status 265 | 266 | async def on_mount(self) -> None: 267 | self.table.add_columns("Time", "From", "Message") 268 | self.set_interval(0.1, self._pump_queue) 269 | 270 | def _pump_queue(self) -> None: 271 | while not self.rx_queue.empty(): 272 | frame = self.rx_queue.get_nowait() 273 | asyncio.create_task(self._process_frame(frame)) 274 | 275 | async def _process_frame(self, frame: bytes) -> None: 276 | self.status.update(f"[dim]{frame.hex(' ')}[/dim]") 277 | try: 278 | raw = extract_frame(frame) 279 | fields = extract_fields(raw) 280 | except Exception as e: 281 | self.status.update(f"[red]Error: {e}[/red]") 282 | return 283 | 284 | sender_hex = fields["sender"].hex().upper() 285 | payload = fields["payload"] 286 | 287 | pb = mesh_pb2.Data() 288 | try: 289 | pb.ParseFromString(payload) 290 | if self._handle_data_packet(pb, sender_hex): 291 | self.status.update("[green]Mensaje sin encriptar[/green]") 292 | return 293 | except Exception: 294 | pass 295 | 296 | for key_b64 in DEFAULT_KEYS: 297 | key = base64.b64decode(key_b64) 298 | try: 299 | decrypted = decrypt(payload, key, fields["sender"], fields["packet_id"]) 300 | pb = mesh_pb2.Data() 301 | pb.ParseFromString(decrypted) 302 | if self._handle_data_packet(pb, sender_hex): 303 | self.status.update(f"[green]Desencriptado[/green]") 304 | return 305 | except Exception: 306 | continue 307 | 308 | self.status.update(f"[yellow]No desencriptable con {len(DEFAULT_KEYS)} claves[/yellow]") 309 | 310 | def _handle_data_packet(self, pb: mesh_pb2.Data, sender_hex: str) -> bool: 311 | if pb.portnum == 4: 312 | try: 313 | user = mesh_pb2.User() 314 | user.ParseFromString(pb.payload) 315 | self.node_registry.set_from_user_pb(user) 316 | except Exception: 317 | pass 318 | return True 319 | 320 | if pb.portnum == 1: 321 | try: 322 | text = pb.payload.decode("utf-8", errors="replace") 323 | except Exception: 324 | text = "" 325 | msg = ChatMessage( 326 | ts=time.time(), 327 | sender_name=self.node_registry.resolve(sender_hex), 328 | text=sanitize_text(text), 329 | ) 330 | self.table.add_row(*msg.as_row()) 331 | return True 332 | return False 333 | 334 | def action_quit(self) -> None: 335 | self.exit() 336 | 337 | 338 | async def run_app(args): 339 | rx_queue = queue.Queue() 340 | mon = Monitor(args.port, args.baudrate, rx_queue) 341 | mon.start() 342 | try: 343 | await ChatApp(mon).run_async() 344 | finally: 345 | mon.stop() 346 | 347 | 348 | def main(): 349 | parser = argparse.ArgumentParser() 350 | parser.add_argument("-p", "--port", default=find_serial_port()) 351 | parser.add_argument("-baud", "--baudrate", type=int, default=DEFAULT_BAUDRATE) 352 | args = parser.parse_args() 353 | asyncio.run(run_app(args)) 354 | 355 | 356 | if __name__ == "__main__": 357 | main() 358 | -------------------------------------------------------------------------------- /applications_user/lora_app/modules/text_input.c: -------------------------------------------------------------------------------- 1 | #include "text_input.h" 2 | #include 3 | #include 4 | #include 5 | 6 | struct TextInput { 7 | View* view; 8 | FuriTimer* timer; 9 | }; 10 | 11 | typedef struct { 12 | const char text; 13 | const uint8_t x; 14 | const uint8_t y; 15 | } TextInputKey; 16 | 17 | typedef struct { 18 | const char* header; 19 | char* text_buffer; 20 | size_t text_buffer_size; 21 | size_t minimum_length; 22 | bool clear_default_text; 23 | 24 | TextInputCallback callback; 25 | void* callback_context; 26 | 27 | uint8_t selected_row; 28 | uint8_t selected_column; 29 | 30 | TextInputValidatorCallback validator_callback; 31 | void* validator_callback_context; 32 | FuriString* validator_text; 33 | bool validator_message_visible; 34 | } TextInputModel; 35 | 36 | static const uint8_t keyboard_origin_x = 1; 37 | static const uint8_t keyboard_origin_y = 29; 38 | static const uint8_t keyboard_row_count = 3; 39 | 40 | #define ENTER_KEY '\r' 41 | #define BACKSPACE_KEY '\b' 42 | 43 | static const TextInputKey keyboard_keys_row_1[] = { 44 | {'q', 1, 8}, 45 | {'w', 10, 8}, 46 | {'e', 19, 8}, 47 | {'r', 28, 8}, 48 | {'t', 37, 8}, 49 | {'y', 46, 8}, 50 | {'u', 55, 8}, 51 | {'i', 64, 8}, 52 | {'o', 73, 8}, 53 | {'p', 82, 8}, 54 | {'0', 91, 8}, 55 | {'1', 100, 8}, 56 | {'2', 110, 8}, 57 | {'3', 120, 8}, 58 | }; 59 | 60 | static const TextInputKey keyboard_keys_row_2[] = { 61 | {'a', 1, 20}, 62 | {'s', 10, 20}, 63 | {'d', 19, 20}, 64 | {'f', 28, 20}, 65 | {'g', 37, 20}, 66 | {'h', 46, 20}, 67 | {'j', 55, 20}, 68 | {'k', 64, 20}, 69 | {'l', 73, 20}, 70 | {BACKSPACE_KEY, 82, 12}, 71 | {'4', 100, 20}, 72 | {'5', 110, 20}, 73 | {'6', 120, 20}, 74 | }; 75 | 76 | static const TextInputKey keyboard_keys_row_3[] = { 77 | {'z', 1, 32}, 78 | {'x', 10, 32}, 79 | {'c', 19, 32}, 80 | {'v', 28, 32}, 81 | {'b', 37, 32}, 82 | {'n', 46, 32}, 83 | {'m', 55, 32}, 84 | {'.', 64, 32}, 85 | {ENTER_KEY, 74, 23}, 86 | {'7', 100, 32}, 87 | {'8', 110, 32}, 88 | {'9', 120, 32}, 89 | }; 90 | 91 | static uint8_t get_row_size(uint8_t row_index) { 92 | uint8_t row_size = 0; 93 | 94 | switch(row_index + 1) { 95 | case 1: 96 | row_size = COUNT_OF(keyboard_keys_row_1); 97 | break; 98 | case 2: 99 | row_size = COUNT_OF(keyboard_keys_row_2); 100 | break; 101 | case 3: 102 | row_size = COUNT_OF(keyboard_keys_row_3); 103 | break; 104 | default: 105 | furi_crash(); 106 | } 107 | 108 | return row_size; 109 | } 110 | 111 | static const TextInputKey* get_row(uint8_t row_index) { 112 | const TextInputKey* row = NULL; 113 | 114 | switch(row_index + 1) { 115 | case 1: 116 | row = keyboard_keys_row_1; 117 | break; 118 | case 2: 119 | row = keyboard_keys_row_2; 120 | break; 121 | case 3: 122 | row = keyboard_keys_row_3; 123 | break; 124 | default: 125 | furi_crash(); 126 | } 127 | 128 | return row; 129 | } 130 | 131 | static char get_selected_char(TextInputModel* model) { 132 | return get_row(model->selected_row)[model->selected_column].text; 133 | } 134 | 135 | static bool char_is_lowercase(char letter) { 136 | return letter >= 0x61 && letter <= 0x7A; 137 | } 138 | 139 | static char char_to_uppercase(const char letter) { 140 | if(letter == '_') { 141 | return 0x20; 142 | } else if(islower(letter)) { 143 | return letter - 0x20; 144 | } else { 145 | return letter; 146 | } 147 | } 148 | 149 | static void text_input_backspace_cb(TextInputModel* model) { 150 | uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer); 151 | if(text_length > 0) { 152 | model->text_buffer[text_length - 1] = 0; 153 | } 154 | } 155 | 156 | static void text_input_view_draw_callback(Canvas* canvas, void* _model) { 157 | TextInputModel* model = _model; 158 | uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; 159 | uint8_t needed_string_width = canvas_width(canvas) - 8; 160 | uint8_t start_pos = 4; 161 | 162 | const char* text = model->text_buffer; 163 | 164 | canvas_clear(canvas); 165 | canvas_set_color(canvas, ColorBlack); 166 | 167 | canvas_draw_str(canvas, 2, 8, model->header); 168 | elements_slightly_rounded_frame(canvas, 1, 12, 126, 15); 169 | 170 | if(canvas_string_width(canvas, text) > needed_string_width) { 171 | canvas_draw_str(canvas, start_pos, 22, "..."); 172 | start_pos += 6; 173 | needed_string_width -= 8; 174 | } 175 | 176 | while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) { 177 | text++; 178 | } 179 | 180 | if(model->clear_default_text) { 181 | elements_slightly_rounded_box( 182 | canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10); 183 | canvas_set_color(canvas, ColorWhite); 184 | } else { 185 | canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|"); 186 | canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|"); 187 | } 188 | canvas_draw_str(canvas, start_pos, 22, text); 189 | 190 | canvas_set_font(canvas, FontKeyboard); 191 | 192 | for(uint8_t row = 0; row < keyboard_row_count; row++) { 193 | const uint8_t column_count = get_row_size(row); 194 | const TextInputKey* keys = get_row(row); 195 | 196 | for(size_t column = 0; column < column_count; column++) { 197 | if(keys[column].text == ENTER_KEY) { 198 | canvas_set_color(canvas, ColorBlack); 199 | if(model->selected_row == row && model->selected_column == column) { 200 | canvas_draw_icon( 201 | canvas, 202 | keyboard_origin_x + keys[column].x, 203 | keyboard_origin_y + keys[column].y, 204 | &I_KeySaveSelected_24x11); 205 | } else { 206 | canvas_draw_icon( 207 | canvas, 208 | keyboard_origin_x + keys[column].x, 209 | keyboard_origin_y + keys[column].y, 210 | &I_KeySave_24x11); 211 | } 212 | } else if(keys[column].text == BACKSPACE_KEY) { 213 | canvas_set_color(canvas, ColorBlack); 214 | if(model->selected_row == row && model->selected_column == column) { 215 | canvas_draw_icon( 216 | canvas, 217 | keyboard_origin_x + keys[column].x, 218 | keyboard_origin_y + keys[column].y, 219 | &I_KeyBackspaceSelected_16x9); 220 | } else { 221 | canvas_draw_icon( 222 | canvas, 223 | keyboard_origin_x + keys[column].x, 224 | keyboard_origin_y + keys[column].y, 225 | &I_KeyBackspace_16x9); 226 | } 227 | } else { 228 | if(model->selected_row == row && model->selected_column == column) { 229 | canvas_set_color(canvas, ColorBlack); 230 | canvas_draw_box( 231 | canvas, 232 | keyboard_origin_x + keys[column].x - 1, 233 | keyboard_origin_y + keys[column].y - 8, 234 | 7, 235 | 10); 236 | canvas_set_color(canvas, ColorWhite); 237 | } else { 238 | canvas_set_color(canvas, ColorBlack); 239 | } 240 | 241 | if(model->clear_default_text || 242 | (text_length == 0 && char_is_lowercase(keys[column].text))) { 243 | canvas_draw_glyph( 244 | canvas, 245 | keyboard_origin_x + keys[column].x, 246 | keyboard_origin_y + keys[column].y, 247 | char_to_uppercase(keys[column].text)); 248 | } else { 249 | canvas_draw_glyph( 250 | canvas, 251 | keyboard_origin_x + keys[column].x, 252 | keyboard_origin_y + keys[column].y, 253 | keys[column].text); 254 | } 255 | } 256 | } 257 | } 258 | if(model->validator_message_visible) { 259 | canvas_set_font(canvas, FontSecondary); 260 | canvas_set_color(canvas, ColorWhite); 261 | canvas_draw_box(canvas, 8, 10, 110, 48); 262 | canvas_set_color(canvas, ColorBlack); 263 | canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); 264 | canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); 265 | canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); 266 | elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text)); 267 | canvas_set_font(canvas, FontKeyboard); 268 | } 269 | } 270 | 271 | static void text_input_handle_up(TextInput* text_input, TextInputModel* model) { 272 | UNUSED(text_input); 273 | if(model->selected_row > 0) { 274 | model->selected_row--; 275 | if(model->selected_column > get_row_size(model->selected_row) - 6) { 276 | model->selected_column = model->selected_column + 1; 277 | } 278 | } 279 | } 280 | 281 | static void text_input_handle_down(TextInput* text_input, TextInputModel* model) { 282 | UNUSED(text_input); 283 | if(model->selected_row < keyboard_row_count - 1) { 284 | model->selected_row++; 285 | if(model->selected_column > get_row_size(model->selected_row) - 4) { 286 | model->selected_column = model->selected_column - 1; 287 | } 288 | } 289 | } 290 | 291 | static void text_input_handle_left(TextInput* text_input, TextInputModel* model) { 292 | UNUSED(text_input); 293 | if(model->selected_column > 0) { 294 | model->selected_column--; 295 | } else { 296 | model->selected_column = get_row_size(model->selected_row) - 1; 297 | } 298 | } 299 | 300 | static void text_input_handle_right(TextInput* text_input, TextInputModel* model) { 301 | UNUSED(text_input); 302 | if(model->selected_column < get_row_size(model->selected_row) - 1) { 303 | model->selected_column++; 304 | } else { 305 | model->selected_column = 0; 306 | } 307 | } 308 | 309 | static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, bool shift) { 310 | char selected = get_selected_char(model); 311 | size_t text_length = strlen(model->text_buffer); 312 | 313 | bool toggle_case = text_length == 0 || model->clear_default_text; 314 | if(shift) toggle_case = !toggle_case; 315 | if(toggle_case) { 316 | selected = char_to_uppercase(selected); 317 | } 318 | 319 | if(selected == ENTER_KEY) { 320 | if(model->validator_callback && 321 | (!model->validator_callback( 322 | model->text_buffer, model->validator_text, model->validator_callback_context))) { 323 | model->validator_message_visible = true; 324 | furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4); 325 | } else if(model->callback != 0 && text_length >= model->minimum_length) { 326 | model->callback(model->callback_context); 327 | } 328 | } else if(selected == BACKSPACE_KEY) { 329 | text_input_backspace_cb(model); 330 | } else { 331 | if(model->clear_default_text) { 332 | text_length = 0; 333 | } 334 | if(text_length < (model->text_buffer_size - 1)) { 335 | model->text_buffer[text_length] = selected; 336 | model->text_buffer[text_length + 1] = 0; 337 | } 338 | } 339 | model->clear_default_text = false; 340 | } 341 | 342 | static bool text_input_view_input_callback(InputEvent* event, void* context) { 343 | TextInput* text_input = context; 344 | furi_assert(text_input); 345 | 346 | bool consumed = false; 347 | 348 | // Acquire model 349 | TextInputModel* model = view_get_model(text_input->view); 350 | 351 | if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && 352 | model->validator_message_visible) { 353 | model->validator_message_visible = false; 354 | consumed = true; 355 | } else if(event->type == InputTypeShort) { 356 | consumed = true; 357 | switch(event->key) { 358 | case InputKeyUp: 359 | text_input_handle_up(text_input, model); 360 | break; 361 | case InputKeyDown: 362 | text_input_handle_down(text_input, model); 363 | break; 364 | case InputKeyLeft: 365 | text_input_handle_left(text_input, model); 366 | break; 367 | case InputKeyRight: 368 | text_input_handle_right(text_input, model); 369 | break; 370 | case InputKeyOk: 371 | text_input_handle_ok(text_input, model, false); 372 | break; 373 | default: 374 | consumed = false; 375 | break; 376 | } 377 | } else if(event->type == InputTypeLong) { 378 | consumed = true; 379 | switch(event->key) { 380 | case InputKeyUp: 381 | text_input_handle_up(text_input, model); 382 | break; 383 | case InputKeyDown: 384 | text_input_handle_down(text_input, model); 385 | break; 386 | case InputKeyLeft: 387 | text_input_handle_left(text_input, model); 388 | break; 389 | case InputKeyRight: 390 | text_input_handle_right(text_input, model); 391 | break; 392 | case InputKeyOk: 393 | text_input_handle_ok(text_input, model, true); 394 | break; 395 | case InputKeyBack: 396 | text_input_backspace_cb(model); 397 | break; 398 | default: 399 | consumed = false; 400 | break; 401 | } 402 | } else if(event->type == InputTypeRepeat) { 403 | consumed = true; 404 | switch(event->key) { 405 | case InputKeyUp: 406 | text_input_handle_up(text_input, model); 407 | break; 408 | case InputKeyDown: 409 | text_input_handle_down(text_input, model); 410 | break; 411 | case InputKeyLeft: 412 | text_input_handle_left(text_input, model); 413 | break; 414 | case InputKeyRight: 415 | text_input_handle_right(text_input, model); 416 | break; 417 | case InputKeyBack: 418 | text_input_backspace_cb(model); 419 | break; 420 | default: 421 | consumed = false; 422 | break; 423 | } 424 | } 425 | 426 | // Commit model 427 | view_commit_model(text_input->view, consumed); 428 | 429 | return consumed; 430 | } 431 | 432 | void text_input_timer_callback(void* context) { 433 | furi_assert(context); 434 | TextInput* text_input = context; 435 | 436 | with_view_model( 437 | text_input->view, 438 | TextInputModel * model, 439 | { model->validator_message_visible = false; }, 440 | true); 441 | } 442 | 443 | TextInput* text_input_alloc(void) { 444 | TextInput* text_input = malloc(sizeof(TextInput)); 445 | text_input->view = view_alloc(); 446 | view_set_context(text_input->view, text_input); 447 | view_allocate_model(text_input->view, ViewModelTypeLocking, sizeof(TextInputModel)); 448 | view_set_draw_callback(text_input->view, text_input_view_draw_callback); 449 | view_set_input_callback(text_input->view, text_input_view_input_callback); 450 | 451 | text_input->timer = furi_timer_alloc(text_input_timer_callback, FuriTimerTypeOnce, text_input); 452 | 453 | with_view_model( 454 | text_input->view, 455 | TextInputModel * model, 456 | { model->validator_text = furi_string_alloc(); }, 457 | false); 458 | 459 | text_input_reset(text_input); 460 | 461 | return text_input; 462 | } 463 | 464 | void text_input_free(TextInput* text_input) { 465 | furi_check(text_input); 466 | with_view_model( 467 | text_input->view, 468 | TextInputModel * model, 469 | { furi_string_free(model->validator_text); }, 470 | false); 471 | 472 | // Send stop command 473 | furi_timer_stop(text_input->timer); 474 | // Release allocated memory 475 | furi_timer_free(text_input->timer); 476 | 477 | view_free(text_input->view); 478 | 479 | free(text_input); 480 | } 481 | 482 | void text_input_reset(TextInput* text_input) { 483 | furi_check(text_input); 484 | with_view_model( 485 | text_input->view, 486 | TextInputModel * model, 487 | { 488 | model->header = ""; 489 | model->selected_row = 0; 490 | model->selected_column = 0; 491 | model->minimum_length = 1; 492 | model->clear_default_text = false; 493 | model->text_buffer = NULL; 494 | model->text_buffer_size = 0; 495 | model->callback = NULL; 496 | model->callback_context = NULL; 497 | model->validator_callback = NULL; 498 | model->validator_callback_context = NULL; 499 | furi_string_reset(model->validator_text); 500 | model->validator_message_visible = false; 501 | }, 502 | true); 503 | } 504 | 505 | View* text_input_get_view(TextInput* text_input) { 506 | furi_check(text_input); 507 | return text_input->view; 508 | } 509 | 510 | void text_input_set_result_callback( 511 | TextInput* text_input, 512 | TextInputCallback callback, 513 | void* callback_context, 514 | char* text_buffer, 515 | size_t text_buffer_size, 516 | bool clear_default_text) { 517 | furi_check(text_input); 518 | with_view_model( 519 | text_input->view, 520 | TextInputModel * model, 521 | { 522 | model->callback = callback; 523 | model->callback_context = callback_context; 524 | model->text_buffer = text_buffer; 525 | model->text_buffer_size = text_buffer_size; 526 | model->clear_default_text = clear_default_text; 527 | if(text_buffer && text_buffer[0] != '\0') { 528 | // Set focus on Save 529 | model->selected_row = 2; 530 | model->selected_column = 8; 531 | } 532 | }, 533 | true); 534 | } 535 | 536 | void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length) { 537 | with_view_model( 538 | text_input->view, 539 | TextInputModel * model, 540 | { model->minimum_length = minimum_length; }, 541 | true); 542 | } 543 | 544 | void text_input_set_validator( 545 | TextInput* text_input, 546 | TextInputValidatorCallback callback, 547 | void* callback_context) { 548 | furi_check(text_input); 549 | with_view_model( 550 | text_input->view, 551 | TextInputModel * model, 552 | { 553 | model->validator_callback = callback; 554 | model->validator_callback_context = callback_context; 555 | }, 556 | true); 557 | } 558 | 559 | TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input) { 560 | furi_check(text_input); 561 | TextInputValidatorCallback validator_callback = NULL; 562 | with_view_model( 563 | text_input->view, 564 | TextInputModel * model, 565 | { validator_callback = model->validator_callback; }, 566 | false); 567 | return validator_callback; 568 | } 569 | 570 | void* text_input_get_validator_callback_context(TextInput* text_input) { 571 | furi_check(text_input); 572 | void* validator_callback_context = NULL; 573 | with_view_model( 574 | text_input->view, 575 | TextInputModel * model, 576 | { validator_callback_context = model->validator_callback_context; }, 577 | false); 578 | return validator_callback_context; 579 | } 580 | 581 | void text_input_set_header_text(TextInput* text_input, const char* text) { 582 | furi_check(text_input); 583 | with_view_model(text_input->view, TextInputModel * model, { model->header = text; }, true); 584 | } 585 | -------------------------------------------------------------------------------- /applications_user/lora_app/lora.c: -------------------------------------------------------------------------------- 1 | /* 2 | Code porting from LoRa library https://github.dev/thekakester/Arduino-LoRa-Sx1262 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #define TAG "LORA" 9 | 10 | //Presets. These help make radio config easier 11 | #define PRESET_DEFAULT 0 12 | #define PRESET_LONGRANGE 1 13 | #define PRESET_FAST 2 14 | 15 | #define REG_LR_SYNCWORD 0x0740 16 | #define RADIO_READ_REGISTER 0x1D 17 | 18 | #define REG_RFFrequency31_24 0x088B 19 | #define REG_RFFrequency23_16 0x088C 20 | #define REG_RFFrequency15_8 0x088D 21 | #define REG_RFFrequency7_0 0x088E 22 | 23 | #define FREQ_STEP 0.95367431640625 24 | 25 | static uint32_t timeout = 1000; 26 | 27 | FuriHalSpiBusHandle spi_handle; 28 | const FuriHalSpiBusHandle* spi = &spi_handle; 29 | 30 | const GpioPin* const pin_beacon = &gpio_swclk; 31 | const GpioPin* const pin_nss0 = &gpio_ext_pa4; 32 | const GpioPin* const pin_nss1 = &gpio_ext_pc0; 33 | const GpioPin* const pin_reset = &gpio_ext_pc1; 34 | const GpioPin* const pin_ant_sw = &gpio_usart_tx; 35 | const GpioPin* const pin_busy = &gpio_usart_rx; 36 | const GpioPin* const pin_dio1 = &gpio_ext_pc3; 37 | 38 | bool inReceiveMode = false; 39 | uint8_t spiBuff[32]; //Buffer for sending SPI commands to radio 40 | 41 | //Config variables (set to PRESET_DEFAULT on init) 42 | uint32_t pllFrequency; 43 | uint8_t bandwidth; 44 | uint8_t codingRate; 45 | uint8_t spreadingFactor; 46 | uint16_t syncWord; 47 | uint8_t lowDataRateOptimize; 48 | uint32_t transmitTimeout; //Worst-case transmit time depends on some factors 49 | 50 | int rssi = 0; 51 | int snr = 0; 52 | int signalRssi = 0; 53 | 54 | // test 55 | void abandone() { 56 | FURI_LOG_E(TAG, "abandon hope all ye who enter here"); 57 | } 58 | 59 | int16_t getRSSI() { 60 | return rssi; 61 | } 62 | 63 | int8_t getSNR() { 64 | return snr; 65 | } 66 | 67 | void checkBusy() { 68 | uint8_t busy_timeout_cnt; 69 | busy_timeout_cnt = 0; 70 | 71 | furi_hal_gpio_init_simple(pin_busy, GpioModeInput); 72 | 73 | while(furi_hal_gpio_read(pin_busy)) { 74 | furi_delay_ms(1); 75 | busy_timeout_cnt++; 76 | 77 | if(busy_timeout_cnt > 10) //wait 10mS for busy to complete 78 | { 79 | busy_timeout_cnt = 0; 80 | FURI_LOG_E(TAG, "ERROR - Busy Timeout!"); 81 | break; 82 | } 83 | } 84 | } 85 | 86 | void readRegisters(uint16_t address, uint8_t* buffer, uint16_t size) { 87 | uint16_t index; 88 | uint8_t addr_l, addr_h; 89 | 90 | addr_h = address >> 8; 91 | addr_l = address & 0x00FF; 92 | checkBusy(); 93 | 94 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 95 | 96 | furi_hal_spi_acquire(spi); 97 | 98 | spiBuff[0] = RADIO_READ_REGISTER; 99 | spiBuff[1] = addr_h; 100 | spiBuff[2] = addr_l; 101 | spiBuff[3] = 0x00; 102 | 103 | furi_hal_spi_bus_tx(spi, spiBuff, 4, timeout); 104 | 105 | for(index = 0; index < size; index++) { 106 | furi_hal_spi_bus_rx(spi, buffer + index, 1, timeout); 107 | } 108 | 109 | furi_hal_spi_release(spi); 110 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 111 | } 112 | 113 | uint8_t readRegister(uint16_t address) { 114 | uint8_t data; 115 | 116 | readRegisters(address, &data, 1); 117 | return data; 118 | } 119 | 120 | uint32_t getFreqInt() { 121 | //get the current set device frequency from registers, return as long integer 122 | 123 | uint8_t MsbH, MsbL, Mid, Lsb; 124 | uint32_t uinttemp; 125 | float floattemp; 126 | MsbH = readRegister(REG_RFFrequency31_24); 127 | MsbL = readRegister(REG_RFFrequency23_16); 128 | Mid = readRegister(REG_RFFrequency15_8); 129 | Lsb = readRegister(REG_RFFrequency7_0); 130 | floattemp = ((MsbH * 0x1000000ul) + (MsbL * 0x10000ul) + (Mid * 0x100ul) + Lsb); 131 | floattemp = ((floattemp * FREQ_STEP) / 1000000ul); 132 | uinttemp = (uint32_t)(floattemp * 1000000); 133 | return uinttemp; 134 | } 135 | 136 | /*Convert a frequency in hz (such as 915000000) to the respective PLL setting. 137 | * The radio requires that we set the PLL, which controls the multipler on the internal clock to achieve the desired frequency. 138 | * Valid frequencies are 150MHz to 960MHz (150000000 to 960000000) 139 | * 140 | * NOTE: This assumes the radio is using a 32mhz clock, which is standard. This is independent of the microcontroller clock 141 | * See datasheet section 13.4.1 for this calculation. 142 | * Example: 915mhz (915000000) has a PLL of 959447040 143 | */ 144 | uint32_t frequencyToPLL(long rfFreq) { 145 | /* Datasheet Says: 146 | * rfFreq = (pllFreq * xtalFreq) / 2^25 147 | * Rewrite to solve for pllFreq 148 | * pllFreq = (2^25 * rfFreq)/xtalFreq 149 | * 150 | * In our case, xtalFreq is 32mhz 151 | * pllFreq = (2^25 * rfFreq) / 32000000 152 | */ 153 | //Basically, we need to do "return ((1 << 25) * rfFreq) / 32000000L" 154 | //It's very important to perform this without losing precision or integer overflow. 155 | //If arduino supported 64-bit varibales (which it doesn't), we could just do this: 156 | // uint64_t firstPart = (1 << 25) * (uint64_t)rfFreq; 157 | // return (uint32_t)(firstPart / 32000000L); 158 | // 159 | //Instead, we need to break this up mathimatically to avoid integer overflow 160 | //First, we'll simplify the equation by dividing both parts by 2048 (2^11) 161 | // ((1 << 25) * rfFreq) / 32000000L --> (16384 * rfFreq) / 15625; 162 | // 163 | // Now, we'll divide first, then multiply (multiplying first would cause integer overflow) 164 | // Because we're dividing, we need to keep track of the remainder to avoid losing precision 165 | uint32_t q = 166 | rfFreq / 15625UL; //Gives us the result (quotient), rounded down to the nearest integer 167 | uint32_t r = 168 | rfFreq % 169 | 15625UL; //Everything that isn't divisible, aka "the part that hasn't been divided yet" 170 | 171 | //Multiply by 16384 to satisfy the equation above 172 | q *= 16384UL; 173 | r *= 174 | 16384UL; //Don't forget, this part still needs to be divided because it was too small to divide before 175 | 176 | return q + 177 | (r / 178 | 15625UL); //Finally divide the the remainder part before adding it back in with the quotient 179 | } 180 | 181 | //Set the radio frequency. Just a single SPI call, 182 | //but this is broken out to make it more convenient to change frequency on-the-fly 183 | //You must set this->pllFrequency before calling this 184 | void updateRadioFrequency() { 185 | // Set PLL frequency (this is a complicated math equation. See datasheet entry for SetRfFrequency) 186 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 187 | furi_hal_spi_acquire(spi); 188 | 189 | spiBuff[0] = 0x86; //Opcode for set RF Frequencty 190 | spiBuff[1] = (pllFrequency >> 24) & 0xFF; //MSB of pll frequency 191 | spiBuff[2] = (pllFrequency >> 16) & 0xFF; // 192 | spiBuff[3] = (pllFrequency >> 8) & 0xFF; // 193 | spiBuff[4] = (pllFrequency >> 0) & 0xFF; //LSB of requency 194 | furi_hal_spi_bus_tx(spi, spiBuff, 5, timeout); 195 | 196 | furi_hal_spi_release(spi); 197 | 198 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 199 | furi_delay_ms(100); // Give time for the radio to process command 200 | } 201 | 202 | /** (Optional) Set the operating frequency of the radio. 203 | * The 1262 radio supports 150-960Mhz. This library uses a default of 915Mhz. 204 | * MAKE SURE THAT YOU ARE OPERATING IN A FREQUENCY THAT IS ALLOWED IN YOUR COUNTRY! 205 | * For example, 915mhz (915000000 hz) is safe in the US. 206 | * 207 | * Specify the desired frequency in Hz (eg 915MHZ is 915000000). 208 | * Returns TRUE on success, FALSE on invalid frequency 209 | */ 210 | bool configSetFrequency(long frequencyInHz) { 211 | //Make sure the specified frequency is in the valid range. 212 | if(frequencyInHz < 150000000 || frequencyInHz > 960000000) { 213 | return false; 214 | } 215 | 216 | //Calculate the PLL frequency (See datasheet section 13.4.1 for calculation) 217 | //PLL frequency controls the radio's clock multipler to achieve the desired frequency 218 | pllFrequency = frequencyToPLL(frequencyInHz); 219 | updateRadioFrequency(); 220 | return true; 221 | } 222 | 223 | // Set the radio modulation parameters. 224 | // This is things like bandwidth, spreading factor, coding rate, etc. 225 | // This is broken into its own function because this command might get called frequently 226 | void updateModulationParameters() { 227 | // Set modulation parameters 228 | // Modulation parameters are: 229 | // - SpreadingFactor 230 | // - Bandwidth 231 | // - CodingRate 232 | // - LowDataRateOptimize 233 | // None of these actually matter that much. You can set them to anything, and data will still show up 234 | // on a radio frequency monitor. 235 | // You just MUST call "setModulationParameters", otherwise the radio won't work at all 236 | 237 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 238 | 239 | spiBuff[0] = 0x8B; // Opcode for "SetModulationParameters" 240 | spiBuff[1] = 241 | spreadingFactor; // ModParam1 = Spreading Factor. Can be SF5-SF12, written in hex (0x05-0x0C) 242 | spiBuff[2] = 243 | bandwidth; // ModParam2 = Bandwidth. See Datasheet 13.4.5.2 for details. 0x00=7.81khz (slowest) 244 | spiBuff[3] = 245 | codingRate; // ModParam3 = CodingRate. Semtech recommends CR_4_5 (which is 0x01). Options are 0x01-0x04, which correspond to coding rate 5-8 respectively 246 | spiBuff[4] = 247 | lowDataRateOptimize; // LowDataRateOptimize. 0x00 = 0ff, 0x01 = On. Required to be on for SF11 + SF12 248 | 249 | furi_hal_spi_acquire(spi); 250 | 251 | if(furi_hal_spi_bus_tx( 252 | spi, spiBuff, 5, timeout)) { // Assuming 'timeout' is defined somewhere in the code 253 | furi_hal_spi_release(spi); 254 | } else { 255 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 256 | furi_hal_spi_release(spi); 257 | } 258 | 259 | //furi_hal_spi_release(spi); 260 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 261 | furi_delay_ms(100); // Give time for the radio to process command 262 | 263 | // Determine transmit timeout based on spreading factor 264 | // TODO: 265 | // TIMEOUT SET TO 1000, STILL CHECKING HOW TO FIX 266 | switch(spreadingFactor) { 267 | case 12: 268 | transmitTimeout = 1000; // 252000; // Actual tx time 126 seconds 269 | break; 270 | case 11: 271 | transmitTimeout = 1000; // 160000; // Actual tx time 81 seconds 272 | break; 273 | case 10: 274 | transmitTimeout = 1000; // 60000; // Actual tx time 36 seconds 275 | break; 276 | case 9: 277 | transmitTimeout = 1000; // 40000; // Actual tx time 20 seconds 278 | break; 279 | case 8: 280 | transmitTimeout = 1000; // 20000; // Actual tx time 11 seconds 281 | break; 282 | case 7: 283 | transmitTimeout = 1000; // 12000; // Actual tx time 6.3 seconds 284 | break; 285 | case 6: 286 | transmitTimeout = 1000; // 7000; // Actual tx time 3.7s seconds 287 | break; 288 | default: // SF5 289 | transmitTimeout = 1000; //5000; // Actual tx time 2.2 seconds 290 | break; 291 | } 292 | } 293 | 294 | /**(Optional) Use one of the pre-made radio configurations 295 | * This is ideal for making simple changes to the radio config 296 | * without needing to understand how the underlying settings work 297 | * 298 | * Argument: pass in one of the following 299 | * - PRESET_DEFAULT: Default radio config. 300 | * Medium range, medium speed 301 | * - PRESET_FAST: Faster speeds, but less reliable at long ranges. 302 | * Use when you need fast data transfer and have radios close together 303 | * - PRESET_LONGRANGE: Most reliable option, but slow. Suitable when you prioritize 304 | * reliability over speed, or when transmitting over long distances 305 | */ 306 | bool configSetPreset(int preset) { 307 | if(preset == PRESET_DEFAULT) { 308 | bandwidth = 0x04; //125khz 309 | codingRate = 0x01; //CR_4_5 310 | spreadingFactor = 0x08; //SF8 311 | lowDataRateOptimize = 0; //Don't optimize (used for SF12 only) 312 | updateModulationParameters(); 313 | return true; 314 | } 315 | 316 | if(preset == PRESET_LONGRANGE) { 317 | bandwidth = 4; //125khz 318 | codingRate = 1; //CR_4_5 319 | spreadingFactor = 12; //SF12 320 | lowDataRateOptimize = 1; //Optimize for low data rate (SF12 only) 321 | updateModulationParameters(); 322 | return true; 323 | } 324 | 325 | if(preset == PRESET_FAST) { 326 | bandwidth = 6; //500khz 327 | codingRate = 1; //CR_4_5 328 | spreadingFactor = 5; //SF5 329 | lowDataRateOptimize = 0; //Don't optimize (used for SF12 only) 330 | updateModulationParameters(); 331 | return true; 332 | } 333 | 334 | //Invalid preset specified 335 | return false; 336 | } 337 | 338 | /*Send the bare-bones required commands needed for radio to run. 339 | * Do not set custom or optional commands here, please keep this section as simplified as possible. 340 | * Essential commands are found by reading the datasheet 341 | */ 342 | void configureRadioEssentials() { 343 | // Tell DIO2 to control the RF switch so we don't have to do it manually 344 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 345 | 346 | furi_hal_spi_acquire(spi); 347 | 348 | spiBuff[0] = 0x9D; //Opcode for "SetDIO2AsRfSwitchCtrl" 349 | spiBuff[1] = 0x01; //Enable 350 | 351 | if(furi_hal_spi_bus_tx(spi, spiBuff, 2, timeout)) { 352 | furi_hal_spi_release(spi); 353 | } else { 354 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 355 | furi_hal_spi_release(spi); 356 | } 357 | 358 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 359 | furi_delay_ms(100); // Give time for the radio to process command 360 | 361 | // Just a single SPI command to set the frequency, but it's broken out 362 | // into its own function so we can call it on-the-fly when the config changes 363 | configSetFrequency(915000000); // Set default frequency to 915mhz 364 | 365 | // Set modem to LoRa (described in datasheet section 13.4.2) 366 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 367 | furi_hal_spi_acquire(spi); 368 | 369 | spiBuff[0] = 0x8A; // Opcode for "SetPacketType" 370 | spiBuff[1] = 0x01; // Packet Type: 0x00=GFSK, 0x01=LoRa 371 | 372 | if(furi_hal_spi_bus_tx(spi, spiBuff, 2, timeout)) { 373 | furi_hal_spi_release(spi); 374 | } else { 375 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 376 | furi_hal_spi_release(spi); 377 | } 378 | 379 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 380 | furi_delay_ms(100); // Give time for radio to process the command 381 | 382 | // Set Rx Timeout to reset on SyncWord or Header detection 383 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 384 | furi_hal_spi_acquire(spi); 385 | 386 | spiBuff[0] = 0x9F; // Opcode for "StopTimerOnPreamble" 387 | spiBuff[1] = 0x00; // Stop timer on: 0x00=SyncWord or header detection, 0x01=preamble detection 388 | 389 | if(furi_hal_spi_bus_tx(spi, spiBuff, 2, timeout)) { 390 | furi_hal_spi_release(spi); 391 | } else { 392 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 393 | furi_hal_spi_release(spi); 394 | } 395 | 396 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 397 | furi_delay_ms(100); // Give time for radio to process the command 398 | 399 | // Set modulation parameters is just one more SPI command, but since it 400 | // is often called frequently when changing the radio config, it's broken up into its own function 401 | configSetPreset(PRESET_DEFAULT); // Sets default modulation parameters 402 | 403 | // Set PA Config 404 | // See datasheet 13.1.4 for descriptions and optimal settings recommendations 405 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 406 | furi_hal_spi_acquire(spi); 407 | 408 | spiBuff[0] = 0x95; // Opcode for "SetPaConfig" 409 | spiBuff[1] = 0x04; // paDutyCycle. See datasheet, set in conjunction with hpMax 410 | spiBuff[2] = 0x07; // hpMax. Basically Tx power. 0x00-0x07 where 0x07 is max power 411 | spiBuff[3] = 0x00; // device select: 0x00 = SX1262, 0x01 = SX1261 412 | spiBuff[4] = 0x01; // paLut (reserved, always set to 1) 413 | 414 | if(furi_hal_spi_bus_tx(spi, spiBuff, 5, timeout)) { 415 | furi_hal_spi_release(spi); 416 | } else { 417 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 418 | furi_hal_spi_release(spi); 419 | } 420 | 421 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 422 | furi_delay_ms(100); // Give time for radio to process the command 423 | 424 | // Set TX Params 425 | // See datasheet 13.4.4 for details 426 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 427 | furi_hal_spi_acquire(spi); 428 | 429 | spiBuff[0] = 0x8E; // Opcode for SetTxParams 430 | spiBuff[1] = 431 | 22; // Power. Can be -17(0xEF) to +14x0E in Low Pow mode. -9(0xF7) to 22(0x16) in high power mode 432 | spiBuff[2] = 0x02; // Ramp time. Lookup table. See table 13-41. 0x02="40uS" 433 | 434 | if(furi_hal_spi_bus_tx(spi, spiBuff, 3, timeout)) { 435 | furi_hal_spi_release(spi); 436 | } else { 437 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 438 | furi_hal_spi_release(spi); 439 | } 440 | 441 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 442 | furi_delay_ms(100); // Give time for radio to process the command 443 | 444 | // Set LoRa Symbol Number timeout 445 | // How many symbols are needed for a good receive. 446 | // Symbols are preamble symbols 447 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 448 | furi_hal_spi_acquire(spi); 449 | 450 | spiBuff[0] = 0xA0; // Opcode for "SetLoRaSymbNumTimeout" 451 | spiBuff[1] = 0x00; // Number of symbols. Ping-pong example from Semtech uses 5 452 | 453 | if(furi_hal_spi_bus_tx(spi, spiBuff, 2, timeout)) { 454 | furi_hal_spi_release(spi); 455 | } else { 456 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 457 | furi_hal_spi_release(spi); 458 | } 459 | 460 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 461 | furi_delay_ms(100); // Give time for radio to process the command 462 | 463 | // Enable interrupts 464 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 465 | furi_hal_spi_acquire(spi); 466 | 467 | spiBuff[0] = 0x08; // 0x08 is the opcode for "SetDioIrqParams" 468 | spiBuff[1] = 0x00; // IRQMask MSB. IRQMask is "what interrupts are enabled" 469 | spiBuff[2] = 0x02; // IRQMask LSB See datasheet table 13-29 for details 470 | spiBuff[3] = 471 | 0xFF; // DIO1 mask MSB. Of the interrupts detected, which should be triggered on DIO1 pin 472 | spiBuff[4] = 0xFF; // DIO1 Mask LSB 473 | spiBuff[5] = 0x00; // DIO2 Mask MSB 474 | spiBuff[6] = 0x00; // DIO2 Mask LSB 475 | spiBuff[7] = 0x00; // DIO3 Mask MSB 476 | spiBuff[8] = 0x00; // DIO3 Mask LSB 477 | 478 | if(furi_hal_spi_bus_tx(spi, spiBuff, 9, timeout)) { 479 | furi_hal_spi_release(spi); 480 | } else { 481 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 482 | furi_hal_spi_release(spi); 483 | } 484 | 485 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 486 | furi_delay_ms(100); // Give time for radio to process the command 487 | } 488 | 489 | bool waitForRadioCommandCompletion(uint32_t timeout) { 490 | uint32_t startTime = furi_get_tick(); // Get the start time in ticks 491 | bool dataTransmitted = false; 492 | 493 | // Keep checking the radio status until the operation is completed 494 | while(!dataTransmitted) { 495 | // Wait a while between SPI status queries to avoid reading too quickly 496 | furi_delay_ms(5); 497 | 498 | // Request a status update from the radio 499 | furi_hal_gpio_write(pin_nss1, false); // Enable the radio chip-select 500 | furi_hal_spi_acquire(spi); 501 | 502 | spiBuff[0] = 0xC0; // Opcode for the "getStatus" command 503 | spiBuff[1] = 0x00; // Dummy byte, status will overwrite this byte 504 | 505 | furi_hal_spi_bus_tx(spi, spiBuff, 2, timeout); 506 | 507 | furi_hal_spi_release(spi); 508 | furi_hal_gpio_write(pin_nss1, true); // Disable the radio chip-select 509 | 510 | // Parse the status 511 | uint8_t chipMode = (spiBuff[1] >> 4) & 0x07; // Chip mode is bits [6:4] (3-bits) 512 | uint8_t commandStatus = (spiBuff[1] >> 1) & 0x07; // Command status is bits [3:1] (3-bits) 513 | 514 | // Check if the operation has finished 515 | //Status 0, 1, 2 mean we're still busy. Anything else means we're done. 516 | //Commands 3-6 = command timeout, command processing error, failure to execute command, and Tx Done (respoectively) 517 | if(commandStatus != 0 && commandStatus != 1 && commandStatus != 2) { 518 | dataTransmitted = true; 519 | FURI_LOG_E(TAG, "DATA TRANSMITTED"); 520 | } 521 | 522 | // If we are in standby mode, there's no need to wait anymore 523 | if(chipMode == 0x03 || chipMode == 0x02) { 524 | dataTransmitted = true; 525 | FURI_LOG_E(TAG, "DATA TRANSMITTED STANBY MODE"); 526 | } 527 | 528 | // Prevent infinite loop by implementing a timeout 529 | if((furi_get_tick() - startTime) >= furi_ms_to_ticks(timeout)) { 530 | return false; 531 | } 532 | } 533 | 534 | // Success! 535 | return true; 536 | } 537 | 538 | /* Set the bandwidth (basically, this is how big the frequency span is that we occupy) 539 | Bigger bandwidth allows us to transmit large amounts of data faster, but it occupies a larger span of frequencies. 540 | Smaller bandwidth takes longer to transmit large amounts of data, but its less likely to collide with other frequencies. 541 | 542 | Available bandwidth settings, pulled from datasheet 13.4.5.2 543 | SETTING. | Bandwidth 544 | ------------+----------- 545 | 0x00 | 7.81khz 546 | 0x08 | 10.42khz 547 | 0x01 | 15.63khz 548 | 0x09 | 20.83khz 549 | 0x02 | 31.25khz 550 | 0x0A | 41.67khz 551 | 0x03 | 62.50khz 552 | 0x04 | 125.00khz 553 | 0x05 | 250.00khz (default) 554 | 0x06 | 500.00khz 555 | */ 556 | bool configSetBandwidth(int bw) { 557 | if(bw < 0 || bw > 0x0A || bw == 7) { 558 | return false; 559 | } 560 | bandwidth = bw; 561 | updateModulationParameters(); 562 | return true; 563 | } 564 | 565 | /* Set the coding rate*/ 566 | bool configSetCodingRate(int cr) { 567 | // Coding rate must be 1-4 (inclusive) 568 | if(cr < 1 || cr > 4) { 569 | return false; 570 | } 571 | codingRate = cr; 572 | updateModulationParameters(); 573 | return true; 574 | } 575 | 576 | bool configSetSyncWord(uint8_t syncWord, uint8_t controlBits) { 577 | uint8_t msb = (syncWord & 0xF0) | ((controlBits & 0xF0) >> 4); 578 | uint8_t lsb = ((syncWord & 0x0F) << 4) | (controlBits & 0x0F); 579 | 580 | // Write both bytes in a single SPI transaction 581 | furi_hal_gpio_write(pin_nss1, false); // CS low 582 | furi_hal_spi_acquire(spi); 583 | 584 | spiBuff[0] = 0x0D; // WriteRegister opcode 585 | spiBuff[1] = 0x07; // Address high byte (0x0740) 586 | spiBuff[2] = 0x40; // Address low byte 587 | spiBuff[3] = msb; // MSB data 588 | spiBuff[4] = lsb; // LSB data 589 | 590 | furi_hal_spi_bus_tx(spi, spiBuff, 5, timeout); 591 | 592 | furi_hal_spi_release(spi); 593 | furi_hal_gpio_write(pin_nss1, true); // CS high 594 | 595 | furi_delay_ms(1); 596 | 597 | return true; 598 | } 599 | 600 | /* Change the spreading factor of a packet 601 | The higher the spreading factor, the slower and more reliable the transmission will be. */ 602 | bool configSetSpreadingFactor(int sf) { 603 | if(sf < 5 || sf > 12) { 604 | return false; 605 | } 606 | FURI_LOG_E(TAG, "SF %d", sf); 607 | FURI_LOG_E(TAG, "BW %d", bandwidth); 608 | lowDataRateOptimize = (sf == 12 && bandwidth == 4) ? 609 | 1 : 610 | 0; // Turn on for SF12 125 kHz, turn off for anything else 611 | FURI_LOG_E(TAG, "LOD %d", lowDataRateOptimize); 612 | spreadingFactor = sf; 613 | updateModulationParameters(); 614 | return true; 615 | } 616 | 617 | void setPacketParams( 618 | uint16_t packetParam1, 619 | uint8_t packetParam2, 620 | uint8_t packetParam3, 621 | uint8_t packetParam4, 622 | uint8_t packetParam5) { 623 | // Order is preamble, header type, packet length, CRC, IQ 624 | 625 | uint8_t preambleMSB = packetParam1 >> 8; 626 | uint8_t preambleLSB = packetParam1 & 0xFF; 627 | 628 | spiBuff[0] = 0x8C; //Opcode for "SetPacketParameters" 629 | spiBuff[1] = preambleMSB; //Preamble Len MSB 630 | spiBuff[2] = preambleLSB; //Preamble Len LSB 631 | spiBuff[3] = packetParam2; //Header Type. 0x00 = Variable Len, 0x01 = Fixed Length 632 | spiBuff[4] = packetParam3; //Payload Length (Max is 255 bytes) 633 | spiBuff[5] = packetParam4; //0x00 = Off, 0x01 = on 634 | spiBuff[6] = packetParam5; //0x00 = Standard, 0x01 = Inverted 635 | 636 | // Acquire SPI and write command 637 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 638 | furi_hal_spi_acquire(spi); 639 | 640 | if(furi_hal_spi_bus_tx(spi, spiBuff, 7, timeout)) { 641 | furi_hal_spi_release(spi); 642 | FURI_LOG_I( 643 | "LoRaSPI", 644 | "PacketParams written OK -> Preamble: %u, Header: %u, Payload: %u, CRC: %u, IQ: %u", 645 | packetParam1, 646 | packetParam2, 647 | packetParam3, 648 | packetParam4, 649 | packetParam5); 650 | } else { 651 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 652 | furi_hal_spi_release(spi); 653 | } 654 | 655 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 656 | waitForRadioCommandCompletion(100); 657 | } 658 | 659 | //Sets the radio into receive mode, allowing it to listen for incoming packets. 660 | //If radio is already in receive mode, this does nothing. 661 | //There's no such thing as "setModeTransmit" because it is set automatically when transmit() is called 662 | void setModeReceive() { 663 | if(inReceiveMode) { 664 | return; 665 | } // We're already in receive mode, this would do nothing 666 | 667 | // Set packet parameters 668 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 669 | furi_hal_spi_acquire(spi); 670 | 671 | spiBuff[0] = 0x8C; //Opcode for "SetPacketParameters" 672 | spiBuff[1] = 0x00; //PacketParam1 = Preamble Len MSB 673 | spiBuff[2] = 0x10; //PacketParam2 = Preamble Len LSB - 16 674 | spiBuff[3] = 0x00; //PacketParam3 = Header Type. 0x00 = Variable Len, 0x01 = Fixed Length 675 | spiBuff[4] = 0xFF; //PacketParam4 = Payload Length (Max is 255 bytes) 676 | spiBuff[5] = 0x00; //PacketParam5 = CRC Type. 0x00 = Off, 0x01 = on 677 | spiBuff[6] = 0x00; //PacketParam6 = Invert IQ. 0x00 = Standard, 0x01 = Inverted 678 | 679 | if(furi_hal_spi_bus_tx(spi, spiBuff, 7, timeout)) { 680 | furi_hal_spi_release(spi); 681 | } else { 682 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 683 | furi_hal_spi_release(spi); 684 | } 685 | 686 | //furi_hal_spi_release(spi); 687 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 688 | 689 | waitForRadioCommandCompletion(100); 690 | 691 | // Tell the chip to wait for it to receive a packet. 692 | // Based on our previous config, this should throw an interrupt when we get a packet 693 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 694 | furi_hal_spi_acquire(spi); 695 | 696 | spiBuff[0] = 0x82; //0x82 is the opcode for "SetRX" 697 | spiBuff[1] = 0xFF; //24-bit timeout, 0xFFFFFF means no timeout 698 | spiBuff[2] = 0xFF; // ^^ 699 | spiBuff[3] = 0xFF; // ^^ 700 | 701 | if(furi_hal_spi_bus_tx(spi, spiBuff, 4, timeout)) { 702 | furi_hal_spi_release(spi); 703 | } else { 704 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 705 | furi_hal_spi_release(spi); 706 | } 707 | 708 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 709 | 710 | waitForRadioCommandCompletion(100); 711 | 712 | // Remember that we're in receive mode so we don't need to run this code again unnecessarily 713 | inReceiveMode = true; 714 | } 715 | 716 | /* Set radio into standby mode. 717 | Switching directly from Rx to Tx mode can be slow, so we first want to go into standby */ 718 | void setModeStandby() { 719 | // Tell the chip to wait for it to receive a packet. 720 | // Based on our previous config, this should throw an interrupt when we get a packet 721 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 722 | 723 | spiBuff[0] = 0x80; //0x80 is the opcode for "SetStandby" 724 | spiBuff[1] = 0x01; //0x00 = STDBY_RC, 0x01=STDBY_XOSC 725 | 726 | furi_hal_spi_acquire(spi); 727 | furi_hal_spi_bus_tx(spi, spiBuff, 2, timeout); 728 | furi_hal_spi_release(spi); 729 | 730 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 731 | waitForRadioCommandCompletion(100); 732 | inReceiveMode = false; // No longer in receive mode 733 | } 734 | 735 | void transmit(uint8_t* data, int dataLen) { 736 | // Max lora packet size is 255 bytes 737 | if(dataLen > 255) { 738 | dataLen = 255; 739 | } 740 | 741 | // Switching directly from rx to tx mode is slow. Go to standby first 742 | if(inReceiveMode) { 743 | setModeStandby(); 744 | } 745 | 746 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 747 | 748 | spiBuff[0] = 0x8C; // Opcode for "SetPacketParameters" 749 | spiBuff[1] = 0x00; // PacketParam1 = Preamble Len MSB 750 | spiBuff[2] = 0x10; // PacketParam2 = Preamble Len LSB 751 | spiBuff[3] = 0x00; // PacketParam3 = Header Type. 0x00 = Variable Len, 0x01 = Fixed Length 752 | spiBuff[4] = dataLen; // PacketParam4 = Payload Length (Max is 255 bytes) 753 | spiBuff[5] = 0x00; // PacketParam5 = CRC Type. 0x00 = Off, 0x01 = on 754 | spiBuff[6] = 0x00; // PacketParam6 = Invert IQ. 0x00 = Standard, 0x01 = Inverted 755 | 756 | furi_hal_spi_acquire(spi); 757 | furi_hal_spi_bus_tx(spi, spiBuff, 7, timeout); 758 | furi_hal_spi_release(spi); 759 | 760 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 761 | waitForRadioCommandCompletion(100); // Give time for radio to process the command 762 | 763 | // Write the payload to the buffer 764 | // Reminder: PayloadLength is defined in setPacketParams 765 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 766 | 767 | spiBuff[0] = 0x0E, //Opcode for WriteBuffer command 768 | spiBuff[1] = 0x00; //Dummy byte before writing payload 769 | 770 | furi_hal_spi_acquire(spi); 771 | furi_hal_spi_bus_tx(spi, spiBuff, 2, timeout); 772 | 773 | // Transmit data in chunks to avoid overwriting the original buffer 774 | uint8_t size = sizeof(spiBuff); 775 | for(uint16_t i = 0; i < dataLen; i += size) { 776 | if(i + size > dataLen) { 777 | size = dataLen - i; 778 | } 779 | memcpy(spiBuff, &(data[i]), size); 780 | furi_hal_spi_bus_tx(spi, data + i, size, timeout); // Write the payload itself 781 | } 782 | 783 | furi_hal_spi_release(spi); 784 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 785 | waitForRadioCommandCompletion(1000); // Give time for radio to process the command 786 | 787 | // Transmit 788 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 789 | 790 | spiBuff[0] = 0x83; // Opcode for SetTx command 791 | spiBuff[1] = 0xFF; // Timeout (3-byte number) 792 | spiBuff[2] = 0xFF; // Timeout (3-byte number) 793 | spiBuff[3] = 0xFF; // Timeout (3-byte number) 794 | 795 | furi_hal_spi_acquire(spi); 796 | furi_hal_spi_bus_tx(spi, spiBuff, 4, timeout); 797 | furi_hal_spi_release(spi); 798 | 799 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 800 | 801 | waitForRadioCommandCompletion( 802 | transmitTimeout); // Wait for tx to complete, with a timeout so we don't wait forever 803 | 804 | // Remember that we are in Tx mode. If we want to receive a packet, we need to switch into receiving mode 805 | inReceiveMode = false; 806 | } 807 | 808 | /*Receive a packet if available 809 | If available, this will return the size of the packet and store the packet contents into the user-provided buffer. 810 | A max length of the buffer can be provided to avoid buffer overflow. If buffer is not large enough for entire payload, overflow is thrown out. 811 | Recommended to pass in a buffer that is 255 bytes long to make sure you can received any lora packet that comes in. 812 | 813 | Returns -1 when no packet is available. 814 | Returns 0 when an empty packet is received (packet with no payload) 815 | Returns payload size (1-255) when a packet with a non-zero payload is received. If packet received is larger than the buffer provided, this will return buffMaxLen 816 | */ 817 | int lora_receive_async(uint8_t* buff, int buffMaxLen) { 818 | setModeReceive(); // Sets the mode to receive (if not already in receive mode) 819 | 820 | if(furi_hal_gpio_read(pin_dio1)) { 821 | furi_hal_gpio_write(pin_beacon, true); 822 | furi_delay_ms(50); 823 | furi_hal_gpio_write(pin_beacon, false); 824 | } 825 | 826 | // Radio pin DIO1 (interrupt) goes high when we have a packet ready. If it's low, there's no packet yet 827 | if(!furi_hal_gpio_read(pin_dio1)) { 828 | return -1; 829 | } // Return -1, meaning no packet ready 830 | 831 | FURI_LOG_E(TAG, "packet ready... "); 832 | 833 | // Tell the radio to clear the interrupt, and set the pin back inactive. 834 | while(furi_hal_gpio_read(pin_dio1)) { 835 | // Clear all interrupt flags. This should result in the interrupt pin going low 836 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 837 | furi_hal_spi_acquire(spi); 838 | 839 | spiBuff[0] = 0x02; //Opcode for ClearIRQStatus command 840 | spiBuff[1] = 0xFF; //IRQ bits to clear (MSB) (0xFFFF means clear all interrupts) 841 | spiBuff[2] = 0xFF; //IRQ bits to clear (LSB) 842 | 843 | furi_hal_spi_bus_tx(spi, spiBuff, 3, timeout); 844 | 845 | furi_hal_spi_release(spi); 846 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 847 | } 848 | 849 | // (Optional) Read the packet status info from the radio. 850 | // This provides debug info about the packet we received 851 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 852 | furi_hal_spi_acquire(spi); 853 | 854 | spiBuff[0] = 0x14; //Opcode for get packet status 855 | spiBuff[1] = 0xFF; //Dummy byte. Returns status 856 | spiBuff[2] = 0xFF; //Dummy byte. Returns rssi 857 | spiBuff[3] = 0xFF; //Dummy byte. Returns snd 858 | spiBuff[4] = 0xFF; //Dummy byte. Returns signal RSSI 859 | 860 | furi_hal_spi_bus_rx(spi, spiBuff, 5, timeout); 861 | 862 | furi_hal_spi_release(spi); 863 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 864 | 865 | // Store these values as class variables so they can be accessed if needed 866 | // Documentation for what these variables mean can be found in the .h file 867 | rssi = 868 | -((int)spiBuff[2]) / 869 | 2; // "Average over last packet received of RSSI. Actual signal power is –RssiPkt/2 (dBm)" 870 | snr = ((int8_t)spiBuff[3]) / 871 | 4; // SNR is returned as a SIGNED byte, so we need to do some conversion first 872 | signalRssi = -((int)spiBuff[4]) / 2; 873 | 874 | // We're almost ready to read the packet from the radio 875 | // But first we have to know how big the packet is, and where in the radio memory it is stored 876 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 877 | furi_hal_spi_acquire(spi); 878 | 879 | spiBuff[0] = 0x13; //Opcode for GetRxBufferStatus command 880 | spiBuff[1] = 0xFF; //Dummy. Returns radio status 881 | spiBuff[2] = 0xFF; //Dummy. Returns loraPacketLength 882 | spiBuff[3] = 0xFF; //Dummy. Returns memory offset (address) 883 | 884 | furi_hal_spi_bus_rx(spi, spiBuff, 4, timeout); 885 | 886 | furi_hal_spi_release(spi); 887 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 888 | 889 | uint8_t payloadLen = spiBuff[2]; // How long the lora packet is 890 | 891 | FURI_LOG_E(TAG, "payloadLen = %d", payloadLen); 892 | 893 | uint8_t startAddress = spiBuff[3]; // Where in 1262 memory is the packet stored 894 | 895 | // Make sure we don't overflow the buffer if the packet is larger than our buffer 896 | if(buffMaxLen < payloadLen) { 897 | payloadLen = buffMaxLen; 898 | } 899 | 900 | // Read the radio buffer from the SX1262 into the user-supplied buffer 901 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 902 | furi_hal_spi_acquire(spi); 903 | 904 | spiBuff[0] = 0x1E; // Opcode for ReadBuffer command 905 | spiBuff[1] = startAddress; // SX1262 memory location to start reading from 906 | spiBuff[2] = 0x00; // Dummy byte 907 | furi_hal_spi_bus_tx(spi, spiBuff, 3, timeout); // Send commands to get read started 908 | furi_hal_spi_bus_rx( 909 | spi, 910 | buff, 911 | payloadLen, 912 | timeout); // Get the contents from the radio and store it into the user provided buffer 913 | 914 | furi_hal_spi_release(spi); 915 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 916 | 917 | return payloadLen; // Return how many bytes we actually read 918 | } 919 | 920 | void regTest() { 921 | uint8_t regValue; 922 | checkBusy(); 923 | 924 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 925 | 926 | furi_hal_spi_acquire(spi); 927 | 928 | spiBuff[0] = 0x1D; 929 | spiBuff[1] = 0x07; 930 | spiBuff[2] = 0x40; 931 | spiBuff[3] = 0x00; 932 | 933 | furi_hal_spi_bus_tx(spi, spiBuff, 4, timeout); 934 | furi_hal_spi_bus_rx(spi, ®Value, 1, timeout); 935 | 936 | furi_hal_spi_release(spi); 937 | 938 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 939 | } 940 | 941 | /* Tests that SPI is communicating correctly with the radio. 942 | * If this fails, check your SPI wiring. This does not require any setup to run. 943 | * We test the radio by reading a register that should have a known value. 944 | * 945 | * Returns: True if radio is communicating over SPI. False if no connection. 946 | */ 947 | bool sanityCheck() { 948 | uint8_t command_read_register[1] = {0x1D}; // OpCode for "read register" 949 | uint8_t read_register_address[2] = {0x07, 0x40}; 950 | uint8_t dummy_byte = 0x00; 951 | uint8_t regValue; 952 | 953 | furi_hal_gpio_write(pin_nss1, false); // Enable radio chip-select 954 | furi_hal_spi_acquire(spi); 955 | 956 | if(furi_hal_spi_bus_tx(spi, command_read_register, 1, timeout) && 957 | furi_hal_spi_bus_tx(spi, read_register_address, 2, timeout) && 958 | furi_hal_spi_bus_tx(spi, &dummy_byte, 1, timeout) && 959 | furi_hal_spi_bus_rx(spi, ®Value, 1, timeout)) { 960 | FURI_LOG_E(TAG, "REGISTER VALUE: %02x", regValue); 961 | furi_hal_spi_release(spi); 962 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 963 | 964 | if(regValue == 0x14) { 965 | // Initialize the LED pin as output. 966 | // GpioModeOutputPushPull means true = 3.3 volts, false = 0 volts. 967 | // GpioModeOutputOpenDrain means true = floating, false = 0 volts. 968 | furi_hal_gpio_init_simple(pin_beacon, GpioModeOutputPushPull); 969 | furi_hal_gpio_write(pin_beacon, true); 970 | furi_delay_ms(100); 971 | furi_hal_gpio_write(pin_beacon, false); 972 | furi_delay_ms(100); 973 | furi_hal_gpio_write(pin_beacon, true); 974 | furi_delay_ms(100); 975 | furi_hal_gpio_write(pin_beacon, false); 976 | furi_delay_ms(100); 977 | } 978 | 979 | return regValue == 0x14; // Success if we read 0x14 from the register 980 | } else { 981 | FURI_LOG_E(TAG, "FAILED - furi_hal_spi_bus_tx or furi_hal_spi_bus_rx failed."); 982 | furi_hal_spi_release(spi); 983 | furi_hal_gpio_write(pin_nss1, true); // Disable radio chip-select 984 | return false; 985 | } 986 | } 987 | 988 | void printRegisters(uint16_t Start, uint16_t End) { 989 | //prints the contents of SX126x registers to serial monitor 990 | 991 | uint16_t Loopv1, Loopv2, RegData; 992 | 993 | FURI_LOG_E(TAG, "Reg 0 1 2 3 4 5 6 7 8 9 A B C D E F"); 994 | 995 | for(Loopv1 = Start; Loopv1 <= End;) //32 lines 996 | { 997 | FURI_LOG_E(TAG, "0x%02x ", Loopv1); 998 | 999 | for(Loopv2 = 0; Loopv2 <= 15; Loopv2++) { 1000 | RegData = readRegister(Loopv1); 1001 | if(RegData < 0x10) { 1002 | //FURI_LOG_E(TAG,"0"); 1003 | } 1004 | 1005 | FURI_LOG_E(TAG, "0x%02x ", RegData); 1006 | 1007 | Loopv1++; 1008 | } 1009 | FURI_LOG_E(TAG, "\n"); 1010 | } 1011 | } 1012 | 1013 | void init_spi() { 1014 | spi_handle.bus = furi_hal_spi_bus_handle_external.bus; 1015 | spi_handle.callback = furi_hal_spi_bus_handle_external.callback; 1016 | spi_handle.cs = pin_nss1; 1017 | spi_handle.miso = furi_hal_spi_bus_handle_external.miso; 1018 | spi_handle.mosi = furi_hal_spi_bus_handle_external.mosi; 1019 | spi_handle.sck = furi_hal_spi_bus_handle_external.sck; 1020 | } 1021 | 1022 | bool begin() { 1023 | //furi_hal_gpio_init(pin_reset, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); 1024 | //furi_hal_gpio_init(pin_nss1, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); 1025 | 1026 | init_spi(); 1027 | 1028 | furi_hal_gpio_init_simple(pin_reset, GpioModeOutputPushPull); 1029 | furi_hal_gpio_init_simple(pin_nss0, GpioModeOutputPushPull); 1030 | furi_hal_gpio_init_simple(pin_nss1, GpioModeOutputPushPull); 1031 | 1032 | furi_hal_gpio_init_simple(pin_beacon, GpioModeOutputPushPull); 1033 | 1034 | furi_hal_gpio_write(pin_nss0, false); 1035 | furi_hal_gpio_write(pin_nss1, true); 1036 | furi_hal_gpio_write(pin_reset, true); 1037 | 1038 | furi_hal_gpio_init_simple(pin_dio1, GpioModeInput); 1039 | 1040 | FURI_LOG_E(TAG, "RESET DEVICE..."); 1041 | furi_delay_ms(10); 1042 | furi_hal_gpio_write(pin_reset, false); 1043 | furi_delay_ms(2); 1044 | furi_hal_gpio_write(pin_reset, true); 1045 | furi_delay_ms(25); 1046 | 1047 | checkBusy(); 1048 | 1049 | //Ensure SPI communication is working with the radio 1050 | FURI_LOG_E(TAG, "SANITYCHECK..."); 1051 | bool success = sanityCheck(); 1052 | if(!success) { 1053 | return false; 1054 | } 1055 | 1056 | //Run the bare-minimum required SPI commands to set up the radio to use 1057 | configureRadioEssentials(); 1058 | 1059 | uint32_t lora_freq = getFreqInt(); 1060 | 1061 | FURI_LOG_E(TAG, " FREQUENCY: %ld", lora_freq); 1062 | 1063 | return true; //Return success that we set up the radio 1064 | } 1065 | -------------------------------------------------------------------------------- /applications_user/lora_app/lora_relay.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include "lora_app_icons.h" 22 | 23 | #define PATHAPP "apps_data/lora" 24 | #define PATHAPPEXT EXT_PATH(PATHAPP) 25 | #define PATHLORA PATHAPPEXT "/data_%d.log" 26 | #define LORA_LOG_FILE_EXTENSION ".log" 27 | 28 | #define MAX_LINE_LENGTH 256 29 | 30 | #define CDC_PORT_NUM 1 // Port CDC 1 (second port USB) 31 | 32 | #define TIME_LEN 12 33 | #define DATE_LEN 14 34 | 35 | #define CLOCK_TIME_FORMAT "%.2d:%.2d:%.2d" 36 | #define CLOCK_ISO_DATE_FORMAT "%.4d-%.2d-%.2d" 37 | 38 | const GpioPin* nss_1 = &gpio_ext_pc0; 39 | const GpioPin* reset_sx = &gpio_ext_pc1; 40 | const GpioPin* const pin_led = &gpio_swclk; 41 | const GpioPin* const pin_back = &gpio_button_back; 42 | 43 | #define TAG "LoRa" 44 | 45 | void abandone(); 46 | int16_t getRSSI(); 47 | int8_t getSNR(); 48 | void configureRadioEssentials(); 49 | bool begin(); 50 | bool sanityCheck(); 51 | void checkBusy(); 52 | void setModeReceive(); 53 | int lora_receive_async(uint8_t* buff, int buffMaxLen); 54 | bool configSetFrequency(long frequencyInHz); 55 | bool configSetBandwidth(int bw); 56 | bool configSetSpreadingFactor(int sf); 57 | bool configSetCodingRate(int cr); 58 | bool configSetSyncWord(uint8_t syncWord, uint8_t controlBits); 59 | void setPacketParams( 60 | uint16_t packetParam1, 61 | uint8_t packetParam2, 62 | uint8_t packetParam3, 63 | uint8_t packetParam4, 64 | uint8_t packetParam5); 65 | 66 | void transmit(uint8_t* data, int dataLen); 67 | 68 | // Change this to BACKLIGHT_AUTO if you don't want the backlight to be continuously on. 69 | #define BACKLIGHT_ON 1 70 | 71 | // Our application menu has 6 items. You can add more items if you want. 72 | typedef enum { 73 | LoRaSubmenuIndexConfigure, 74 | LoRaSubmenuIndexLoRaWAN, 75 | LoRaSubmenuIndexMeshtastic, 76 | LoRaSubmenuIndexSniffer, 77 | LoRaSubmenuIndexTransmitter, 78 | LoRaSubmenuIndexManualTX, 79 | LoRaSubmenuIndexLinkerSubGHZ, 80 | LoRaSubmenuIndexAbout, 81 | } LoRaSubmenuIndex; 82 | 83 | // Each view is a screen we show the user. 84 | typedef enum { 85 | LoRaViewSubmenu, // The menu when the app starts 86 | LoRaViewFrequencyInput, // Input for configuring frequency settings 87 | LoRaViewByteInput, // Input for send data (bytes) 88 | LoRaViewConfigure, // The configuration screen 89 | LoRaViewLoRaWAN, // The presets LoRaWAN screen 90 | LoRaViewMeshtastic, // The presets Meshtastic screen 91 | LoRaViewSniffer, // Sniffer 92 | LoraViewTransmitter, // Transmitter 93 | LoRaViewAbout, // The about screen with directions, link to social channel, etc. 94 | } LoRaView; 95 | 96 | typedef enum { 97 | LoRaEventIdRedrawScreen = 0, // Custom event to redraw the screen 98 | LoRaEventIdOkPressed = 42, // Custom event to process OK button getting pressed down 99 | } LoRaEventId; 100 | 101 | typedef struct { 102 | ViewDispatcher* view_dispatcher; // Switches between our views 103 | NotificationApp* notifications; // Used for controlling the backlight 104 | Submenu* submenu; // The application menu 105 | TextInput* frequency_input; // The text input screen 106 | ByteInput* byte_input; // The byte input screen 107 | 108 | VariableItemList* variable_item_list_config; // The configuration screen 109 | VariableItemList* variable_item_list_lorawan; // The lorawan presets screen 110 | VariableItemList* variable_item_list_meshtastic; // The meshtastic presets screen 111 | 112 | VariableItem* item_bw; 113 | VariableItem* item_sf; 114 | VariableItem* item_cr; 115 | VariableItem* item_sw; 116 | VariableItem* item_pl; 117 | VariableItem* item_header_type; 118 | VariableItem* item_crc; 119 | VariableItem* item_iq; 120 | 121 | VariableItem* item_meshtastic; 122 | 123 | VariableItem* item_region; 124 | VariableItem* item_eu_dr; 125 | VariableItem* item_eu868_ul_channels_125k; 126 | VariableItem* item_eu868_ul_channels_250k; 127 | VariableItem* item_eu868_dl_channels_rx1; 128 | VariableItem* item_us_dr; 129 | VariableItem* item_us915_ul_channels_125k; 130 | VariableItem* item_us915_ul_channels_500k; 131 | VariableItem* item_us915_dl_channels_500k; 132 | 133 | View* view_sniffer; // The sniffer screen 134 | View* view_transmitter; // The transmitter screen 135 | Widget* widget_about; // The about screen 136 | 137 | VariableItem* config_freq_item; // The frequency setting item (so we can update the frequency) 138 | char* temp_buffer; // Temporary buffer for text input 139 | uint32_t temp_buffer_size; // Size of temporary buffer 140 | 141 | uint8_t* byte_buffer; // Temporary buffer for text input 142 | uint32_t byte_buffer_size; // Size of temporary buffer 143 | 144 | FuriTimer* timer_rx; // Timer for redrawing the sniffer screen 145 | FuriTimer* timer_tx; // Timer for redrawing the transmitter screen 146 | 147 | uint32_t config_frequency; 148 | 149 | // Order is preamble, header type, packet length, CRC, IQ 150 | uint16_t packetPreamble; 151 | uint8_t packetHeaderType; 152 | uint8_t packetPayloadLength; 153 | uint8_t packetCRC; 154 | uint8_t packetInvertIQ; 155 | 156 | } LoRaApp; 157 | 158 | typedef struct { 159 | FuriString* config_freq_name; // The frequency setting 160 | uint32_t config_bw_index; // Bandwidth setting index 161 | uint32_t config_sf_index; // Spread Factor setting index 162 | uint32_t config_cr_index; // Coding Rate setting index 163 | uint32_t config_sw_index; // Coding Rate setting index 164 | uint32_t config_pl_index; // Preamble Length setting index 165 | 166 | uint32_t config_header_type_index; // Header Type setting index 167 | uint32_t config_crc_index; // CRC setting index 168 | uint32_t config_iq_index; // IQ setting index 169 | 170 | uint32_t config_meshtastic_index; // meshtastic setting index 171 | 172 | uint32_t config_region_index; // Frequency plan setting index 173 | uint32_t config_bw_region_index; // BW region setting index 174 | uint32_t config_us_dr_index; // US915 Data Rate setting index 175 | uint32_t config_eu_dr_index; // EU868 Data Rate setting index 176 | 177 | uint32_t config_us915_ul_channels_125k_index; 178 | uint32_t config_us915_ul_channels_500k_index; 179 | uint32_t config_eu868_ul_channels_125k_index; 180 | 181 | uint32_t config_eu868_ul_channels_250k_index; 182 | uint32_t config_us915_dl_channels_500k_index; 183 | uint32_t config_eu868_dl_channels_rx1_index; 184 | 185 | uint8_t x; // The x coordinate (dummy variable) 186 | 187 | bool flag_file; 188 | DialogsApp* dialogs_rx; 189 | Storage* storage_rx; 190 | File* file_rx; 191 | } LoRaSnifferModel; 192 | 193 | typedef struct { 194 | uint32_t test; 195 | bool flag_tx_file; 196 | bool flag_signal; 197 | FuriString* text; 198 | DialogsApp* dialogs_tx; 199 | Storage* storage_tx; 200 | File* file_tx; 201 | uint8_t x; // The x coordinate 202 | } LoRaTransmitterModel; 203 | 204 | void makePaths(void* context) { 205 | LoRaApp* app = (LoRaApp*)context; 206 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 207 | furi_assert(app); 208 | if(!storage_simply_mkdir(model->storage_rx, PATHAPPEXT)) { 209 | dialog_message_show_storage_error(model->dialogs_rx, "Cannot create\napp folder"); 210 | } 211 | } 212 | 213 | /** 214 | * @brief Callback for exiting the application. 215 | * @details This function is called when user press back button. We return VIEW_NONE to 216 | * indicate that we want to exit the application. 217 | * @param _context The context - unused 218 | * @return next view id 219 | */ 220 | static uint32_t lora_navigation_exit_callback(void* _context) { 221 | UNUSED(_context); 222 | return VIEW_NONE; 223 | } 224 | 225 | /** 226 | * @brief Callback for returning to submenu. 227 | * @details This function is called when user press back button. We return VIEW_NONE to 228 | * indicate that we want to navigate to the submenu. 229 | * @param _context The context - unused 230 | * @return next view id 231 | */ 232 | static uint32_t lora_navigation_submenu_callback(void* _context) { 233 | UNUSED(_context); 234 | return LoRaViewSubmenu; 235 | } 236 | 237 | /** 238 | * @brief Callback for returning to configure screen. 239 | * @details This function is called when user press back button. We return VIEW_NONE to 240 | * indicate that we want to navigate to the configure screen. 241 | * @param _context The context - unused 242 | * @return next view id 243 | */ 244 | static uint32_t lora_navigation_configure_callback(void* _context) { 245 | UNUSED(_context); 246 | return LoRaViewConfigure; 247 | } 248 | 249 | /** 250 | * @brief Callback for returning to LoRaWAN screen. 251 | * @details This function is called when user press back button. We return VIEW_NONE to 252 | * indicate that we want to navigate to the LoRaWAN screen. 253 | * @param _context The context - unused 254 | * @return next view id 255 | */ 256 | 257 | // +++++++++++++++ TODO +++++++++++++++ 258 | // not used at the moment 259 | 260 | // static uint32_t lora_navigation_lorawan_callback(void* _context) { 261 | // UNUSED(_context); 262 | // return LoRaViewLoRaWAN; 263 | // } 264 | 265 | /** 266 | * @brief Handle submenu item selection. 267 | * @details This function is called when user selects an item from the submenu. 268 | * @param context The context - LoRaApp object. 269 | * @param index The LoRaSubmenuIndex item that was clicked. 270 | */ 271 | static void lora_submenu_callback(void* context, uint32_t index) { 272 | LoRaApp* app = (LoRaApp*)context; 273 | switch(index) { 274 | case LoRaSubmenuIndexConfigure: 275 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewConfigure); 276 | break; 277 | case LoRaSubmenuIndexLoRaWAN: 278 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewLoRaWAN); 279 | break; 280 | case LoRaSubmenuIndexMeshtastic: 281 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewMeshtastic); 282 | break; 283 | case LoRaSubmenuIndexSniffer: 284 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewSniffer); 285 | break; 286 | case LoRaSubmenuIndexTransmitter: 287 | view_dispatcher_switch_to_view(app->view_dispatcher, LoraViewTransmitter); 288 | break; 289 | case LoRaSubmenuIndexManualTX: 290 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewByteInput); 291 | break; 292 | case LoRaSubmenuIndexLinkerSubGHZ: 293 | furi_hal_gpio_init_simple(nss_1, GpioModeOutputPushPull); 294 | furi_hal_gpio_init_simple(reset_sx, GpioModeOutputPushPull); 295 | furi_hal_gpio_init_simple(pin_led, GpioModeOutputPushPull); 296 | 297 | furi_hal_gpio_write(nss_1, false); 298 | furi_hal_gpio_write(reset_sx, false); 299 | 300 | furi_hal_gpio_write(pin_led, true); 301 | furi_delay_ms(100); 302 | furi_hal_gpio_write(pin_led, false); 303 | furi_delay_ms(100); 304 | furi_hal_gpio_write(pin_led, true); 305 | furi_delay_ms(100); 306 | furi_hal_gpio_write(pin_led, false); 307 | 308 | view_dispatcher_stop(app->view_dispatcher); 309 | 310 | Loader* loader = furi_record_open(RECORD_LOADER); 311 | 312 | loader_enqueue_launch(loader, "Sub-GHz", NULL, LoaderDeferredLaunchFlagGui); 313 | 314 | furi_record_close(RECORD_LOADER); 315 | break; 316 | case LoRaSubmenuIndexAbout: 317 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewAbout); 318 | break; 319 | default: 320 | break; 321 | } 322 | } 323 | 324 | // Meshtastic modem presets configuration 325 | const uint8_t config_meshtastic_values[] = { 326 | 1, // SHORT_TURBO 327 | 2, // SHORT_FAST 328 | 3, // SHORT_SLOW 329 | 4, // MEDIUM_FAST 330 | 5, // MEDIUM_SLOW 331 | 6, // LONG_FAST 332 | 7, // LONG_MODERATE 333 | 8, // LONG_SLOW 334 | }; 335 | 336 | const char* const config_meshtastic_names[] = { 337 | "SHORT_TURBO", 338 | "SHORT_FAST", 339 | "SHORT_SLOW", 340 | "MEDIUM_FAST", 341 | "MEDIUM_SLOW", 342 | "LONG_FAST", 343 | "LONG_MODERATE", 344 | "LONG_SLOW", 345 | }; 346 | 347 | // Bandwidth configuration 348 | const uint8_t config_bw_values[] = { 349 | 0x00, 350 | 0x08, 351 | 0x01, 352 | 0x09, 353 | 0x02, 354 | 0x0A, 355 | 0x03, 356 | 0x04, 357 | 0x05, 358 | 0x06, 359 | }; 360 | const char* const config_bw_names[] = { 361 | "7.81 kHz", 362 | "10.42 kHz", 363 | "15.63 kHz", 364 | "20.83 kHz", 365 | "31.25 kHz", 366 | "41.67 kHz", 367 | "62.50 kHz", 368 | "125 kHz", 369 | "250 kHz", 370 | "500 kHz", 371 | }; 372 | 373 | // Spreading Factor configuration 374 | const uint8_t config_sf_values[] = { 375 | 0x05, 376 | 0x06, 377 | 0x07, 378 | 0x08, 379 | 0x09, 380 | 0x0A, 381 | 0x0B, 382 | 0x0C, 383 | }; 384 | const char* const config_sf_names[] = { 385 | "SF5", 386 | "SF6", 387 | "SF7", 388 | "SF8", 389 | "SF9", 390 | "SF10", 391 | "SF11", 392 | "SF12", 393 | }; 394 | 395 | // Coding Rate configuration 396 | const uint8_t config_cr_values[] = { 397 | 0x01, // 4/5 398 | 0x02, // 4/6 399 | 0x03, // 4/7 400 | 0x04, // 4/8 401 | }; 402 | 403 | const char* const config_cr_names[] = { 404 | "4/5", 405 | "4/6", 406 | "4/7", 407 | "4/8", 408 | }; 409 | 410 | // Sync Word options 411 | const uint8_t config_sw_values[] = { 412 | 0x12, // Private network 413 | 0x34, // Public network (LoRaWAN/TTN) 414 | 0x2B, // Meshtastic 415 | }; 416 | 417 | // Human-readable names 418 | const char* const config_sw_names[] = { 419 | "Private (0x12)", 420 | "Public (0x34)", 421 | "Meshtastic (0x2B)", 422 | }; 423 | 424 | // Preamble length configuration 425 | const uint8_t config_pl_values[] = { 426 | 0x08, // 8 427 | 0x10, // 16 428 | }; 429 | 430 | const char* const config_pl_names[] = { 431 | "8", 432 | "16", 433 | }; 434 | 435 | const uint8_t config_region_values[] = { 436 | 0x01, 437 | 0x02 438 | //, 439 | // 0x03, 440 | // 0x04, 441 | // 0x05, 442 | // 0x06, 443 | // 0x07, 444 | // 0x08, 445 | // 0x09, 446 | // 0x0A, 447 | // 0x0B, 448 | // 0x0C, 449 | // 0x0D 450 | }; 451 | 452 | // Regional names 453 | const char* const config_region_names[] = { 454 | "EU868", 455 | "US915" 456 | //, 457 | // "CN779", 458 | // "EU433", 459 | // "AU915", 460 | // "CN470", 461 | // "AS923", 462 | // "AS923-2", 463 | // "AS923-3", 464 | // "KR920", 465 | // "IN865", 466 | // "RU864", 467 | // "AS923-4" 468 | }; 469 | 470 | // Data Rate configuration for US915 471 | const uint8_t config_us_dr_values[] = { 472 | 0x00, // DR0 473 | 0x01, // DR1 474 | 0x02, // DR2 475 | 0x03, // DR3 476 | 0x04, // DR4 477 | 0x08, // DR8 478 | 0x09, // DR9 479 | 0x0A, // DR10 480 | 0x0B, // DR11 481 | 0x0C, // DR12 482 | 0x0D // DR13 483 | }; 484 | const char* const config_us_dr_names[] = { 485 | "SF10/125kHz", 486 | "SF9/125kHz", 487 | "SF8/125kHz", 488 | "SF7/125kHz", 489 | "SF8/500kHz", 490 | "SF12/500kHz", 491 | "SF11/500kHz", 492 | "SF10/500kHz", 493 | "SF9/500kHz", 494 | "SF8/500kHz", 495 | "SF7/500kHz"}; 496 | 497 | // Data Rate configuration for EU868 498 | const uint8_t config_eu_dr_values[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; 499 | 500 | const char* const config_eu_dr_names[] = { 501 | "SF12/125kHz", 502 | "SF11/125kHz", 503 | "SF10/125kHz", 504 | "SF9/125kHz", 505 | "SF8/125kHz", 506 | "SF7/125kHz", 507 | "SF7/250kHz"}; 508 | 509 | // Transmit Power configuration for US915 510 | const uint8_t config_txpower_values[] = { 511 | 0x00, // 30 dBm 512 | 0x01, // 28 dBm 513 | 0x02, // 26 dBm 514 | 0x03, // 24 dBm 515 | 0x04, // 22 dBm 516 | 0x05, // 20 dBm 517 | 0x06, // 18 dBm 518 | 0x07, // 16 dBm 519 | 0x08, // 14 dBm 520 | 0x09, // 12 dBm 521 | 0x0A // 10 dBm 522 | }; 523 | const char* const config_txpower_names[] = { 524 | "30 dBm", 525 | "28 dBm", 526 | "26 dBm", 527 | "24 dBm", 528 | "22 dBm", 529 | "20 dBm", 530 | "18 dBm", 531 | "16 dBm", 532 | "14 dBm", 533 | "12 dBm", 534 | "10 dBm"}; 535 | 536 | // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 537 | 538 | // Uplink channel frequencies for US915 (125 kHz channels) 539 | const uint32_t config_us915_ul_channels_125k[] = { 540 | 902300000, 902500000, 902700000, 902900000, 903100000, 903300000, 903500000, 903700000, 541 | 903900000, 904100000, 904300000, 904500000, 904700000, 904900000, 905100000, 905300000, 542 | 905500000, 905700000, 905900000, 906100000, 906300000, 906500000, 906700000, 906900000, 543 | 907100000, 907300000, 907500000, 907700000, 907900000, 908100000, 908300000, 908500000, 544 | 908700000, 908900000, 909100000, 909300000, 909500000, 909700000, 909900000, 910100000, 545 | 910300000, 910500000, 910700000, 910900000, 911100000, 911300000, 911500000, 911700000, 546 | 911900000, 912100000, 912300000, 912500000, 912700000, 912900000, 913100000, 913300000, 547 | 913500000, 913700000, 913900000, 914100000, 914300000, 914500000, 914700000, 914900000}; 548 | 549 | // Uplink channel frequencies for US915 (500 kHz channels) 550 | const uint32_t config_us915_ul_channels_500k[] = 551 | {903000000, 904600000, 906200000, 907800000, 909400000, 911000000, 912600000, 914200000}; 552 | 553 | // Downlink channel frequencies for US915 554 | const uint32_t config_us915_dl_channels_500k[] = 555 | {923300000, 923900000, 924500000, 925100000, 925700000, 926300000, 926900000, 927500000}; 556 | 557 | // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 558 | 559 | // Uplink channel frequencies for EU868 (125 kHz default channels) 560 | const uint32_t config_eu868_ul_channels_125k[] = {868100000, 868300000, 868500000}; 561 | 562 | // Uplink channel frequencies for EU868 (250 kHz channel) 563 | const uint32_t config_eu868_ul_channels_250k[] = {868300000}; 564 | 565 | // Additional uplink channel frequencies for EU868 (may be used depending on local regulations) 566 | const uint32_t config_eu868_ul_channels_additional[] = 567 | {867100000, 867300000, 867500000, 867700000, 867900000}; 568 | 569 | // Downlink channel frequencies for EU868 (RX1 - same as uplink) 570 | const uint32_t config_eu868_dl_channels_rx1[] = {868100000, 868300000, 868500000}; 571 | 572 | // Downlink channel frequency for EU868 (RX2 - fixed frequency) 573 | const uint32_t config_eu868_dl_channel_rx2 = 869525000; 574 | 575 | // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 576 | 577 | // Uplink channel frequencies for AS923 (125 kHz default channels) 578 | const uint32_t config_as923_ul_channels_125k[] = {923200000, 923400000}; 579 | 580 | // Additional uplink channel frequencies for AS923 (may be used depending on local regulations) 581 | const uint32_t config_as923_ul_channels_additional[] = 582 | {923600000, 923800000, 924000000, 924200000, 924400000, 924600000}; 583 | 584 | // Downlink channel frequencies for AS923 (RX1 - same as uplink) 585 | const uint32_t config_as923_dl_channels_rx1[] = {923200000, 923400000}; 586 | 587 | // Downlink channel frequency for AS923 (RX2 - fixed frequency) 588 | const uint32_t config_as923_dl_channel_rx2 = 923200000; 589 | 590 | // Frequency offsets for different AS923 sub-bands 591 | const int32_t config_as923_frequency_offsets[] = { 592 | 0, // AS923-1 593 | -1800000, // AS923-2 594 | -6600000, // AS923-3 595 | -5900000 // AS923-4 596 | }; 597 | 598 | // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 599 | 600 | //Header Type. 0x00 = Variable Len, 0x01 = Fixed Length 601 | const uint8_t config_header_type_values[] = { 602 | 0x00, 603 | 0x01, 604 | }; 605 | const char* const config_header_type_names[] = { 606 | "Variable Len", 607 | "Fixed Length", 608 | }; 609 | 610 | //CRC. 0x00 = Off, 0x01 = On 611 | const uint8_t config_crc_values[] = { 612 | 0x00, 613 | 0x01, 614 | }; 615 | const char* const config_crc_names[] = { 616 | "Off", 617 | "On", 618 | }; 619 | 620 | //IQ. 0x00 = Standard, 0x01 = Inverted 621 | const uint8_t config_iq_values[] = { 622 | 0x00, 623 | 0x01, 624 | }; 625 | const char* const config_iq_names[] = { 626 | "Standard", 627 | "Inverted", 628 | }; 629 | 630 | static const char* config_bw_label = "Bandwidth"; 631 | 632 | static void lora_config_bw_change(VariableItem* item) { 633 | LoRaApp* app = variable_item_get_context(item); 634 | uint8_t index = variable_item_get_current_value_index(item); 635 | variable_item_set_current_value_text(item, config_bw_names[index]); 636 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 637 | model->config_bw_index = index; 638 | 639 | configSetBandwidth(config_bw_values[index]); 640 | } 641 | 642 | static const char* config_sf_label = "Spread Factor"; 643 | 644 | static void lora_config_sf_change(VariableItem* item) { 645 | LoRaApp* app = variable_item_get_context(item); 646 | uint8_t index = variable_item_get_current_value_index(item); 647 | variable_item_set_current_value_text(item, config_sf_names[index]); 648 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 649 | model->config_sf_index = index; 650 | 651 | configSetSpreadingFactor(config_sf_values[index]); 652 | } 653 | 654 | static const char* config_cr_label = "Coding Rate"; 655 | 656 | static void lora_config_cr_change(VariableItem* item) { 657 | LoRaApp* app = variable_item_get_context(item); 658 | uint8_t index = variable_item_get_current_value_index(item); 659 | variable_item_set_current_value_text(item, config_cr_names[index]); 660 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 661 | model->config_cr_index = index; 662 | 663 | configSetCodingRate(config_cr_values[index]); 664 | } 665 | 666 | static const char* config_pl_label = "Preamble Length"; 667 | 668 | static void lora_config_pl_change(VariableItem* item) { 669 | LoRaApp* app = variable_item_get_context(item); 670 | uint8_t index = variable_item_get_current_value_index(item); 671 | variable_item_set_current_value_text(item, config_pl_names[index]); 672 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 673 | model->config_pl_index = index; 674 | 675 | app->packetPreamble = config_pl_values[index]; 676 | 677 | // Order is preamble, header type, packet length, CRC, IQ 678 | setPacketParams( 679 | app->packetPreamble, 680 | app->packetHeaderType, 681 | app->packetPayloadLength, 682 | app->packetCRC, 683 | app->packetInvertIQ); 684 | // Log PacketParams for debugging 685 | FURI_LOG_I( 686 | "LoRaConfig", 687 | "PacketParams -> Preamble: %u, Header: %u, PayloadLen: %u, CRC: %u, InvertIQ: %u", 688 | app->packetPreamble, 689 | app->packetHeaderType, 690 | app->packetPayloadLength, 691 | app->packetCRC, 692 | app->packetInvertIQ); 693 | } 694 | 695 | static const char* config_sw_label = "Sync Word"; 696 | 697 | static void lora_config_sw_change(VariableItem* item) { 698 | LoRaApp* app = variable_item_get_context(item); 699 | uint8_t index = variable_item_get_current_value_index(item); 700 | variable_item_set_current_value_text(item, config_sw_names[index]); 701 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 702 | model->config_sw_index = index; 703 | 704 | configSetSyncWord(config_sw_values[index], 0x44); 705 | } 706 | 707 | static const char* config_header_type_label = "Header Type"; 708 | 709 | static void lora_config_header_type_change(VariableItem* item) { 710 | LoRaApp* app = variable_item_get_context(item); 711 | uint8_t index = variable_item_get_current_value_index(item); 712 | variable_item_set_current_value_text(item, config_header_type_names[index]); 713 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 714 | model->config_header_type_index = index; 715 | 716 | app->packetHeaderType = config_header_type_values[index]; 717 | 718 | // Order is preamble, header type, packet length, CRC, IQ 719 | setPacketParams( 720 | app->packetPreamble, 721 | app->packetHeaderType, 722 | app->packetPayloadLength, 723 | app->packetCRC, 724 | app->packetInvertIQ); 725 | } 726 | 727 | static const char* config_crc_label = "CRC"; 728 | 729 | static void lora_config_crc_change(VariableItem* item) { 730 | LoRaApp* app = variable_item_get_context(item); 731 | uint8_t index = variable_item_get_current_value_index(item); 732 | variable_item_set_current_value_text(item, config_crc_names[index]); 733 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 734 | model->config_crc_index = index; 735 | 736 | app->packetCRC = config_crc_values[index]; 737 | 738 | // Order is preamble, header type, packet length, CRC, IQ 739 | setPacketParams( 740 | app->packetPreamble, 741 | app->packetHeaderType, 742 | app->packetPayloadLength, 743 | app->packetCRC, 744 | app->packetInvertIQ); 745 | } 746 | 747 | static const char* config_iq_label = "IQ"; 748 | 749 | static void lora_config_iq_change(VariableItem* item) { 750 | LoRaApp* app = variable_item_get_context(item); 751 | uint8_t index = variable_item_get_current_value_index(item); 752 | 753 | variable_item_set_current_value_text(item, config_iq_names[index]); 754 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 755 | model->config_iq_index = index; 756 | 757 | app->packetInvertIQ = config_iq_values[index]; 758 | 759 | // Order is preamble, header type, packet length, CRC, IQ 760 | setPacketParams( 761 | app->packetPreamble, 762 | app->packetHeaderType, 763 | app->packetPayloadLength, 764 | app->packetCRC, 765 | app->packetInvertIQ); 766 | } 767 | 768 | static const char* config_eu_dr_label = "EU868 DR"; 769 | 770 | static void lora_config_eu_dr_change(VariableItem* item) { 771 | LoRaApp* app = variable_item_get_context(item); 772 | uint8_t index = variable_item_get_current_value_index(item); 773 | variable_item_set_current_value_text(item, config_eu_dr_names[index]); 774 | 775 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 776 | model->config_eu_dr_index = index; 777 | 778 | switch(index) { 779 | case 0: // SF12/125kHz 780 | configSetSpreadingFactor(0xC); 781 | configSetBandwidth(0x04); 782 | 783 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 784 | variable_item_set_current_value_index(app->item_bw, 7); 785 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 786 | model->config_bw_index = 7; 787 | 788 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 789 | variable_item_set_current_value_index(app->item_sf, 7); 790 | variable_item_set_current_value_text(app->item_sf, config_sf_names[7]); 791 | model->config_sf_index = 7; 792 | 793 | break; 794 | case 1: // SF11/125kHz 795 | configSetSpreadingFactor(0x0B); 796 | configSetBandwidth(0x04); 797 | 798 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 799 | variable_item_set_current_value_index(app->item_bw, 7); 800 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 801 | model->config_bw_index = 7; 802 | 803 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 804 | variable_item_set_current_value_index(app->item_sf, 6); 805 | variable_item_set_current_value_text(app->item_sf, config_sf_names[6]); 806 | model->config_sf_index = 6; 807 | 808 | break; 809 | case 2: // SF10/125kHz 810 | configSetSpreadingFactor(0x0A); 811 | configSetBandwidth(0x04); 812 | 813 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 814 | variable_item_set_current_value_index(app->item_bw, 7); 815 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 816 | model->config_bw_index = 7; 817 | 818 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 819 | variable_item_set_current_value_index(app->item_sf, 5); 820 | variable_item_set_current_value_text(app->item_sf, config_sf_names[5]); 821 | model->config_sf_index = 5; 822 | 823 | break; 824 | case 3: // SF9/125kHz 825 | configSetSpreadingFactor(0x09); 826 | configSetBandwidth(0x04); 827 | 828 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 829 | variable_item_set_current_value_index(app->item_bw, 7); 830 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 831 | model->config_bw_index = 7; 832 | 833 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 834 | variable_item_set_current_value_index(app->item_sf, 4); 835 | variable_item_set_current_value_text(app->item_sf, config_sf_names[4]); 836 | model->config_sf_index = 4; 837 | 838 | break; 839 | case 4: // SF8/125kHz 840 | configSetSpreadingFactor(0x08); 841 | configSetBandwidth(0x04); 842 | 843 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 844 | variable_item_set_current_value_index(app->item_bw, 7); 845 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 846 | model->config_bw_index = 7; 847 | 848 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 849 | variable_item_set_current_value_index(app->item_sf, 3); 850 | variable_item_set_current_value_text(app->item_sf, config_sf_names[3]); 851 | model->config_sf_index = 3; 852 | 853 | break; 854 | case 5: // SF7/125kHz 855 | configSetSpreadingFactor(0x07); 856 | configSetBandwidth(0x04); 857 | 858 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 859 | variable_item_set_current_value_index(app->item_bw, 7); 860 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 861 | model->config_bw_index = 7; 862 | 863 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 864 | variable_item_set_current_value_index(app->item_sf, 2); 865 | variable_item_set_current_value_text(app->item_sf, config_sf_names[2]); 866 | model->config_sf_index = 2; 867 | 868 | break; 869 | case 6: // SF7/250kHz 870 | configSetSpreadingFactor(0x07); 871 | configSetBandwidth(0x05); 872 | 873 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 874 | variable_item_set_current_value_index(app->item_bw, 8); 875 | variable_item_set_current_value_text(app->item_bw, config_bw_names[8]); 876 | model->config_bw_index = 9; 877 | 878 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 879 | variable_item_set_current_value_index(app->item_sf, 1); 880 | variable_item_set_current_value_text(app->item_sf, config_sf_names[1]); 881 | model->config_sf_index = 1; 882 | 883 | break; 884 | } 885 | } 886 | 887 | static const char* config_us_dr_label = "US915 DR"; 888 | 889 | static void lora_config_us_dr_change(VariableItem* item) { 890 | LoRaApp* app = variable_item_get_context(item); 891 | uint8_t index = variable_item_get_current_value_index(item); 892 | variable_item_set_current_value_text(item, config_us_dr_names[index]); 893 | 894 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 895 | model->config_us_dr_index = index; 896 | 897 | switch(index) { 898 | case 0: // SF10/125kHz 899 | configSetSpreadingFactor(0x0A); 900 | configSetBandwidth(0x04); 901 | 902 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 903 | variable_item_set_current_value_index(app->item_bw, 7); 904 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 905 | model->config_bw_index = 7; 906 | 907 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 908 | variable_item_set_current_value_index(app->item_sf, 5); 909 | variable_item_set_current_value_text(app->item_sf, config_sf_names[5]); 910 | model->config_sf_index = 5; 911 | 912 | break; 913 | case 1: // SF9/125kHz 914 | configSetSpreadingFactor(0x09); 915 | configSetBandwidth(0x04); 916 | 917 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 918 | variable_item_set_current_value_index(app->item_bw, 7); 919 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 920 | model->config_bw_index = 7; 921 | 922 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 923 | variable_item_set_current_value_index(app->item_sf, 4); 924 | variable_item_set_current_value_text(app->item_sf, config_sf_names[4]); 925 | model->config_sf_index = 4; 926 | 927 | break; 928 | case 2: // SF8/125kHz 929 | configSetSpreadingFactor(0x08); 930 | configSetBandwidth(0x04); 931 | 932 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 933 | variable_item_set_current_value_index(app->item_bw, 7); 934 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 935 | model->config_bw_index = 7; 936 | 937 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 938 | variable_item_set_current_value_index(app->item_sf, 3); 939 | variable_item_set_current_value_text(app->item_sf, config_sf_names[3]); 940 | model->config_sf_index = 3; 941 | 942 | break; 943 | case 3: // SF7/125kHz 944 | configSetSpreadingFactor(0x07); 945 | configSetBandwidth(0x04); 946 | 947 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 948 | variable_item_set_current_value_index(app->item_bw, 7); 949 | variable_item_set_current_value_text(app->item_bw, config_bw_names[7]); 950 | model->config_bw_index = 7; 951 | 952 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 953 | variable_item_set_current_value_index(app->item_sf, 2); 954 | variable_item_set_current_value_text(app->item_sf, config_sf_names[2]); 955 | model->config_sf_index = 2; 956 | 957 | break; 958 | case 4: // SF8/500kHz 959 | configSetSpreadingFactor(0x08); 960 | configSetBandwidth(0x06); 961 | 962 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 963 | variable_item_set_current_value_index(app->item_bw, 9); 964 | variable_item_set_current_value_text(app->item_bw, config_bw_names[9]); 965 | model->config_bw_index = 9; 966 | 967 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 968 | variable_item_set_current_value_index(app->item_sf, 3); 969 | variable_item_set_current_value_text(app->item_sf, config_sf_names[3]); 970 | model->config_sf_index = 3; 971 | 972 | break; 973 | case 5: // SF12/500kHz 974 | configSetSpreadingFactor(0x0C); 975 | configSetBandwidth(0x06); 976 | 977 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 978 | variable_item_set_current_value_index(app->item_bw, 9); 979 | variable_item_set_current_value_text(app->item_bw, config_bw_names[9]); 980 | model->config_bw_index = 9; 981 | 982 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 983 | variable_item_set_current_value_index(app->item_sf, 7); 984 | variable_item_set_current_value_text(app->item_sf, config_sf_names[7]); 985 | model->config_sf_index = 7; 986 | 987 | break; 988 | case 6: // SF11/500kHz 989 | configSetSpreadingFactor(0x0B); 990 | configSetBandwidth(0x06); 991 | 992 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 993 | variable_item_set_current_value_index(app->item_bw, 9); 994 | variable_item_set_current_value_text(app->item_bw, config_bw_names[9]); 995 | model->config_bw_index = 9; 996 | 997 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 998 | variable_item_set_current_value_index(app->item_sf, 6); 999 | variable_item_set_current_value_text(app->item_sf, config_sf_names[6]); 1000 | model->config_sf_index = 6; 1001 | 1002 | break; 1003 | case 7: // SF10/500kHz 1004 | configSetSpreadingFactor(0x0A); 1005 | configSetBandwidth(0x06); 1006 | 1007 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 1008 | variable_item_set_current_value_index(app->item_bw, 9); 1009 | variable_item_set_current_value_text(app->item_bw, config_bw_names[9]); 1010 | model->config_bw_index = 9; 1011 | 1012 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 1013 | variable_item_set_current_value_index(app->item_sf, 5); 1014 | variable_item_set_current_value_text(app->item_sf, config_sf_names[5]); 1015 | model->config_sf_index = 5; 1016 | 1017 | break; 1018 | case 8: // SF9/500kHz 1019 | configSetSpreadingFactor(0x09); 1020 | configSetBandwidth(0x06); 1021 | 1022 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 1023 | variable_item_set_current_value_index(app->item_bw, 9); 1024 | variable_item_set_current_value_text(app->item_bw, config_bw_names[9]); 1025 | model->config_bw_index = 9; 1026 | 1027 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 1028 | variable_item_set_current_value_index(app->item_sf, 4); 1029 | variable_item_set_current_value_text(app->item_sf, config_sf_names[4]); 1030 | model->config_sf_index = 4; 1031 | 1032 | break; 1033 | case 9: // SF8/500kHz 1034 | configSetSpreadingFactor(0x08); 1035 | configSetBandwidth(0x06); 1036 | 1037 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 1038 | variable_item_set_current_value_index(app->item_bw, 9); 1039 | variable_item_set_current_value_text(app->item_bw, config_bw_names[9]); 1040 | model->config_bw_index = 9; 1041 | 1042 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 1043 | variable_item_set_current_value_index(app->item_sf, 3); 1044 | variable_item_set_current_value_text(app->item_sf, config_sf_names[3]); 1045 | model->config_sf_index = 3; 1046 | 1047 | break; 1048 | case 10: // SF7/500kHz 1049 | configSetSpreadingFactor(0x07); 1050 | configSetBandwidth(0x06); 1051 | 1052 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 1053 | variable_item_set_current_value_index(app->item_bw, 9); 1054 | variable_item_set_current_value_text(app->item_bw, config_bw_names[9]); 1055 | model->config_bw_index = 9; 1056 | 1057 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 1058 | variable_item_set_current_value_index(app->item_sf, 2); 1059 | variable_item_set_current_value_text(app->item_sf, config_sf_names[2]); 1060 | model->config_sf_index = 2; 1061 | 1062 | break; 1063 | } 1064 | } 1065 | 1066 | static const char* config_us915_ul_channels_125k_label = "UL 125 kHz"; 1067 | 1068 | static void lora_config_us915_ul_channels_125k_change(VariableItem* item) { 1069 | LoRaApp* app = variable_item_get_context(item); 1070 | uint8_t index = variable_item_get_current_value_index(item); 1071 | 1072 | char text_buf[11] = {0}; 1073 | snprintf( 1074 | text_buf, 1075 | sizeof(text_buf), 1076 | "%3lu.%1lu MHz", 1077 | config_us915_ul_channels_125k[index] / 1000000, 1078 | (config_us915_ul_channels_125k[index] % 1000000) / 100000); 1079 | variable_item_set_current_value_text(item, text_buf); 1080 | 1081 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1082 | model->config_us915_ul_channels_125k_index = index; 1083 | 1084 | app->config_frequency = (int)config_us915_ul_channels_125k[index]; 1085 | // setting text for configure frequency 1086 | furi_string_set(model->config_freq_name, text_buf); 1087 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1088 | 1089 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1090 | 1091 | configSetFrequency(app->config_frequency); 1092 | } 1093 | 1094 | static const char* config_us915_ul_channels_500k_label = "UL 500 kHz"; 1095 | 1096 | static void lora_config_us915_ul_channels_500k_change(VariableItem* item) { 1097 | LoRaApp* app = variable_item_get_context(item); 1098 | uint8_t index = variable_item_get_current_value_index(item); 1099 | 1100 | char text_buf[11] = {0}; 1101 | snprintf( 1102 | text_buf, 1103 | sizeof(text_buf), 1104 | "%3lu.%1lu MHz", 1105 | config_us915_ul_channels_500k[index] / 1000000, 1106 | (config_us915_ul_channels_500k[index] % 1000000) / 100000); 1107 | variable_item_set_current_value_text(item, text_buf); 1108 | 1109 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1110 | model->config_us915_ul_channels_500k_index = index; 1111 | 1112 | app->config_frequency = (int)config_us915_ul_channels_500k[index]; 1113 | // setting text for configure frequency 1114 | furi_string_set(model->config_freq_name, text_buf); 1115 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1116 | 1117 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1118 | 1119 | configSetFrequency(app->config_frequency); 1120 | } 1121 | 1122 | static const char* config_us915_dl_channels_500k_label = "DL 500 kHz"; 1123 | 1124 | static void lora_config_us915_dl_channels_500k_change(VariableItem* item) { 1125 | LoRaApp* app = variable_item_get_context(item); 1126 | uint8_t index = variable_item_get_current_value_index(item); 1127 | 1128 | char text_buf[11] = {0}; 1129 | snprintf( 1130 | text_buf, 1131 | sizeof(text_buf), 1132 | "%3lu.%1lu MHz", 1133 | config_us915_dl_channels_500k[index] / 1000000, 1134 | (config_us915_dl_channels_500k[index] % 1000000) / 100000); 1135 | variable_item_set_current_value_text(item, text_buf); 1136 | 1137 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1138 | model->config_us915_dl_channels_500k_index = index; 1139 | 1140 | app->config_frequency = (int)config_us915_dl_channels_500k[index]; 1141 | // setting text for configure frequency 1142 | furi_string_set(model->config_freq_name, text_buf); 1143 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1144 | 1145 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1146 | 1147 | configSetFrequency(app->config_frequency); 1148 | } 1149 | 1150 | static const char* config_eu868_ul_channels_125k_label = "UL 125 kHz"; 1151 | 1152 | static void lora_config_eu868_ul_channels_125k_change(VariableItem* item) { 1153 | LoRaApp* app = variable_item_get_context(item); 1154 | uint8_t index = variable_item_get_current_value_index(item); 1155 | 1156 | char text_buf[11] = {0}; 1157 | snprintf( 1158 | text_buf, 1159 | sizeof(text_buf), 1160 | "%3lu.%1lu MHz", 1161 | config_eu868_ul_channels_125k[index] / 1000000, 1162 | (config_eu868_ul_channels_125k[index] % 1000000) / 100000); 1163 | variable_item_set_current_value_text(item, text_buf); 1164 | 1165 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1166 | model->config_eu868_ul_channels_125k_index = index; 1167 | 1168 | app->config_frequency = (int)config_eu868_ul_channels_125k[index]; 1169 | // setting text for configure frequency 1170 | furi_string_set(model->config_freq_name, text_buf); 1171 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1172 | 1173 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1174 | 1175 | configSetFrequency(app->config_frequency); 1176 | } 1177 | 1178 | static const char* config_eu868_ul_channels_250k_label = "UL 250 kHz"; 1179 | 1180 | static void lora_config_eu868_ul_channels_250k_change(VariableItem* item) { 1181 | LoRaApp* app = variable_item_get_context(item); 1182 | uint8_t index = variable_item_get_current_value_index(item); 1183 | 1184 | char text_buf[11] = {0}; 1185 | snprintf( 1186 | text_buf, 1187 | sizeof(text_buf), 1188 | "%3lu.%1lu MHz", 1189 | config_eu868_ul_channels_250k[index] / 1000000, 1190 | (config_eu868_ul_channels_250k[index] % 1000000) / 100000); 1191 | variable_item_set_current_value_text(item, text_buf); 1192 | 1193 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1194 | model->config_eu868_ul_channels_250k_index = index; 1195 | 1196 | app->config_frequency = (int)config_eu868_ul_channels_250k[index]; 1197 | // setting text for configure frequency 1198 | furi_string_set(model->config_freq_name, text_buf); 1199 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1200 | 1201 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1202 | 1203 | configSetFrequency(app->config_frequency); 1204 | } 1205 | 1206 | static const char* config_eu868_dl_channels_rx1_label = "DL RX1"; 1207 | 1208 | static void lora_config_eu868_dl_channels_rx1_change(VariableItem* item) { 1209 | LoRaApp* app = variable_item_get_context(item); 1210 | uint8_t index = variable_item_get_current_value_index(item); 1211 | 1212 | char text_buf[11] = {0}; 1213 | snprintf( 1214 | text_buf, 1215 | sizeof(text_buf), 1216 | "%3lu.%1lu MHz", 1217 | config_eu868_dl_channels_rx1[index] / 1000000, 1218 | (config_eu868_dl_channels_rx1[index] % 1000000) / 100000); 1219 | variable_item_set_current_value_text(item, text_buf); 1220 | 1221 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1222 | model->config_eu868_dl_channels_rx1_index = index; 1223 | 1224 | app->config_frequency = (int)config_eu868_dl_channels_rx1[index]; 1225 | // setting text for configure frequency 1226 | furi_string_set(model->config_freq_name, text_buf); 1227 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1228 | 1229 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1230 | 1231 | configSetFrequency(app->config_frequency); 1232 | } 1233 | 1234 | static const char* config_region_label = "Frequency Plan"; 1235 | 1236 | static void lora_config_region_change(VariableItem* item) { 1237 | LoRaApp* app = variable_item_get_context(item); 1238 | uint8_t index = variable_item_get_current_value_index(item); 1239 | variable_item_set_current_value_text(item, config_region_names[index]); 1240 | 1241 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1242 | model->config_region_index = index; 1243 | 1244 | variable_item_list_reset(app->variable_item_list_lorawan); 1245 | 1246 | char text_buf[11] = {0}; 1247 | 1248 | if(index == 0) { 1249 | app->config_frequency = 868100000; 1250 | // setting text for configure frequency 1251 | snprintf( 1252 | text_buf, 1253 | sizeof(text_buf), 1254 | "%3lu.%1lu MHz", 1255 | app->config_frequency / 1000000, 1256 | (app->config_frequency % 1000000) / 100000); 1257 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1258 | 1259 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1260 | 1261 | configSetFrequency(app->config_frequency); 1262 | 1263 | // Frequency Plan 1264 | app->item_region = variable_item_list_add( 1265 | app->variable_item_list_lorawan, 1266 | config_region_label, 1267 | COUNT_OF(config_region_values), 1268 | lora_config_region_change, 1269 | app); 1270 | uint8_t config_region_index = 0; 1271 | variable_item_set_current_value_index(app->item_region, config_region_index); 1272 | variable_item_set_current_value_text( 1273 | app->item_region, config_region_names[config_region_index]); 1274 | 1275 | // EU868 Data Rate 1276 | app->item_eu_dr = variable_item_list_add( 1277 | app->variable_item_list_lorawan, 1278 | config_eu_dr_label, 1279 | COUNT_OF(config_eu_dr_values), 1280 | lora_config_eu_dr_change, 1281 | app); 1282 | uint8_t config_eu_dr_index = 0; 1283 | variable_item_set_current_value_index(app->item_eu_dr, config_eu_dr_index); 1284 | variable_item_set_current_value_text( 1285 | app->item_eu_dr, config_eu_dr_names[config_eu_dr_index]); 1286 | 1287 | // Uplink EU868 Channel 125K 1288 | app->item_eu868_ul_channels_125k = variable_item_list_add( 1289 | app->variable_item_list_lorawan, 1290 | config_eu868_ul_channels_125k_label, 1291 | COUNT_OF(config_eu868_ul_channels_125k), 1292 | lora_config_eu868_ul_channels_125k_change, 1293 | app); 1294 | uint8_t config_eu868_ul_channels_125k_index = 0; 1295 | variable_item_set_current_value_index( 1296 | app->item_eu868_ul_channels_125k, config_eu868_ul_channels_125k_index); 1297 | 1298 | snprintf( 1299 | text_buf, 1300 | sizeof(text_buf), 1301 | "%3lu.%1lu MHz", 1302 | config_eu868_ul_channels_125k[index] / 1000000, 1303 | (config_eu868_ul_channels_125k[index] % 1000000) / 100000); 1304 | variable_item_set_current_value_text(app->item_eu868_ul_channels_125k, text_buf); 1305 | 1306 | // Uplink EU868 Channel 250K 1307 | app->item_eu868_ul_channels_250k = variable_item_list_add( 1308 | app->variable_item_list_lorawan, 1309 | config_eu868_ul_channels_250k_label, 1310 | COUNT_OF(config_eu868_ul_channels_250k), 1311 | lora_config_eu868_ul_channels_250k_change, 1312 | app); 1313 | uint8_t config_eu868_ul_channels_250k_index = 0; 1314 | variable_item_set_current_value_index( 1315 | app->item_eu868_ul_channels_250k, config_eu868_ul_channels_250k_index); 1316 | 1317 | snprintf( 1318 | text_buf, 1319 | sizeof(text_buf), 1320 | "%3lu.%1lu MHz", 1321 | config_eu868_ul_channels_250k[index] / 1000000, 1322 | (config_eu868_ul_channels_250k[index] % 1000000) / 100000); 1323 | variable_item_set_current_value_text(app->item_eu868_ul_channels_250k, text_buf); 1324 | 1325 | // Downlink EU868 Channel RX1 1326 | app->item_eu868_dl_channels_rx1 = variable_item_list_add( 1327 | app->variable_item_list_lorawan, 1328 | config_eu868_dl_channels_rx1_label, 1329 | COUNT_OF(config_eu868_dl_channels_rx1), 1330 | lora_config_eu868_dl_channels_rx1_change, 1331 | app); 1332 | uint8_t config_eu868_dl_channels_rx1_index = 0; 1333 | variable_item_set_current_value_index( 1334 | app->item_eu868_dl_channels_rx1, config_eu868_dl_channels_rx1_index); 1335 | 1336 | snprintf( 1337 | text_buf, 1338 | sizeof(text_buf), 1339 | "%3lu.%1lu MHz", 1340 | config_eu868_dl_channels_rx1[index] / 1000000, 1341 | (config_eu868_dl_channels_rx1[index] % 1000000) / 100000); 1342 | variable_item_set_current_value_text(app->item_eu868_dl_channels_rx1, text_buf); 1343 | 1344 | } else if(index == 1) { 1345 | app->config_frequency = 902300000; //first channel 1346 | // setting text for configure frequency 1347 | snprintf( 1348 | text_buf, 1349 | sizeof(text_buf), 1350 | "%3lu.%1lu MHz", 1351 | app->config_frequency / 1000000, 1352 | (app->config_frequency % 1000000) / 100000); 1353 | variable_item_set_current_value_text(app->config_freq_item, text_buf); 1354 | 1355 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1356 | 1357 | configSetFrequency(app->config_frequency); 1358 | 1359 | // Frequency Plan 1360 | app->item_region = variable_item_list_add( 1361 | app->variable_item_list_lorawan, 1362 | config_region_label, 1363 | COUNT_OF(config_region_values), 1364 | lora_config_region_change, 1365 | app); 1366 | uint8_t config_region_index = 1; 1367 | variable_item_set_current_value_index(app->item_region, config_region_index); 1368 | variable_item_set_current_value_text( 1369 | app->item_region, config_region_names[config_region_index]); 1370 | 1371 | // US915 Data Rate 1372 | app->item_us_dr = variable_item_list_add( 1373 | app->variable_item_list_lorawan, 1374 | config_us_dr_label, 1375 | COUNT_OF(config_us_dr_values), 1376 | lora_config_us_dr_change, 1377 | app); 1378 | uint8_t config_us_dr_index = 0; 1379 | variable_item_set_current_value_index(app->item_us_dr, config_us_dr_index); 1380 | variable_item_set_current_value_text( 1381 | app->item_us_dr, config_us_dr_names[config_us_dr_index]); 1382 | 1383 | // Uplink US915 Channel 125K 1384 | app->item_us915_ul_channels_125k = variable_item_list_add( 1385 | app->variable_item_list_lorawan, 1386 | config_us915_ul_channels_125k_label, 1387 | COUNT_OF(config_us915_ul_channels_125k), 1388 | lora_config_us915_ul_channels_125k_change, 1389 | app); 1390 | uint8_t config_us915_ul_channels_125k_index = 0; 1391 | variable_item_set_current_value_index( 1392 | app->item_us915_ul_channels_125k, config_us915_ul_channels_125k_index); 1393 | 1394 | snprintf( 1395 | text_buf, 1396 | sizeof(text_buf), 1397 | "%3lu.%1lu MHz", 1398 | config_us915_ul_channels_125k[index] / 1000000, 1399 | (config_us915_ul_channels_125k[index] % 1000000) / 100000); 1400 | variable_item_set_current_value_text(app->item_us915_ul_channels_125k, text_buf); 1401 | 1402 | // Uplink US915 Channel 500K 1403 | app->item_us915_ul_channels_500k = variable_item_list_add( 1404 | app->variable_item_list_lorawan, 1405 | config_us915_ul_channels_500k_label, 1406 | COUNT_OF(config_us915_ul_channels_500k), 1407 | lora_config_us915_ul_channels_500k_change, 1408 | app); 1409 | uint8_t config_us915_ul_channels_500k_index = 0; 1410 | variable_item_set_current_value_index( 1411 | app->item_us915_ul_channels_500k, config_us915_ul_channels_500k_index); 1412 | 1413 | snprintf( 1414 | text_buf, 1415 | sizeof(text_buf), 1416 | "%3lu.%1lu MHz", 1417 | config_us915_ul_channels_500k[index] / 1000000, 1418 | (config_us915_ul_channels_500k[index] % 1000000) / 100000); 1419 | variable_item_set_current_value_text(app->item_us915_ul_channels_500k, text_buf); 1420 | 1421 | // Downlink US915 Channel 500K 1422 | app->item_us915_dl_channels_500k = variable_item_list_add( 1423 | app->variable_item_list_lorawan, 1424 | config_us915_dl_channels_500k_label, 1425 | COUNT_OF(config_us915_dl_channels_500k), 1426 | lora_config_us915_dl_channels_500k_change, 1427 | app); 1428 | uint8_t config_us915_dl_channels_500k_index = 0; 1429 | variable_item_set_current_value_index( 1430 | app->item_us915_dl_channels_500k, config_us915_dl_channels_500k_index); 1431 | 1432 | snprintf( 1433 | text_buf, 1434 | sizeof(text_buf), 1435 | "%3lu.%1lu MHz", 1436 | config_us915_dl_channels_500k[index] / 1000000, 1437 | (config_us915_dl_channels_500k[index] % 1000000) / 100000); 1438 | variable_item_set_current_value_text(app->item_us915_dl_channels_500k, text_buf); 1439 | } 1440 | } 1441 | 1442 | static const char* config_meshtastic_label = "Presets"; 1443 | static void lora_config_meshtastic_change(VariableItem* item) { 1444 | LoRaApp* app = variable_item_get_context(item); 1445 | uint8_t index = variable_item_get_current_value_index(item); 1446 | variable_item_set_current_value_text(item, config_meshtastic_names[index]); 1447 | 1448 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 1449 | model->config_meshtastic_index = index; 1450 | 1451 | uint8_t bw_index = 0; 1452 | uint8_t sf_index = 0; 1453 | uint8_t cr_index = 0; 1454 | uint8_t sw_index = 2; // Meshtastic (0x2B) 1455 | uint8_t pl_index = 1; // 16 1456 | 1457 | switch(index) { 1458 | case 0: // Short Turbo: SF7 / 500kHz / 4/5 1459 | bw_index = 9; 1460 | sf_index = 2; 1461 | cr_index = 0; 1462 | break; 1463 | 1464 | case 1: // Short Fast: SF7 / 250kHz / 4/5 1465 | bw_index = 8; 1466 | sf_index = 2; 1467 | cr_index = 0; 1468 | break; 1469 | 1470 | case 2: // Short Slow: SF8 / 250kHz / 4/5 1471 | bw_index = 8; 1472 | sf_index = 3; 1473 | cr_index = 0; 1474 | break; 1475 | 1476 | case 3: // Medium Fast: SF9 / 250kHz / 4/5 1477 | bw_index = 8; 1478 | sf_index = 4; 1479 | cr_index = 0; 1480 | break; 1481 | 1482 | case 4: // Medium Slow: SF10 / 250kHz / 4/5 1483 | bw_index = 8; 1484 | sf_index = 5; 1485 | cr_index = 0; 1486 | break; 1487 | 1488 | case 5: // Long Fast: SF11 / 250kHz / 4/5 1489 | bw_index = 8; 1490 | sf_index = 6; 1491 | cr_index = 0; 1492 | break; 1493 | 1494 | case 6: // Long Moderate: SF11 / 125kHz / 4/8 1495 | bw_index = 7; 1496 | sf_index = 6; 1497 | cr_index = 3; 1498 | break; 1499 | 1500 | case 7: // Long Slow: SF12 / 125kHz / 4/8 1501 | bw_index = 7; 1502 | sf_index = 7; 1503 | cr_index = 3; 1504 | break; 1505 | 1506 | default: 1507 | return; 1508 | } 1509 | 1510 | // Apply configuration to hardware 1511 | configSetBandwidth(config_bw_values[bw_index]); 1512 | configSetSpreadingFactor(config_sf_values[sf_index]); 1513 | configSetCodingRate(config_cr_values[cr_index]); 1514 | configSetSyncWord(0x2B, 0x44); 1515 | 1516 | app->packetPreamble = config_pl_values[pl_index]; 1517 | setPacketParams( 1518 | app->packetPreamble, 1519 | app->packetHeaderType, 1520 | app->packetPayloadLength, 1521 | app->packetCRC, 1522 | app->packetInvertIQ); 1523 | 1524 | // Update UI 1525 | variable_item_list_set_selected_item(app->variable_item_list_config, 1); 1526 | variable_item_set_current_value_index(app->item_bw, bw_index); 1527 | variable_item_set_current_value_text(app->item_bw, config_bw_names[bw_index]); 1528 | model->config_bw_index = bw_index; 1529 | 1530 | variable_item_list_set_selected_item(app->variable_item_list_config, 2); 1531 | variable_item_set_current_value_index(app->item_sf, sf_index); 1532 | variable_item_set_current_value_text(app->item_sf, config_sf_names[sf_index]); 1533 | model->config_sf_index = sf_index; 1534 | 1535 | variable_item_list_set_selected_item(app->variable_item_list_config, 3); 1536 | variable_item_set_current_value_index(app->item_cr, cr_index); 1537 | variable_item_set_current_value_text(app->item_cr, config_cr_names[cr_index]); 1538 | model->config_cr_index = cr_index; 1539 | 1540 | variable_item_list_set_selected_item(app->variable_item_list_config, 4); 1541 | variable_item_set_current_value_index(app->item_sw, sw_index); 1542 | variable_item_set_current_value_text(app->item_sw, config_sw_names[sw_index]); 1543 | model->config_sw_index = sw_index; 1544 | 1545 | variable_item_list_set_selected_item(app->variable_item_list_config, 5); 1546 | variable_item_set_current_value_index(app->item_pl, pl_index); 1547 | variable_item_set_current_value_text(app->item_pl, config_pl_names[pl_index]); 1548 | model->config_pl_index = pl_index; 1549 | 1550 | FURI_LOG_I( 1551 | TAG, 1552 | "Preset %s -> SF=%s BW=%s CR=%s SW=%s Preamble=%s", 1553 | config_meshtastic_names[index], 1554 | config_sf_names[sf_index], 1555 | config_bw_names[bw_index], 1556 | config_cr_names[cr_index], 1557 | config_sw_names[sw_index], 1558 | config_pl_names[pl_index]); 1559 | } 1560 | 1561 | /** 1562 | * When the user clicks OK on the configuration frequency setting we use a text input screen to allow 1563 | * the user to enter a frequency. This function is called when the user clicks OK on the text input screen. 1564 | */ 1565 | static const char* config_freq_config_label = "Frequency"; 1566 | static const char* config_freq_entry_text = "Enter frequency (MHz)"; 1567 | static const char* config_freq_default_value = "915.0"; 1568 | static void lora_config_freq_text_updated(void* context) { 1569 | LoRaApp* app = (LoRaApp*)context; 1570 | bool redraw = true; 1571 | with_view_model( 1572 | app->view_sniffer, 1573 | LoRaSnifferModel * model, 1574 | { 1575 | furi_string_set(model->config_freq_name, app->temp_buffer); 1576 | variable_item_set_current_value_text( 1577 | app->config_freq_item, furi_string_get_cstr(model->config_freq_name)); 1578 | 1579 | const char* freq_str = furi_string_get_cstr(model->config_freq_name); 1580 | app->config_frequency = (int)(strtof(freq_str, NULL) * 1000000); 1581 | FURI_LOG_E(TAG, "Frequency = %lu", app->config_frequency); 1582 | }, 1583 | redraw); 1584 | 1585 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewConfigure); 1586 | configSetFrequency(app->config_frequency); 1587 | } 1588 | 1589 | static void set_value(void* context) { 1590 | LoRaApp* app = (LoRaApp*)context; 1591 | 1592 | FURI_LOG_E(TAG, "Byte buffer: %s", (char*)app->byte_buffer); 1593 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewSubmenu); 1594 | transmit(app->byte_buffer, app->byte_buffer_size); 1595 | } 1596 | 1597 | /** 1598 | * @brief Callback when item in configuration screen is clicked. 1599 | * @details This function is called when user clicks OK on an item in the configuration screen. 1600 | * If the item clicked is our text field then we switch to the text input screen. 1601 | * @param context The context - LoRaApp object. 1602 | * @param index - The index of the item that was clicked. 1603 | */ 1604 | static void lora_setting_item_clicked(void* context, uint32_t index) { 1605 | LoRaApp* app = (LoRaApp*)context; 1606 | index++; // The index starts at zero, but we want to start at 1. 1607 | 1608 | // Our configuration UI has the 2nd item as a text field. 1609 | if(index == 1) { 1610 | // Header to display on the text input screen. 1611 | text_input_set_header_text(app->frequency_input, config_freq_entry_text); 1612 | 1613 | // Copy the current name into the temporary buffer. 1614 | bool redraw = false; 1615 | with_view_model( 1616 | app->view_sniffer, 1617 | LoRaSnifferModel * model, 1618 | { 1619 | strncpy( 1620 | app->temp_buffer, 1621 | furi_string_get_cstr(model->config_freq_name), 1622 | app->temp_buffer_size); 1623 | }, 1624 | redraw); 1625 | 1626 | // Configure the text input. When user enters text and clicks OK, lora_setting_text_updated be called. 1627 | bool clear_previous_text = false; 1628 | text_input_set_result_callback( 1629 | app->frequency_input, 1630 | lora_config_freq_text_updated, 1631 | app, 1632 | app->temp_buffer, 1633 | app->temp_buffer_size, 1634 | clear_previous_text); 1635 | 1636 | // Pressing the BACK button will reload the configure screen. 1637 | view_set_previous_callback( 1638 | text_input_get_view(app->frequency_input), lora_navigation_configure_callback); 1639 | 1640 | // Show text input dialog. 1641 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewFrequencyInput); 1642 | } 1643 | } 1644 | 1645 | // Open serial port USB (dual mode CDC) 1646 | bool serial_open_port(void) { 1647 | if(furi_hal_usb_get_config() != &usb_cdc_dual) { 1648 | return furi_hal_usb_set_config(&usb_cdc_dual, NULL); 1649 | } 1650 | return true; 1651 | } 1652 | 1653 | // Close serial port (simple mode) 1654 | bool serial_close_port(void) { 1655 | if(furi_hal_usb_get_config() == &usb_cdc_dual) { 1656 | return furi_hal_usb_set_config(&usb_cdc_single, NULL); 1657 | } 1658 | return true; 1659 | } 1660 | 1661 | // Send raw bytes 1662 | void serial_send_bytes(const uint8_t* data, size_t len) { 1663 | if(len > 0) { 1664 | furi_hal_cdc_send(CDC_PORT_NUM, (uint8_t*)data, len); 1665 | } 1666 | } 1667 | 1668 | uint8_t receiveBuff[255]; 1669 | char asciiBuff[512]; 1670 | 1671 | void bytesToAsciiHex(uint8_t* buffer, uint8_t length) { 1672 | uint8_t i; 1673 | for(i = 0; i < length; ++i) { 1674 | asciiBuff[i * 2] = "0123456789ABCDEF"[buffer[i] >> 4]; // High nibble 1675 | asciiBuff[i * 2 + 1] = "0123456789ABCDEF"[buffer[i] & 0x0F]; // Low nibble 1676 | } 1677 | asciiBuff[length * 2] = '\0'; // Null-terminate the string 1678 | } 1679 | 1680 | void asciiHexToBytes(const char* hex, uint8_t* bytes, size_t length) { 1681 | for(size_t i = 0; i < length; i++) { 1682 | sscanf(hex + 2 * i, "%02hhx", &bytes[i]); 1683 | } 1684 | } 1685 | 1686 | uint8_t hola[] = "¡Hola mundo!, esta es una prueba."; 1687 | uint8_t mundo[] = " lo esencial :D"; 1688 | 1689 | /** 1690 | * @brief Callback for drawing the sniffer screen. 1691 | * @details This function is called when the screen needs to be redrawn, like when the model gets updated. 1692 | * @param canvas The canvas to draw on. 1693 | * @param model The model - MyModel object. 1694 | */ 1695 | static void lora_view_sniffer_draw_callback(Canvas* canvas, void* model) { 1696 | LoRaSnifferModel* my_model = (LoRaSnifferModel*)model; 1697 | 1698 | bool flag_file = my_model->flag_file; 1699 | 1700 | canvas_draw_icon(canvas, 0, 17, &I_flippers_cat); 1701 | 1702 | // Receive a packet over radio 1703 | int bytesRead = lora_receive_async(receiveBuff, sizeof(receiveBuff)); 1704 | 1705 | if(bytesRead > -1) { 1706 | FURI_LOG_E(TAG, "Packet received... "); 1707 | receiveBuff[bytesRead] = '\0'; 1708 | bytesToAsciiHex(receiveBuff, bytesRead); 1709 | 1710 | FURI_LOG_E(TAG, "bytesRead = %d", bytesRead); 1711 | FURI_LOG_E(TAG, "receiveBuff -> %s", receiveBuff); 1712 | 1713 | uint8_t frame[512]; 1714 | uint16_t index = 0; 1715 | 1716 | // SOF 1717 | frame[index++] = '@'; 1718 | frame[index++] = 'S'; 1719 | 1720 | // Packet length (bytesRead) 1721 | frame[index++] = (uint8_t)((bytesRead >> 8) & 0xFF); // high byte 1722 | frame[index++] = (uint8_t)(bytesRead & 0xFF); // low byte 1723 | 1724 | // Payload 1725 | memcpy(&frame[index], receiveBuff, bytesRead); 1726 | index += bytesRead; 1727 | 1728 | // RSSI 1729 | int16_t rssi = getRSSI(); 1730 | frame[index++] = (uint8_t)((rssi >> 8) & 0xFF); 1731 | frame[index++] = (uint8_t)(rssi & 0xFF); 1732 | 1733 | // SNR 1734 | int8_t snr = getSNR(); 1735 | frame[index++] = (uint8_t)snr; 1736 | 1737 | // EOF 1738 | frame[index++] = '@'; 1739 | frame[index++] = 'E'; 1740 | frame[index++] = '\r'; 1741 | frame[index++] = '\n'; 1742 | 1743 | for(uint16_t i = 0; i < index; i += 64) { 1744 | size_t chunk = (index - i > 64) ? 64 : (index - i); 1745 | serial_send_bytes(frame + i, chunk); 1746 | furi_delay_ms(2); 1747 | } 1748 | 1749 | if(flag_file) { 1750 | DateTime curr_dt; 1751 | furi_hal_rtc_get_datetime(&curr_dt); 1752 | 1753 | char time_string[TIME_LEN]; 1754 | char date_string[DATE_LEN]; 1755 | 1756 | snprintf( 1757 | time_string, 1758 | TIME_LEN, 1759 | CLOCK_TIME_FORMAT, 1760 | curr_dt.hour, 1761 | curr_dt.minute, 1762 | curr_dt.second); 1763 | snprintf( 1764 | date_string, 1765 | DATE_LEN, 1766 | CLOCK_ISO_DATE_FORMAT, 1767 | curr_dt.year, 1768 | curr_dt.month, 1769 | curr_dt.day); 1770 | 1771 | char final_string[400]; 1772 | const char* freq_str = furi_string_get_cstr(my_model->config_freq_name); 1773 | 1774 | // JSON format 1775 | snprintf( 1776 | final_string, 1777 | 666, 1778 | "{\"date\":\"%s\", \"time\":\"%s\", \"frequency\":\"%s\", \"bw\":\"%s\", \"sf\":\"%s\", \"RSSI\":\"%d\", \"payload\":\"%s\"}", 1779 | date_string, 1780 | time_string, 1781 | freq_str, 1782 | config_bw_names[my_model->config_bw_index], 1783 | config_sf_names[my_model->config_sf_index], 1784 | getRSSI(), 1785 | asciiBuff); 1786 | 1787 | FURI_LOG_E(TAG, "TS: %s", final_string); 1788 | FURI_LOG_E(TAG, "Length: %d", strlen(final_string) + 1); 1789 | 1790 | storage_file_write(my_model->file_rx, final_string, strlen(final_string)); 1791 | storage_file_write(my_model->file_rx, "\n", 1); 1792 | } 1793 | 1794 | FURI_LOG_E(TAG, "%s", asciiBuff); //receiveBuff); 1795 | } 1796 | 1797 | FuriString* xstr = furi_string_alloc(); 1798 | 1799 | if(flag_file) { 1800 | canvas_draw_icon(canvas, 110, 1, &I_write); 1801 | furi_string_printf(xstr, "Recording..."); 1802 | canvas_draw_str(canvas, 60, 20, furi_string_get_cstr(xstr)); 1803 | } else { 1804 | canvas_draw_icon(canvas, 110, 1, &I_no_write); 1805 | furi_string_printf(xstr, " "); 1806 | canvas_draw_str(canvas, 60, 20, furi_string_get_cstr(xstr)); 1807 | } 1808 | 1809 | receiveBuff[17] = '.'; 1810 | receiveBuff[18] = '.'; 1811 | receiveBuff[19] = '.'; 1812 | receiveBuff[20] = '\0'; 1813 | 1814 | canvas_draw_str(canvas, 1, 10, (const char*)receiveBuff); 1815 | 1816 | furi_string_printf(xstr, "RSSI: %d ", getRSSI()); 1817 | canvas_draw_str(canvas, 1, 19, furi_string_get_cstr(xstr)); 1818 | 1819 | furi_string_printf(xstr, "BW:%s", config_bw_names[my_model->config_bw_index]); 1820 | canvas_draw_str(canvas, 1, 28, furi_string_get_cstr(xstr)); 1821 | 1822 | furi_string_printf(xstr, "FQ:%s MHz", furi_string_get_cstr(my_model->config_freq_name)); 1823 | canvas_draw_str(canvas, 60, 28, furi_string_get_cstr(xstr)); 1824 | 1825 | furi_string_free(xstr); 1826 | } 1827 | 1828 | /** 1829 | * @brief Callback for drawing the transmitter screen. 1830 | * @details This function is called when the screen needs to be redrawn, like when the model gets updated. 1831 | * @param canvas The canvas to draw on. 1832 | * @param model The model - MyModel object. 1833 | */ 1834 | static void lora_view_transmitter_draw_callback(Canvas* canvas, void* model) { 1835 | LoRaTransmitterModel* my_model = (LoRaTransmitterModel*)model; 1836 | 1837 | my_model->x = 0; 1838 | 1839 | canvas_draw_icon(canvas, 1, 3, &I_kitty_tx); 1840 | 1841 | canvas_draw_str(canvas, 1, 10, "Press central"); 1842 | canvas_draw_str(canvas, 1, 20, "button to"); 1843 | canvas_draw_str(canvas, 1, 30, "browser"); 1844 | 1845 | FuriString* xstr = furi_string_alloc(); 1846 | canvas_draw_str(canvas, 1, 50, furi_string_get_cstr(xstr)); 1847 | furi_string_free(xstr); 1848 | } 1849 | 1850 | /** 1851 | * @brief Callback for timer elapsed. 1852 | * @details This function is called when the timer is elapsed. We use this to queue a redraw event. 1853 | * @param context The context - LoRaApp object. 1854 | */ 1855 | static void lora_view_sniffer_timer_callback(void* context) { 1856 | LoRaApp* app = (LoRaApp*)context; 1857 | view_dispatcher_send_custom_event(app->view_dispatcher, LoRaEventIdRedrawScreen); 1858 | } 1859 | 1860 | /** 1861 | * @brief Callback for timer elapsed. 1862 | * @details This function is called when the timer is elapsed. We use this to queue a redraw event. 1863 | * @param context The context - LoRaApp object. 1864 | */ 1865 | static void lora_view_transmitter_timer_callback(void* context) { 1866 | LoRaApp* app = (LoRaApp*)context; 1867 | view_dispatcher_send_custom_event(app->view_dispatcher, LoRaEventIdRedrawScreen); 1868 | // HERE!!! 1869 | } 1870 | 1871 | /** 1872 | * @brief Callback when the user starts the sniffer screen. 1873 | * @details This function is called when the user enters the sniffer screen. We start a timer to 1874 | * redraw the screen periodically. 1875 | * @param context The context - LoRaApp object. 1876 | */ 1877 | static void lora_view_sniffer_enter_callback(void* context) { 1878 | uint32_t period = furi_ms_to_ticks(1000); 1879 | LoRaApp* app = (LoRaApp*)context; 1880 | furi_assert(app->timer_rx == NULL); 1881 | app->timer_rx = 1882 | furi_timer_alloc(lora_view_sniffer_timer_callback, FuriTimerTypePeriodic, context); 1883 | furi_timer_start(app->timer_rx, period); 1884 | } 1885 | 1886 | /** 1887 | * @brief Callback when the user starts the transmitter screen. 1888 | * @details This function is called when the user enters the transmitter screen. We start a timer to 1889 | * redraw the screen periodically (so the random number is refreshed). 1890 | * @param context The context - LoRaApp object. 1891 | */ 1892 | static void lora_view_transmitter_enter_callback(void* context) { 1893 | uint32_t period = furi_ms_to_ticks(1000); 1894 | LoRaApp* app = (LoRaApp*)context; 1895 | furi_assert(app->timer_tx == NULL); 1896 | app->timer_tx = 1897 | furi_timer_alloc(lora_view_transmitter_timer_callback, FuriTimerTypePeriodic, context); 1898 | furi_timer_start(app->timer_tx, period); 1899 | } 1900 | 1901 | /** 1902 | * @brief Callback when the user exits the sniffer screen. 1903 | * @details This function is called when the user exits the sniffer screen. We stop the timer. 1904 | * @param context The context - LoRaApp object. 1905 | */ 1906 | static void lora_view_sniffer_exit_callback(void* context) { 1907 | LoRaApp* app = (LoRaApp*)context; 1908 | furi_timer_stop(app->timer_rx); 1909 | furi_timer_free(app->timer_rx); 1910 | app->timer_rx = NULL; 1911 | FURI_LOG_E(TAG, "Stop timer rx"); 1912 | } 1913 | 1914 | /** 1915 | * @brief Callback when the user exits the transmitter screen. 1916 | * @details This function is called when the user exits the transmitter screen. We stop the timer. 1917 | * @param context The context - LoRaApp object. 1918 | */ 1919 | static void lora_view_transmitter_exit_callback(void* context) { 1920 | LoRaApp* app = (LoRaApp*)context; 1921 | furi_timer_stop(app->timer_tx); 1922 | furi_timer_free(app->timer_tx); 1923 | app->timer_tx = NULL; 1924 | FURI_LOG_E(TAG, "Stop timer tx"); 1925 | } 1926 | 1927 | /** 1928 | * @brief Callback for custom events. 1929 | * @details This function is called when a custom event is sent to the view dispatcher. 1930 | * @param event The event id - LoRaEventId value. 1931 | * @param context The context - LoRaApp object. 1932 | */ 1933 | static bool lora_view_sniffer_custom_event_callback(uint32_t event, void* context) { 1934 | LoRaApp* app = (LoRaApp*)context; 1935 | switch(event) { 1936 | case LoRaEventIdRedrawScreen: 1937 | // Redraw screen by passing true to last parameter of with_view_model. 1938 | { 1939 | bool redraw = true; 1940 | with_view_model( 1941 | app->view_sniffer, LoRaSnifferModel * _model, { UNUSED(_model); }, redraw); 1942 | return true; 1943 | } 1944 | case LoRaEventIdOkPressed: 1945 | // Process the OK button. We play a tone. 1946 | if(furi_hal_speaker_acquire(500)) { 1947 | float frequency; 1948 | bool redraw = false; 1949 | with_view_model( 1950 | app->view_sniffer, 1951 | LoRaSnifferModel * model, 1952 | { frequency = model->x * 100 + 100; }, 1953 | redraw); 1954 | furi_hal_speaker_start(frequency, 1.0); 1955 | furi_delay_ms(100); 1956 | furi_hal_speaker_stop(); 1957 | furi_hal_speaker_release(); 1958 | } 1959 | return true; 1960 | default: 1961 | return false; 1962 | } 1963 | } 1964 | 1965 | /** 1966 | * @brief Callback for custom events. 1967 | * @details This function is called when a custom event is sent to the view dispatcher. 1968 | * @param event The event id - LoRaEventId value. 1969 | * @param context The context - LoRaApp object. 1970 | */ 1971 | static bool lora_view_transmitter_custom_event_callback(uint32_t event, void* context) { 1972 | LoRaApp* app = (LoRaApp*)context; 1973 | switch(event) { 1974 | case LoRaEventIdRedrawScreen: 1975 | // Redraw screen by passing true to last parameter of with_view_model. 1976 | { 1977 | bool redraw = true; 1978 | with_view_model( 1979 | app->view_transmitter, LoRaTransmitterModel * _model, { UNUSED(_model); }, redraw); 1980 | return true; 1981 | } 1982 | case LoRaEventIdOkPressed: 1983 | // Process the OK button. We play a tone. 1984 | if(furi_hal_speaker_acquire(500)) { 1985 | float frequency; 1986 | bool redraw = false; 1987 | with_view_model( 1988 | app->view_transmitter, 1989 | LoRaTransmitterModel * model, 1990 | { frequency = model->x * 100 + 100; }, 1991 | redraw); 1992 | furi_hal_speaker_start(frequency, 1.0); 1993 | furi_delay_ms(100); 1994 | furi_hal_speaker_stop(); 1995 | furi_hal_speaker_release(); 1996 | } 1997 | return true; 1998 | default: 1999 | return false; 2000 | } 2001 | } 2002 | 2003 | /** 2004 | * @brief Callback for sniffer screen input. 2005 | * @details This function is called when the user presses a button while on the sniffer screen. 2006 | * @param event The event - InputEvent object. 2007 | * @param context The context - LoRaApp object. 2008 | * @return true if the event was handled, false otherwise. 2009 | */ 2010 | static bool lora_view_sniffer_input_callback(InputEvent* event, void* context) { 2011 | LoRaApp* app = (LoRaApp*)context; 2012 | if(event->type == InputTypeShort) { 2013 | if(event->key == InputKeyLeft) { 2014 | // Left button clicked, reduce x coordinate. 2015 | bool redraw = true; 2016 | with_view_model( 2017 | app->view_sniffer, 2018 | LoRaSnifferModel * model, 2019 | { 2020 | if(model->x > 0) { 2021 | model->x--; 2022 | } 2023 | }, 2024 | redraw); 2025 | } else if(event->key == InputKeyRight) { 2026 | // Right button clicked, increase x coordinate. 2027 | bool redraw = true; 2028 | with_view_model( 2029 | app->view_sniffer, 2030 | LoRaSnifferModel * model, 2031 | { 2032 | // Should we have some maximum value? 2033 | model->x++; 2034 | }, 2035 | redraw); 2036 | } 2037 | } else if(event->type == InputTypePress) { 2038 | if(event->key == InputKeyOk) { 2039 | // We choose to send a custom event when user presses OK button. lora_custom_event_callback will 2040 | // handle our LoRaEventIdOkPressed event. We could have just put the code from 2041 | // lora_custom_event_callback here, it's a matter of preference. 2042 | 2043 | bool redraw = true; 2044 | with_view_model( 2045 | app->view_sniffer, 2046 | LoRaSnifferModel * model, 2047 | { 2048 | // Start/Stop recording 2049 | model->flag_file = !model->flag_file; 2050 | 2051 | if(model->flag_file) { 2052 | // if(!storage_simply_mkdir(model->storage_rx, PATHAPPEXT)) { 2053 | // FURI_LOG_E(TAG, "Failed to create directory %s", PATHAPPEXT); 2054 | // return; 2055 | // } 2056 | 2057 | char filename[256]; 2058 | int file_index = 0; 2059 | 2060 | do { 2061 | snprintf(filename, sizeof(filename), PATHLORA, file_index); 2062 | file_index++; 2063 | } while(storage_file_exists(model->storage_rx, filename)); 2064 | 2065 | if(!storage_file_open( 2066 | model->file_rx, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { 2067 | FURI_LOG_E(TAG, "Failed to open file %s", filename); 2068 | return 0; 2069 | } 2070 | FURI_LOG_E(TAG, "OPEN FILE "); 2071 | 2072 | } else { 2073 | storage_file_close(model->file_rx); 2074 | FURI_LOG_E(TAG, "CLOSE FILE "); 2075 | } 2076 | }, 2077 | redraw); 2078 | 2079 | view_dispatcher_send_custom_event(app->view_dispatcher, LoRaEventIdOkPressed); 2080 | return true; 2081 | } 2082 | } 2083 | 2084 | return false; 2085 | } 2086 | 2087 | void tx_payload(const char* line) { 2088 | const char* key = "\"payload\":\""; 2089 | char* start = strstr(line, key); 2090 | if(start) { 2091 | start += strlen(key); // Advance the pointer to the end of “payload”: 2092 | char* end = strchr(start, '"'); // find next " 2093 | if(end) { 2094 | // Calculates the length of the substring 2095 | size_t length = end - start; 2096 | char payload[length + 1]; // +1 to end in NULL 2097 | strncpy(payload, start, length); 2098 | payload[length] = '\0'; // adds the NULL 2099 | 2100 | FURI_LOG_E(TAG, "%s\n", payload); 2101 | 2102 | size_t byte_length = length / 2; 2103 | uint8_t bytes[byte_length]; 2104 | 2105 | // Convert hex string to bytes 2106 | asciiHexToBytes(payload, bytes, byte_length); 2107 | 2108 | FURI_LOG_E(TAG, "%s\n", payload); 2109 | transmit(bytes, byte_length); 2110 | furi_delay_ms(10); 2111 | } 2112 | } 2113 | } 2114 | 2115 | void send_data(void* context) { 2116 | LoRaApp* app = (LoRaApp*)context; 2117 | LoRaTransmitterModel* model = view_get_model(app->view_transmitter); 2118 | 2119 | //uint8_t transmitBuff[64]; 2120 | FuriString* predefined_filepath = furi_string_alloc_set_str(PATHAPP); 2121 | FuriString* selected_filepath = furi_string_alloc(); 2122 | DialogsFileBrowserOptions browser_options; 2123 | dialog_file_browser_set_basic_options(&browser_options, LORA_LOG_FILE_EXTENSION, NULL); 2124 | browser_options.base_path = PATHAPP; 2125 | 2126 | dialog_file_browser_show( 2127 | model->dialogs_tx, selected_filepath, predefined_filepath, &browser_options); 2128 | 2129 | if(storage_file_open( 2130 | model->file_tx, 2131 | furi_string_get_cstr(selected_filepath), 2132 | FSAM_READ, 2133 | FSOM_OPEN_EXISTING)) { 2134 | model->flag_tx_file = true; 2135 | model->test = 1; 2136 | 2137 | char buffer[256]; 2138 | size_t buffer_index = 0; 2139 | size_t bytes_read; 2140 | char c; 2141 | 2142 | while((bytes_read = storage_file_read(model->file_tx, &c, 1)) > 0 && model->flag_signal) { 2143 | if(c == '\n' || buffer_index >= 256 - 1) { 2144 | buffer[buffer_index] = '\0'; 2145 | 2146 | FURI_LOG_E(TAG, "%s\n", buffer); 2147 | 2148 | tx_payload(buffer); 2149 | buffer_index = 0; 2150 | } else { 2151 | buffer[buffer_index++] = c; 2152 | } 2153 | } 2154 | 2155 | } else { 2156 | dialog_message_show_storage_error(model->dialogs_tx, "Cannot open File"); 2157 | } 2158 | storage_file_close(model->file_tx); 2159 | model->test = 0; 2160 | furi_string_free(selected_filepath); 2161 | furi_string_free(predefined_filepath); 2162 | 2163 | furi_hal_gpio_write(pin_led, true); 2164 | furi_delay_ms(50); 2165 | furi_hal_gpio_write(pin_led, false); 2166 | 2167 | model->flag_tx_file = false; 2168 | } 2169 | 2170 | /** 2171 | * @brief Callback for sniffer screen input. 2172 | * @details This function is called when the user presses a button while on the transmitter screen. 2173 | * @param event The event - InputEvent object. 2174 | * @param context The context - LoRaApp object. 2175 | * @return true if the event was handled, false otherwise. 2176 | */ 2177 | static bool lora_view_transmitter_input_callback(InputEvent* event, void* context) { 2178 | LoRaApp* app = (LoRaApp*)context; 2179 | 2180 | bool consumed = false; 2181 | if(event->type == InputTypeShort || event->type == InputTypeRepeat) { 2182 | with_view_model( 2183 | app->view_transmitter, 2184 | LoRaTransmitterModel * model, 2185 | { 2186 | if(event->key == InputKeyLeft && model->test > 0) { 2187 | //model->test--; 2188 | consumed = true; 2189 | } else if(event->key == InputKeyRight) { //&& 2190 | //model->test < (COUNT_OF(view_lora_tx_tests) - 1)) { 2191 | //model->test++; 2192 | consumed = true; 2193 | } else if(event->key == InputKeyDown) { //&& model->size > 0) { 2194 | //model->size--; 2195 | consumed = true; 2196 | } else if(event->key == InputKeyUp) { //&& model->size < 24) { 2197 | //model->size++; 2198 | consumed = true; 2199 | } else if(event->key == InputKeyOk) { 2200 | uint32_t period = furi_ms_to_ticks(1000); 2201 | furi_timer_stop(app->timer_tx); 2202 | model->flag_signal = 1; 2203 | send_data(app); 2204 | furi_timer_start(app->timer_tx, period); 2205 | consumed = true; 2206 | } else if(event->key == InputKeyBack) { 2207 | // FLAG TO STOP TRANSMISSION 2208 | model->flag_signal = 0; 2209 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewSubmenu); 2210 | consumed = true; 2211 | } 2212 | }, 2213 | consumed); 2214 | } 2215 | 2216 | return consumed; 2217 | } 2218 | 2219 | static void lora_app_config_set_payload_length(VariableItem* item) { 2220 | LoRaApp* app = variable_item_get_context(item); 2221 | uint8_t index = variable_item_get_current_value_index(item); 2222 | FuriString* temp; 2223 | temp = furi_string_alloc(); 2224 | furi_string_cat_printf(temp, "%d", index); 2225 | variable_item_set_current_value_text(item, furi_string_get_cstr(temp)); 2226 | furi_string_free(temp); 2227 | app->packetPayloadLength = index; 2228 | 2229 | app->byte_buffer_size = index; 2230 | free(app->byte_buffer); 2231 | app->byte_buffer = (uint8_t*)malloc(app->byte_buffer_size); 2232 | 2233 | byte_input_set_result_callback( 2234 | app->byte_input, set_value, NULL, app, app->byte_buffer, app->byte_buffer_size); 2235 | 2236 | // Order is preamble, header type, packet length, CRC, IQ 2237 | setPacketParams( 2238 | app->packetPreamble, 2239 | app->packetHeaderType, 2240 | app->packetPayloadLength, 2241 | app->packetCRC, 2242 | app->packetInvertIQ); 2243 | } 2244 | 2245 | /** 2246 | * @brief Allocate the LoRa application. 2247 | * @details This function allocates the LoRa application resources. 2248 | * @return LoRaApp object. 2249 | */ 2250 | static LoRaApp* lora_app_alloc() { 2251 | UNUSED(lora_config_eu_dr_change); 2252 | UNUSED(config_eu_dr_label); 2253 | 2254 | LoRaApp* app = (LoRaApp*)malloc(sizeof(LoRaApp)); 2255 | VariableItem* item; 2256 | Gui* gui = furi_record_open(RECORD_GUI); 2257 | 2258 | app->view_dispatcher = view_dispatcher_alloc(); 2259 | //view_dispatcher_enable_queue(app->view_dispatcher); 2260 | view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen); 2261 | view_dispatcher_set_event_callback_context(app->view_dispatcher, app); 2262 | 2263 | app->submenu = submenu_alloc(); 2264 | submenu_add_item( 2265 | app->submenu, "Config", LoRaSubmenuIndexConfigure, lora_submenu_callback, app); 2266 | submenu_add_item(app->submenu, "LoRaWAN", LoRaSubmenuIndexLoRaWAN, lora_submenu_callback, app); 2267 | submenu_add_item( 2268 | app->submenu, "Meshtastic", LoRaSubmenuIndexMeshtastic, lora_submenu_callback, app); 2269 | submenu_add_item(app->submenu, "Sniffer", LoRaSubmenuIndexSniffer, lora_submenu_callback, app); 2270 | submenu_add_item( 2271 | app->submenu, "Transmitter", LoRaSubmenuIndexTransmitter, lora_submenu_callback, app); 2272 | submenu_add_item( 2273 | app->submenu, "Send LoRa byte", LoRaSubmenuIndexManualTX, lora_submenu_callback, app); 2274 | submenu_add_item( 2275 | app->submenu, "Linker Sub-GHz", LoRaSubmenuIndexLinkerSubGHZ, lora_submenu_callback, app); 2276 | submenu_add_item(app->submenu, "About", LoRaSubmenuIndexAbout, lora_submenu_callback, app); 2277 | view_set_previous_callback(submenu_get_view(app->submenu), lora_navigation_exit_callback); 2278 | view_dispatcher_add_view( 2279 | app->view_dispatcher, LoRaViewSubmenu, submenu_get_view(app->submenu)); 2280 | view_dispatcher_switch_to_view(app->view_dispatcher, LoRaViewSubmenu); 2281 | 2282 | app->frequency_input = text_input_alloc(); 2283 | view_dispatcher_add_view( 2284 | app->view_dispatcher, LoRaViewFrequencyInput, text_input_get_view(app->frequency_input)); 2285 | app->temp_buffer_size = 32; 2286 | app->temp_buffer = (char*)malloc(app->temp_buffer_size); 2287 | 2288 | app->byte_buffer_size = 16; 2289 | app->byte_buffer = (uint8_t*)malloc(app->byte_buffer_size); 2290 | 2291 | app->byte_input = byte_input_alloc(); 2292 | 2293 | view_dispatcher_add_view( 2294 | app->view_dispatcher, LoRaViewByteInput, byte_input_get_view(app->byte_input)); 2295 | 2296 | byte_input_set_header_text(app->byte_input, "Set byte to LoRa TX"); 2297 | byte_input_set_result_callback( 2298 | app->byte_input, set_value, NULL, app, app->byte_buffer, app->byte_buffer_size); 2299 | 2300 | view_set_previous_callback( 2301 | byte_input_get_view(app->byte_input), lora_navigation_submenu_callback); 2302 | 2303 | app->packetPayloadLength = 16; 2304 | 2305 | app->variable_item_list_config = variable_item_list_alloc(); 2306 | variable_item_list_reset(app->variable_item_list_config); 2307 | 2308 | app->variable_item_list_lorawan = variable_item_list_alloc(); 2309 | variable_item_list_reset(app->variable_item_list_lorawan); 2310 | 2311 | app->variable_item_list_meshtastic = variable_item_list_alloc(); 2312 | variable_item_list_reset(app->variable_item_list_meshtastic); 2313 | 2314 | // frequency 2315 | FuriString* config_freq_name = furi_string_alloc(); 2316 | furi_string_set_str(config_freq_name, config_freq_default_value); 2317 | app->config_freq_item = variable_item_list_add( 2318 | app->variable_item_list_config, config_freq_config_label, 1, NULL, NULL); 2319 | variable_item_set_current_value_text( 2320 | app->config_freq_item, furi_string_get_cstr(config_freq_name)); 2321 | 2322 | // bw 2323 | app->item_bw = variable_item_list_add( 2324 | app->variable_item_list_config, 2325 | config_bw_label, 2326 | COUNT_OF(config_bw_values), 2327 | lora_config_bw_change, 2328 | app); 2329 | uint8_t config_bw_index = 7; 2330 | variable_item_set_current_value_index(app->item_bw, config_bw_index); 2331 | variable_item_set_current_value_text(app->item_bw, config_bw_names[config_bw_index]); 2332 | 2333 | // sf 2334 | app->item_sf = variable_item_list_add( 2335 | app->variable_item_list_config, 2336 | config_sf_label, 2337 | COUNT_OF(config_sf_values), 2338 | lora_config_sf_change, 2339 | app); 2340 | uint8_t config_sf_index = 3; 2341 | variable_item_set_current_value_index(app->item_sf, config_sf_index); 2342 | variable_item_set_current_value_text(app->item_sf, config_sf_names[config_sf_index]); 2343 | 2344 | // cr 2345 | app->item_cr = variable_item_list_add( 2346 | app->variable_item_list_config, 2347 | config_cr_label, 2348 | COUNT_OF(config_cr_values), 2349 | lora_config_cr_change, 2350 | app); 2351 | uint8_t config_cr_index = 0; 2352 | variable_item_set_current_value_index(app->item_cr, config_cr_index); 2353 | variable_item_set_current_value_text(app->item_cr, config_cr_names[config_cr_index]); 2354 | 2355 | // pl 2356 | app->item_pl = variable_item_list_add( 2357 | app->variable_item_list_config, 2358 | config_pl_label, 2359 | COUNT_OF(config_pl_values), 2360 | lora_config_pl_change, 2361 | app); 2362 | uint8_t config_pl_index = 0; 2363 | variable_item_set_current_value_index(app->item_pl, config_pl_index); 2364 | variable_item_set_current_value_text(app->item_pl, config_pl_names[config_pl_index]); 2365 | 2366 | // sw 2367 | app->item_sw = variable_item_list_add( 2368 | app->variable_item_list_config, 2369 | config_sw_label, 2370 | COUNT_OF(config_sw_values), 2371 | lora_config_sw_change, 2372 | app); 2373 | uint8_t config_sw_index = 0; 2374 | variable_item_set_current_value_index(app->item_sw, config_sw_index); 2375 | variable_item_set_current_value_text(app->item_sw, config_sw_names[config_sw_index]); 2376 | 2377 | // Payload length 2378 | item = variable_item_list_add( 2379 | app->variable_item_list_config, 2380 | "Payload length", 2381 | 64, 2382 | lora_app_config_set_payload_length, 2383 | app); 2384 | variable_item_set_current_value_index(item, 16); 2385 | variable_item_set_current_value_text(item, "16"); 2386 | 2387 | // Header Type 2388 | app->item_header_type = variable_item_list_add( 2389 | app->variable_item_list_config, 2390 | config_header_type_label, 2391 | COUNT_OF(config_header_type_values), 2392 | lora_config_header_type_change, 2393 | app); 2394 | uint8_t config_header_type_index = 0; 2395 | variable_item_set_current_value_index(app->item_header_type, config_header_type_index); 2396 | variable_item_set_current_value_text( 2397 | app->item_header_type, config_header_type_names[config_header_type_index]); 2398 | 2399 | // CRC 2400 | app->item_crc = variable_item_list_add( 2401 | app->variable_item_list_config, 2402 | config_crc_label, 2403 | COUNT_OF(config_crc_values), 2404 | lora_config_crc_change, 2405 | app); 2406 | uint8_t config_crc_index = 0; 2407 | variable_item_set_current_value_index(app->item_crc, config_crc_index); 2408 | variable_item_set_current_value_text(app->item_crc, config_crc_names[config_crc_index]); 2409 | 2410 | // Inverted IQ 2411 | app->item_iq = variable_item_list_add( 2412 | app->variable_item_list_config, 2413 | config_iq_label, 2414 | COUNT_OF(config_iq_values), 2415 | lora_config_iq_change, 2416 | app); 2417 | uint8_t config_iq_index = 0; 2418 | variable_item_set_current_value_index(app->item_iq, config_iq_index); 2419 | variable_item_set_current_value_text(app->item_iq, config_iq_names[config_iq_index]); 2420 | 2421 | // Frequency Plan 2422 | app->item_region = variable_item_list_add( 2423 | app->variable_item_list_lorawan, 2424 | config_region_label, 2425 | COUNT_OF(config_region_values), 2426 | lora_config_region_change, 2427 | app); 2428 | uint8_t config_region_index = 1; 2429 | variable_item_set_current_value_index(app->item_region, config_region_index); 2430 | variable_item_set_current_value_text( 2431 | app->item_region, config_region_names[config_region_index]); 2432 | 2433 | // Data Rate 2434 | app->item_us_dr = variable_item_list_add( 2435 | app->variable_item_list_lorawan, 2436 | config_us_dr_label, 2437 | COUNT_OF(config_us_dr_values), 2438 | lora_config_us_dr_change, 2439 | app); 2440 | uint8_t config_us_dr_index = 0; 2441 | variable_item_set_current_value_index(app->item_us_dr, config_us_dr_index); 2442 | variable_item_set_current_value_text(app->item_us_dr, config_us_dr_names[config_us_dr_index]); 2443 | 2444 | char text_buf[11] = {0}; 2445 | 2446 | // Uplink Channel 125K 2447 | app->item_us915_ul_channels_125k = variable_item_list_add( 2448 | app->variable_item_list_lorawan, 2449 | config_us915_ul_channels_125k_label, 2450 | COUNT_OF(config_us915_ul_channels_125k), 2451 | lora_config_us915_ul_channels_125k_change, 2452 | app); 2453 | uint8_t config_us915_ul_channels_125k_index = 0; 2454 | variable_item_set_current_value_index( 2455 | app->item_us915_ul_channels_125k, config_us915_ul_channels_125k_index); 2456 | 2457 | snprintf( 2458 | text_buf, 2459 | sizeof(text_buf), 2460 | "%3lu.%1lu MHz", 2461 | config_us915_ul_channels_125k[config_us915_ul_channels_125k_index] / 1000000, 2462 | (config_us915_ul_channels_125k[config_us915_ul_channels_125k_index] % 1000000) / 100000); 2463 | variable_item_set_current_value_text(app->item_us915_ul_channels_125k, text_buf); 2464 | 2465 | // Uplink Channel 500K 2466 | app->item_us915_ul_channels_500k = variable_item_list_add( 2467 | app->variable_item_list_lorawan, 2468 | config_us915_ul_channels_500k_label, 2469 | COUNT_OF(config_us915_ul_channels_500k), 2470 | lora_config_us915_ul_channels_500k_change, 2471 | app); 2472 | uint8_t config_us915_ul_channels_500k_index = 0; 2473 | variable_item_set_current_value_index( 2474 | app->item_us915_ul_channels_500k, config_us915_ul_channels_500k_index); 2475 | 2476 | snprintf( 2477 | text_buf, 2478 | sizeof(text_buf), 2479 | "%3lu.%1lu MHz", 2480 | config_us915_ul_channels_500k[config_us915_ul_channels_500k_index] / 1000000, 2481 | (config_us915_ul_channels_500k[config_us915_ul_channels_500k_index] % 1000000) / 100000); 2482 | variable_item_set_current_value_text(app->item_us915_ul_channels_500k, text_buf); 2483 | 2484 | // Downlink Channel 500K 2485 | app->item_us915_dl_channels_500k = variable_item_list_add( 2486 | app->variable_item_list_lorawan, 2487 | config_us915_dl_channels_500k_label, 2488 | COUNT_OF(config_us915_dl_channels_500k), 2489 | lora_config_us915_dl_channels_500k_change, 2490 | app); 2491 | uint8_t config_us915_dl_channels_500k_index = 0; 2492 | variable_item_set_current_value_index( 2493 | app->item_us915_dl_channels_500k, config_us915_dl_channels_500k_index); 2494 | 2495 | snprintf( 2496 | text_buf, 2497 | sizeof(text_buf), 2498 | "%3lu.%1lu MHz", 2499 | config_us915_dl_channels_500k[config_us915_dl_channels_500k_index] / 1000000, 2500 | (config_us915_dl_channels_500k[config_us915_dl_channels_500k_index] % 1000000) / 100000); 2501 | variable_item_set_current_value_text(app->item_us915_dl_channels_500k, text_buf); 2502 | 2503 | // Meshtastic Presets 2504 | app->item_meshtastic = variable_item_list_add( 2505 | app->variable_item_list_meshtastic, 2506 | config_meshtastic_label, 2507 | COUNT_OF(config_meshtastic_values), 2508 | lora_config_meshtastic_change, 2509 | app); 2510 | uint8_t config_meshtastic_index = 1; 2511 | variable_item_set_current_value_index(app->item_meshtastic, config_meshtastic_index); 2512 | variable_item_set_current_value_text( 2513 | app->item_meshtastic, config_meshtastic_names[config_meshtastic_index]); 2514 | 2515 | variable_item_list_set_enter_callback( 2516 | app->variable_item_list_config, lora_setting_item_clicked, app); 2517 | 2518 | view_set_previous_callback( 2519 | variable_item_list_get_view(app->variable_item_list_config), 2520 | lora_navigation_submenu_callback); 2521 | 2522 | view_set_previous_callback( 2523 | variable_item_list_get_view(app->variable_item_list_lorawan), 2524 | lora_navigation_submenu_callback); 2525 | 2526 | view_set_previous_callback( 2527 | variable_item_list_get_view(app->variable_item_list_meshtastic), 2528 | lora_navigation_submenu_callback); 2529 | 2530 | view_dispatcher_add_view( 2531 | app->view_dispatcher, 2532 | LoRaViewConfigure, 2533 | variable_item_list_get_view(app->variable_item_list_config)); 2534 | 2535 | view_dispatcher_add_view( 2536 | app->view_dispatcher, 2537 | LoRaViewLoRaWAN, 2538 | variable_item_list_get_view(app->variable_item_list_lorawan)); 2539 | 2540 | view_dispatcher_add_view( 2541 | app->view_dispatcher, 2542 | LoRaViewMeshtastic, 2543 | variable_item_list_get_view(app->variable_item_list_meshtastic)); 2544 | 2545 | app->view_sniffer = view_alloc(); 2546 | view_set_draw_callback(app->view_sniffer, lora_view_sniffer_draw_callback); 2547 | view_set_input_callback(app->view_sniffer, lora_view_sniffer_input_callback); 2548 | view_set_previous_callback(app->view_sniffer, lora_navigation_submenu_callback); 2549 | view_set_enter_callback(app->view_sniffer, lora_view_sniffer_enter_callback); 2550 | view_set_exit_callback(app->view_sniffer, lora_view_sniffer_exit_callback); 2551 | view_set_context(app->view_sniffer, app); 2552 | view_set_custom_callback(app->view_sniffer, lora_view_sniffer_custom_event_callback); 2553 | view_allocate_model(app->view_sniffer, ViewModelTypeLockFree, sizeof(LoRaSnifferModel)); 2554 | LoRaSnifferModel* model_s = view_get_model(app->view_sniffer); 2555 | 2556 | model_s->config_freq_name = config_freq_name; 2557 | model_s->config_bw_index = config_bw_index; 2558 | model_s->config_sf_index = config_sf_index; 2559 | model_s->config_cr_index = config_sf_index; 2560 | 2561 | model_s->config_header_type_index = config_header_type_index; 2562 | model_s->config_crc_index = config_crc_index; 2563 | model_s->config_iq_index = config_iq_index; 2564 | 2565 | model_s->config_meshtastic_index = config_meshtastic_index; 2566 | 2567 | model_s->x = 0; 2568 | 2569 | model_s->dialogs_rx = furi_record_open(RECORD_DIALOGS); 2570 | model_s->storage_rx = furi_record_open(RECORD_STORAGE); 2571 | model_s->file_rx = storage_file_alloc(model_s->storage_rx); 2572 | 2573 | view_dispatcher_add_view(app->view_dispatcher, LoRaViewSniffer, app->view_sniffer); 2574 | 2575 | app->view_transmitter = view_alloc(); 2576 | view_set_draw_callback(app->view_transmitter, lora_view_transmitter_draw_callback); 2577 | view_set_input_callback(app->view_transmitter, lora_view_transmitter_input_callback); 2578 | view_set_previous_callback(app->view_transmitter, lora_navigation_submenu_callback); 2579 | view_set_enter_callback(app->view_transmitter, lora_view_transmitter_enter_callback); 2580 | view_set_exit_callback(app->view_transmitter, lora_view_transmitter_exit_callback); 2581 | view_set_context(app->view_transmitter, app); 2582 | view_set_custom_callback(app->view_transmitter, lora_view_transmitter_custom_event_callback); 2583 | view_allocate_model( 2584 | app->view_transmitter, ViewModelTypeLockFree, sizeof(LoRaTransmitterModel)); 2585 | LoRaTransmitterModel* model_t = view_get_model(app->view_transmitter); 2586 | 2587 | model_t->x = 0; 2588 | 2589 | model_t->dialogs_tx = furi_record_open(RECORD_DIALOGS); 2590 | model_t->storage_tx = furi_record_open(RECORD_STORAGE); 2591 | model_t->file_tx = storage_file_alloc(model_t->storage_tx); 2592 | 2593 | makePaths(app); 2594 | 2595 | view_dispatcher_add_view(app->view_dispatcher, LoraViewTransmitter, app->view_transmitter); 2596 | 2597 | app->widget_about = widget_alloc(); 2598 | widget_add_text_scroll_element( 2599 | app->widget_about, 2600 | 0, 2601 | 0, 2602 | 128, 2603 | 64, 2604 | "This is a LoRa sniffer app.\n---\nBrought to you by\nElectronicCats!\n\nauthor: @pigpen\nhttps://github.com/ElectronicCats/flipper-SX1262-LoRa"); 2605 | view_set_previous_callback( 2606 | widget_get_view(app->widget_about), lora_navigation_submenu_callback); 2607 | view_dispatcher_add_view( 2608 | app->view_dispatcher, LoRaViewAbout, widget_get_view(app->widget_about)); 2609 | 2610 | app->notifications = furi_record_open(RECORD_NOTIFICATION); 2611 | 2612 | serial_open_port(); // Second serial port USB active 2613 | 2614 | #ifdef BACKLIGHT_ON 2615 | notification_message(app->notifications, &sequence_display_backlight_enforce_on); 2616 | #endif 2617 | 2618 | return app; 2619 | } 2620 | 2621 | /** 2622 | * @brief Free the LoRa application. 2623 | * @details This function frees the LoRa application resources. 2624 | * @param app The LoRa application object. 2625 | */ 2626 | static void lora_app_free(LoRaApp* app) { 2627 | #ifdef BACKLIGHT_ON 2628 | notification_message(app->notifications, &sequence_display_backlight_enforce_auto); 2629 | #endif 2630 | furi_record_close(RECORD_NOTIFICATION); 2631 | 2632 | LoRaSnifferModel* model = view_get_model(app->view_sniffer); 2633 | storage_file_free(model->file_rx); 2634 | furi_record_close(RECORD_STORAGE); 2635 | furi_record_close(RECORD_DIALOGS); 2636 | 2637 | LoRaTransmitterModel* model_t = view_get_model(app->view_transmitter); 2638 | storage_file_free(model_t->file_tx); 2639 | furi_record_close(RECORD_STORAGE); 2640 | furi_record_close(RECORD_DIALOGS); 2641 | 2642 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewFrequencyInput); 2643 | text_input_free(app->frequency_input); 2644 | 2645 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewByteInput); 2646 | byte_input_free(app->byte_input); 2647 | 2648 | free(app->temp_buffer); 2649 | free(app->byte_buffer); 2650 | 2651 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewAbout); 2652 | widget_free(app->widget_about); 2653 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewSniffer); 2654 | view_free(app->view_sniffer); 2655 | view_dispatcher_remove_view(app->view_dispatcher, LoraViewTransmitter); 2656 | view_free(app->view_transmitter); 2657 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewConfigure); 2658 | variable_item_list_free(app->variable_item_list_config); 2659 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewLoRaWAN); 2660 | variable_item_list_free(app->variable_item_list_lorawan); 2661 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewMeshtastic); 2662 | variable_item_list_free(app->variable_item_list_meshtastic); 2663 | view_dispatcher_remove_view(app->view_dispatcher, LoRaViewSubmenu); 2664 | submenu_free(app->submenu); 2665 | view_dispatcher_free(app->view_dispatcher); 2666 | furi_record_close(RECORD_GUI); 2667 | 2668 | serial_close_port(); // USB normal 2669 | 2670 | free(app); 2671 | } 2672 | 2673 | /** 2674 | * @brief Main function for LoRa application. 2675 | * @details This function is the entry point for the LoRa application. It should be defined in 2676 | * application.fam as the entry_point setting. 2677 | * @param _p Input parameter - unused 2678 | * @return 0 - Success 2679 | */ 2680 | int32_t main_lora_app(void* _p) { 2681 | UNUSED(_p); 2682 | 2683 | static FuriHalSpiBusHandle spi_handle; 2684 | const FuriHalSpiBusHandle* spi; 2685 | 2686 | memcpy(&spi_handle, &furi_hal_spi_bus_handle_external, sizeof(FuriHalSpiBusHandle)); 2687 | spi_handle.cs = &gpio_ext_pc0; 2688 | spi = &spi_handle; 2689 | 2690 | furi_hal_spi_bus_handle_init(spi); 2691 | 2692 | abandone(); 2693 | 2694 | if(!begin()) { 2695 | DialogsApp* dialogs_msg = furi_record_open(RECORD_DIALOGS); 2696 | DialogMessage* message = dialog_message_alloc(); 2697 | dialog_message_set_text( 2698 | message, 2699 | "Error!\nSubGHz add-on module failed to start\n\nCheck that the module is plugged in", 2700 | 0, 2701 | 0, 2702 | AlignLeft, 2703 | AlignTop); 2704 | dialog_message_show(dialogs_msg, message); 2705 | dialog_message_free(message); 2706 | furi_record_close(RECORD_DIALOGS); 2707 | return 0; 2708 | } 2709 | 2710 | LoRaApp* app = lora_app_alloc(); 2711 | 2712 | app->packetPreamble = 0x0010; 2713 | app->packetHeaderType = 0x00; 2714 | app->packetPayloadLength = 0xFF; 2715 | app->packetCRC = 0x00; 2716 | app->packetInvertIQ = 0x00; 2717 | 2718 | view_dispatcher_run(app->view_dispatcher); 2719 | 2720 | lora_app_free(app); 2721 | 2722 | furi_hal_spi_bus_handle_deinit(spi); 2723 | 2724 | memcpy(&spi_handle, &furi_hal_spi_bus_handle_external, sizeof(FuriHalSpiBusHandle)); 2725 | spi_handle.cs = &gpio_ext_pa4; 2726 | spi = &spi_handle; 2727 | 2728 | // Typically when a pin is no longer in use, it is set to analog mode. 2729 | furi_hal_gpio_init_simple(pin_led, GpioModeAnalog); 2730 | 2731 | return 0; 2732 | } 2733 | --------------------------------------------------------------------------------