├── extend_range_10px.png ├── README.md ├── application.fam └── extend_range.c /extend_range_10px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maede97/flipperzero-extend-range/HEAD/extend_range_10px.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flipperzero-extend-range 2 | Extend SubGHz Range 3 | 4 | Instead of manually changing the configuration of the subghz module (to enable the full potential), this application does it for you. 5 | 6 | # USE AT YOUR OWN RISK. ONLY FOR EDUCATIONAL PURPOSES. 7 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="ExtendRange", 3 | name="Extend Range", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="extend_range_app", 6 | cdefines=["EXTEND_RANGE"], 7 | requires=[ 8 | "gui", 9 | "storage", 10 | ], 11 | stack_size=1 * 1024, 12 | order=20, 13 | fap_icon="extend_range_10px.png", 14 | fap_category="Misc", 15 | ) 16 | -------------------------------------------------------------------------------- /extend_range.c: -------------------------------------------------------------------------------- 1 | /* 2 | This application let's you unlock the extended range of the Sub-GHz module. 3 | Use at your own risk! 4 | 5 | @author: maede97 6 | 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #define PATH_SUBGHZ EXT_PATH("subghz") "/assets/extend_range.txt" 17 | #define KEY_EXTEND_RANGE "use_ext_range_at_own_risk" 18 | #define KEY_IGNORE_DEFAULT "ignore_default_tx_region" 19 | 20 | struct UserSelection { 21 | bool extend_range; 22 | bool ignore_default; 23 | unsigned int current_selection; // 0 - extend_range, 1 - ignore_default 24 | }; 25 | 26 | struct UserSelection global_user_selection = { 27 | .extend_range = false, 28 | .ignore_default = false, 29 | .current_selection = 0, 30 | }; 31 | int global_ret_val = -1; 32 | 33 | static void extend_range_close_file(FlipperFormat* file) { 34 | if(file == NULL) { 35 | furi_record_close(RECORD_STORAGE); 36 | return; 37 | } 38 | flipper_format_file_close(file); 39 | flipper_format_free(file); 40 | furi_record_close(RECORD_STORAGE); 41 | } 42 | 43 | static FlipperFormat* extend_range_open_file() { 44 | Storage* storage = furi_record_open(RECORD_STORAGE); 45 | FlipperFormat* file = flipper_format_file_alloc(storage); 46 | if(storage_common_stat(storage, PATH_SUBGHZ, NULL) == FSE_OK) { 47 | if(!flipper_format_file_open_existing(file, PATH_SUBGHZ)) { 48 | extend_range_close_file(file); 49 | return NULL; 50 | } 51 | } else { 52 | return NULL; 53 | } 54 | return file; 55 | } 56 | 57 | void extend_range_draw_callback(Canvas* canvas, void* ctx) { 58 | UNUSED(ctx); 59 | canvas_clear(canvas); 60 | 61 | canvas_set_font(canvas, FontPrimary); 62 | canvas_draw_str(canvas, 2, 10, "Extend Range Sub-GHz"); 63 | 64 | canvas_set_font(canvas, FontSecondary); 65 | canvas_draw_str(canvas, 2, 20, "Back -> Exit, OK -> Apply"); 66 | 67 | // Extend Range 68 | 69 | if(global_user_selection.extend_range) 70 | canvas_draw_str(canvas, 2, 30, "< Risky Range: Yes >"); 71 | else 72 | canvas_draw_str(canvas, 2, 30, "< Risky Range: No >"); 73 | if(global_user_selection.current_selection == 0) { 74 | canvas_draw_line(canvas, 2, 31, 125, 31); 75 | } 76 | 77 | // Ignore Default 78 | if(global_user_selection.ignore_default) 79 | canvas_draw_str(canvas, 2, 40, "< Ignore Region: Yes >"); 80 | else 81 | canvas_draw_str(canvas, 2, 40, "< Ignore Region: No >"); 82 | if(global_user_selection.current_selection == 1) { 83 | canvas_draw_line(canvas, 2, 41, 125, 41); 84 | } 85 | 86 | // Current status 87 | canvas_draw_str(canvas, 2, 50, "Current Status: "); 88 | switch(global_ret_val) { 89 | case -1: 90 | canvas_draw_str(canvas, 2, 60, "Make your selection!"); 91 | break; 92 | case 0: 93 | canvas_draw_str(canvas, 2, 60, "Success. Reboot now."); 94 | break; 95 | case 1: 96 | canvas_draw_str(canvas, 2, 60, "File not found"); 97 | break; 98 | case 2: 99 | canvas_draw_str(canvas, 2, 60, "Failed to update 'risky ranges'"); 100 | break; 101 | case 3: 102 | canvas_draw_str(canvas, 2, 60, "Failed to update 'ignore region'"); 103 | break; 104 | default: 105 | canvas_draw_str(canvas, 2, 60, "Unknown error"); 106 | break; 107 | } 108 | } 109 | 110 | void extend_range_input_callback(InputEvent* input_event, void* ctx) { 111 | furi_assert(ctx); 112 | FuriMessageQueue* event_queue = ctx; 113 | furi_message_queue_put(event_queue, input_event, FuriWaitForever); 114 | } 115 | 116 | void extend_range_read_defaults() { 117 | FlipperFormat* file = extend_range_open_file(); 118 | if(file == NULL) { 119 | return; 120 | } 121 | 122 | FuriString* extend_range_; 123 | FuriString* ignore_default_; 124 | extend_range_ = furi_string_alloc(); 125 | ignore_default_ = furi_string_alloc(); 126 | 127 | if(!flipper_format_read_string(file, KEY_EXTEND_RANGE, extend_range_)) { 128 | extend_range_close_file(file); 129 | return; 130 | } 131 | if(!flipper_format_read_string(file, KEY_IGNORE_DEFAULT, ignore_default_)) { 132 | extend_range_close_file(file); 133 | return; 134 | } 135 | 136 | if(furi_string_cmp_str(extend_range_, "true") == 0) { 137 | global_user_selection.extend_range = true; 138 | } else { 139 | global_user_selection.extend_range = false; 140 | } 141 | 142 | if(furi_string_cmp_str(ignore_default_, "true") == 0) { 143 | global_user_selection.ignore_default = true; 144 | } else { 145 | global_user_selection.ignore_default = false; 146 | } 147 | 148 | extend_range_close_file(file); 149 | } 150 | 151 | int apply_user_selection() { 152 | FlipperFormat* file = extend_range_open_file(); 153 | if(file == NULL) { 154 | return 1; 155 | } 156 | 157 | if(!flipper_format_update_string_cstr( 158 | file, KEY_EXTEND_RANGE, global_user_selection.extend_range ? "true" : "false")) { 159 | extend_range_close_file(file); 160 | return 2; 161 | } 162 | if(!flipper_format_update_string_cstr( 163 | file, KEY_IGNORE_DEFAULT, global_user_selection.ignore_default ? "true" : "false")) { 164 | extend_range_close_file(file); 165 | return 3; 166 | } 167 | 168 | extend_range_close_file(file); 169 | return 0; 170 | } 171 | 172 | void handle_key(InputEvent* input_event) { 173 | if(input_event->type != InputTypePress) { 174 | return; 175 | } 176 | 177 | switch(input_event->key) { 178 | case InputKeyBack: 179 | return; 180 | case InputKeyOk: 181 | global_ret_val = apply_user_selection(); 182 | return; 183 | case InputKeyUp: 184 | global_user_selection.current_selection = 185 | (global_user_selection.current_selection + 1) % 2; 186 | return; 187 | case InputKeyDown: 188 | global_user_selection.current_selection = 189 | (global_user_selection.current_selection + 1) % 2; 190 | return; 191 | case InputKeyLeft: 192 | if(global_user_selection.current_selection == 0) { 193 | global_user_selection.extend_range = !global_user_selection.extend_range; 194 | } else { 195 | global_user_selection.ignore_default = !global_user_selection.ignore_default; 196 | } 197 | return; 198 | case InputKeyRight: 199 | if(global_user_selection.current_selection == 0) { 200 | global_user_selection.extend_range = !global_user_selection.extend_range; 201 | } else { 202 | global_user_selection.ignore_default = !global_user_selection.ignore_default; 203 | } 204 | return; 205 | default: 206 | return; 207 | } 208 | } 209 | 210 | int32_t extend_range_app(void* p) { 211 | UNUSED(p); 212 | FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); 213 | 214 | extend_range_read_defaults(); 215 | 216 | // Configure view port & callbacks 217 | ViewPort* view_port = view_port_alloc(); 218 | view_port_draw_callback_set(view_port, extend_range_draw_callback, NULL); 219 | view_port_input_callback_set(view_port, extend_range_input_callback, event_queue); 220 | 221 | Gui* gui = furi_record_open(RECORD_GUI); 222 | gui_add_view_port(gui, view_port, GuiLayerFullscreen); 223 | 224 | InputEvent event; 225 | while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { 226 | if(event.type == InputTypeShort && event.key == InputKeyBack) break; 227 | handle_key(&event); 228 | } 229 | 230 | gui_remove_view_port(gui, view_port); 231 | view_port_free(view_port); 232 | furi_message_queue_free(event_queue); 233 | 234 | furi_record_close(RECORD_GUI); 235 | 236 | return 0; 237 | } --------------------------------------------------------------------------------