Merge branch 'dev' of https://github.com/ClaraCrazy/Flipper-Xtreme into fix-bad_kb_bt-flipper_app-conflict

This commit is contained in:
Willy-JL
2023-02-19 01:12:46 +00:00
251 changed files with 3770 additions and 8173 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

+3
View File
@@ -28,6 +28,9 @@
[submodule "lib/cxxheaderparser"]
path = lib/cxxheaderparser
url = https://github.com/robotpy/cxxheaderparser.git
[submodule "applications/plugins/subbrute"]
path = applications/plugins/subbrute
url = https://github.com/derskythe/flipperzero-subbrute.git
[submodule "applications/plugins/dap_link/lib/free-dap"]
path = applications/plugins/dap_link/lib/free-dap
url = https://github.com/ataradov/free-dap.git
+2 -2
View File
@@ -1,6 +1,6 @@
App(
appid="UART_Echo",
name="UART Echo",
name="[GPIO] UART Echo",
apptype=FlipperAppType.PLUGIN,
entry_point="uart_echo_app",
cdefines=["APP_UART_ECHO"],
@@ -8,5 +8,5 @@ App(
stack_size=2 * 1024,
order=70,
fap_icon="uart_10px.png",
fap_category="Misc",
fap_category="GPIO",
)
+17
View File
@@ -37,9 +37,26 @@ static void bad_kb_load_settings(BadKbApp* app) {
!storage_file_eof(settings_file) && !isspace(chr)) {
furi_string_push_back(app->keyboard_layout, chr);
}
} else {
furi_string_reset(app->keyboard_layout);
}
storage_file_close(settings_file);
storage_file_free(settings_file);
if(!furi_string_empty(app->keyboard_layout)) {
Storage* fs_api = furi_record_open(RECORD_STORAGE);
FileInfo layout_file_info;
FS_Error file_check_err = storage_common_stat(
fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
furi_record_close(RECORD_STORAGE);
if(file_check_err != FSE_OK) {
furi_string_reset(app->keyboard_layout);
return;
}
if(layout_file_info.size != 256) {
furi_string_reset(app->keyboard_layout);
}
}
}
static void bad_kb_save_settings(BadKbApp* app) {
+1 -1
View File
@@ -17,7 +17,7 @@
#include "views/bad_kb_view.h"
#define BAD_KB_APP_BASE_FOLDER ANY_PATH("badkb")
#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/layouts"
#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/assets/layouts"
#define BAD_KB_APP_SCRIPT_EXTENSION ".txt"
#define BAD_KB_APP_LAYOUT_EXTENSION ".kl"
+1 -1
View File
@@ -949,7 +949,7 @@ void bad_kb_script_set_keyboard_layout(BadKbScript* bad_kb, FuriString* layout_p
}
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
if(!furi_string_empty(layout_path)) {
if(!furi_string_empty(layout_path)) { //-V1051
furi_string_set(bad_kb->keyboard_layout, layout_path);
if(storage_file_open(
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
@@ -3,8 +3,6 @@
#include "furi_hal_usb.h"
#include <storage/storage.h>
#define KEYBOARD_FOLDER "/ext/badkb/layouts"
static bool bad_kb_layout_select(BadKbApp* bad_kb) {
furi_assert(bad_kb);
@@ -19,7 +17,8 @@ static bool bad_kb_layout_select(BadKbApp* bad_kb) {
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, BAD_KB_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
browser_options.base_path = KEYBOARD_FOLDER;
browser_options.base_path = BAD_KB_APP_PATH_LAYOUT_FOLDER;
browser_options.skip_assets = false;
// Input events and views are managed by file_browser
bool res = dialog_file_browser_show(
@@ -52,7 +52,8 @@ void infrared_scene_universal_on_enter(void* context) {
infrared_scene_universal_submenu_callback,
context);
submenu_set_selected_item(submenu, 0);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneUniversal));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
}
@@ -117,16 +117,15 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) {
// First stage: coarse scan
for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) {
if(furi_hal_subghz_is_frequency_valid(
subghz_setting_get_frequency(instance->setting, i)) &&
uint32_t current_frequnecy = subghz_setting_get_frequency(instance->setting, i);
if(furi_hal_subghz_is_frequency_valid(current_frequnecy) &&
(current_frequnecy != 467750000) &&
!((furi_hal_subghz.radio_type == SubGhzRadioExternal) &&
(subghz_setting_get_frequency(instance->setting, i) >= 311900000 &&
subghz_setting_get_frequency(instance->setting, i) <= 312200000))) {
(current_frequnecy >= 311900000 && current_frequnecy <= 312200000))) {
furi_hal_spi_acquire(furi_hal_subghz.spi_bus_handle);
cc1101_switch_to_idle(furi_hal_subghz.spi_bus_handle);
frequency = cc1101_set_frequency(
furi_hal_subghz.spi_bus_handle,
subghz_setting_get_frequency(instance->setting, i));
frequency =
cc1101_set_frequency(furi_hal_subghz.spi_bus_handle, current_frequnecy);
cc1101_calibrate(furi_hal_subghz.spi_bus_handle);
do {
@@ -331,4 +330,4 @@ void subghz_frequency_analyzer_worker_set_trigger_level(
float subghz_frequency_analyzer_worker_get_trigger_level(SubGhzFrequencyAnalyzerWorker* instance) {
return instance->trigger_level;
}
}
@@ -13,7 +13,7 @@ const char* const radio_modules_variables_text[] = {
#define DEBUG_P_COUNT 2
const char* const debug_pin_text[DEBUG_P_COUNT] = {
"OFF",
"A7",
"17(1W)",
};
static void subghz_scene_ext_module_changed(VariableItem* item) {
+2 -2
View File
@@ -598,7 +598,7 @@ void subghz_hopper_update(SubGhz* subghz) {
void subghz_speaker_on(SubGhz* subghz) {
if(subghz->txrx->debug_pin_state) {
furi_hal_subghz_set_async_mirror_pin(&gpio_ext_pa7);
furi_hal_subghz_set_async_mirror_pin(&ibutton_gpio);
}
if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
@@ -643,7 +643,7 @@ void subghz_speaker_mute(SubGhz* subghz) {
void subghz_speaker_unmute(SubGhz* subghz) {
if(subghz->txrx->debug_pin_state) {
furi_hal_subghz_set_async_mirror_pin(&gpio_ext_pa7);
furi_hal_subghz_set_async_mirror_pin(&ibutton_gpio);
}
if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
if(furi_hal_speaker_is_mine()) {
+10 -2
View File
@@ -261,13 +261,21 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
if(model->history_item == 0) {
if(model->mode == SubGhzViewReceiverModeLive) {
canvas_draw_icon(canvas, 0, 0, XTREME_ASSETS()->I_Scanning_123x52);
canvas_draw_icon(
canvas,
0,
0,
furi_hal_subghz_get_radio_type() ? &I_Fishing_123x52 : XTREME_ASSETS()->I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning...");
//canvas_draw_line(canvas, 46, 51, 125, 51);
canvas_set_font(canvas, FontSecondary);
} else {
canvas_draw_icon(canvas, 0, 0, XTREME_ASSETS()->I_Scanning_123x52);
canvas_draw_icon(
canvas,
0,
0,
furi_hal_subghz_get_radio_type() ? &I_Fishing_123x52 : XTREME_ASSETS()->I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Decoding...");
canvas_set_font(canvas, FontSecondary);
@@ -21,13 +21,12 @@
#define MAX_HISTORY 4
static const uint32_t subghz_frequency_list[] = {
300000000, 302757000, 303875000, 304250000, 307000000, 307500000, 307800000,
309000000, 310000000, 312000000, 312100000, 313000000, 313850000, 314000000,
314350000, 314980000, 315000000, 318000000, 330000000, 345000000, 348000000,
350000000, 387000000, 390000000, 418000000, 433075000, 433220000, 433420000,
433657070, 433889000, 433920000, 434075000, 434176948, 434390000, 434420000,
434775000, 438900000, 440175000, 464000000, 467750000, 779000000, 868350000,
868400000, 868800000, 868950000, 906400000, 915000000, 925000000, 928000000};
300000000, 302757000, 303875000, 304250000, 307000000, 307500000, 307800000, 309000000,
310000000, 312000000, 312100000, 313000000, 313850000, 314000000, 314350000, 314980000,
315000000, 318000000, 330000000, 345000000, 348000000, 350000000, 387000000, 390000000,
418000000, 433075000, 433220000, 433420000, 433657070, 433889000, 433920000, 434075000,
434176948, 434390000, 434420000, 434775000, 438900000, 440175000, 464000000, 779000000,
868350000, 868400000, 868800000, 868950000, 906400000, 915000000, 925000000, 928000000};
typedef enum {
SubGhzFrequencyAnalyzerStatusIDLE,
@@ -522,13 +522,14 @@ static bool unirfremix_send_sub(UniRFRemix* app, FlipperFormat* fff_data) {
furi_hal_subghz_reset();
furi_hal_subghz_idle();
furi_hal_subghz_load_custom_preset(app->txpreset->data);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_idle();
furi_hal_subghz_set_frequency_and_path(app->txpreset->frequency);
furi_hal_gpio_write(&gpio_cc1101_g0, false);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false);
furi_hal_gpio_init(
furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
if(!furi_hal_subghz_tx()) {
FURI_LOG_E(TAG, "Sending not allowed");
@@ -5,6 +5,7 @@
#include <gui/view.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
#define TAG "Arkanoid"
@@ -397,6 +398,9 @@ int32_t arkanoid_game_app(void* p) {
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
GameEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
@@ -9,7 +9,7 @@ App(
"dialogs",
],
stack_size=1 * 1024,
order=250,
order=50,
fap_icon="barcode_10px.png",
fap_category="Misc",
)
)
@@ -1,8 +1,3 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include "barcode_generator.h"
static BarcodeType* barcodeTypes[NUMBER_OF_BARCODE_TYPES];
@@ -103,9 +98,9 @@ int get_menu_text_location(int index) {
}
int get_barcode_max_index(PluginState* plugin_state) {
return plugin_state->doParityCalculation ?
barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits - 1 :
barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits;
return plugin_state->barcode_state.doParityCalculation ?
barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]->numberOfDigits - 1 :
barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]->numberOfDigits;
}
int calculate_check_digit(PluginState* plugin_state, BarcodeType* type) {
@@ -114,12 +109,12 @@ int calculate_check_digit(PluginState* plugin_state, BarcodeType* type) {
int checkDigitEven = 0;
//add all odd positions. Confusing because 0index
for(int i = 0; i < type->numberOfDigits - 1; i += 2) {
checkDigitOdd += plugin_state->barcodeNumeral[i];
checkDigitOdd += plugin_state->barcode_state.barcodeNumeral[i];
}
//add all even positions to above. Confusing because 0index
for(int i = 1; i < type->numberOfDigits - 1; i += 2) {
checkDigitEven += plugin_state->barcodeNumeral[i];
checkDigitEven += plugin_state->barcode_state.barcodeNumeral[i];
}
if(type->bartype == BarTypeEAN13) {
@@ -152,7 +147,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
canvas, 64, get_menu_text_location(2), AlignCenter, AlignCenter, "Parity?");
canvas_draw_frame(canvas, 83, get_menu_text_location(2) - 3, 6, 6);
if(plugin_state->doParityCalculation == true) {
if(plugin_state->barcode_state.doParityCalculation == true) {
canvas_draw_box(canvas, 85, get_menu_text_location(2) - 1, 2, 2);
}
canvas_draw_str_aligned(
@@ -161,14 +156,14 @@ static void render_callback(Canvas* const canvas, void* ctx) {
get_menu_text_location(3),
AlignCenter,
AlignCenter,
(barcodeTypes[plugin_state->barcodeTypeIndex])->name);
(barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex])->name);
canvas_draw_disc(
canvas,
40,
get_menu_text_location(plugin_state->menuIndex) - 1,
2); //draw menu cursor
} else {
BarcodeType* type = barcodeTypes[plugin_state->barcodeTypeIndex];
BarcodeType* type = barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex];
//start saftey
canvas_set_color(canvas, ColorBlack);
@@ -181,13 +176,13 @@ static void render_callback(Canvas* const canvas, void* ctx) {
startpos++;
draw_digit(
canvas,
plugin_state->barcodeNumeral[0],
plugin_state->barcode_state.barcodeNumeral[0],
BarEncodingTypeRight,
get_digit_position(0, barcodeTypes[plugin_state->barcodeTypeIndex]),
get_digit_position(0, barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]),
false);
}
if(plugin_state->doParityCalculation) { //calculate the check digit
plugin_state->barcodeNumeral[type->numberOfDigits - 1] =
if(plugin_state->barcode_state.doParityCalculation) { //calculate the check digit
plugin_state->barcode_state.barcodeNumeral[type->numberOfDigits - 1] =
calculate_check_digit(plugin_state, type);
}
for(int index = startpos; index < endpos; index++) {
@@ -197,7 +192,9 @@ static void render_callback(Canvas* const canvas, void* ctx) {
barEncodingType = BarEncodingTypeRight;
} else {
barEncodingType =
(FURI_BIT(EAN13ENCODE[plugin_state->barcodeNumeral[0]], index - 1)) ?
(FURI_BIT(
EAN13ENCODE[plugin_state->barcode_state.barcodeNumeral[0]],
index - 1)) ?
BarEncodingTypeG :
BarEncodingTypeLeft;
}
@@ -207,10 +204,14 @@ static void render_callback(Canvas* const canvas, void* ctx) {
}
}
int digitPosition =
get_digit_position(index, barcodeTypes[plugin_state->barcodeTypeIndex]);
int digitPosition = get_digit_position(
index, barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]);
draw_digit(
canvas, plugin_state->barcodeNumeral[index], barEncodingType, digitPosition, true);
canvas,
plugin_state->barcode_state.barcodeNumeral[index],
barEncodingType,
digitPosition,
true);
}
//central separator
@@ -223,7 +224,8 @@ static void render_callback(Canvas* const canvas, void* ctx) {
canvas_draw_box(
canvas,
get_digit_position(
plugin_state->editingIndex, barcodeTypes[plugin_state->barcodeTypeIndex]) -
plugin_state->editingIndex,
barcodeTypes[plugin_state->barcode_state.barcodeTypeIndex]) -
1,
63,
7,
@@ -247,15 +249,17 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void barcode_generator_state_init(PluginState* const plugin_state) {
for(int i = 0; i < BARCODE_MAX_LENS; ++i) {
plugin_state->barcodeNumeral[i] = i % 10;
}
static void barcode_generator_state_init(PluginState* plugin_state) {
plugin_state->editingIndex = 0;
plugin_state->mode = ViewMode;
plugin_state->doParityCalculation = true;
plugin_state->menuIndex = MENU_INDEX_VIEW;
plugin_state->barcodeTypeIndex = 0;
if(!LOAD_BARCODE_SETTINGS(&plugin_state->barcode_state)) {
for(int i = 0; i < BARCODE_MAX_LENS; ++i) {
plugin_state->barcode_state.barcodeNumeral[i] = i % 10;
}
plugin_state->barcode_state.doParityCalculation = true;
plugin_state->barcode_state.barcodeTypeIndex = 0;
}
}
static bool handle_key_press_view(InputKey key, PluginState* plugin_state) {
@@ -277,15 +281,15 @@ static bool handle_key_press_edit(InputKey key, PluginState* plugin_state) {
switch(key) {
case InputKeyUp:
plugin_state->barcodeNumeral[plugin_state->editingIndex] =
(plugin_state->barcodeNumeral[plugin_state->editingIndex] + 1) % 10;
plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] =
(plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] + 1) % 10;
break;
case InputKeyDown:
plugin_state->barcodeNumeral[plugin_state->editingIndex] =
(plugin_state->barcodeNumeral[plugin_state->editingIndex] == 0) ?
plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] =
(plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] == 0) ?
9 :
plugin_state->barcodeNumeral[plugin_state->editingIndex] - 1;
plugin_state->barcode_state.barcodeNumeral[plugin_state->editingIndex] - 1;
break;
case InputKeyRight:
@@ -324,21 +328,24 @@ static bool handle_key_press_menu(InputKey key, PluginState* plugin_state) {
case InputKeyRight:
if(plugin_state->menuIndex == MENU_INDEX_TYPE) {
plugin_state->barcodeTypeIndex =
(plugin_state->barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ?
plugin_state->barcode_state.barcodeTypeIndex =
(plugin_state->barcode_state.barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ?
0 :
plugin_state->barcodeTypeIndex + 1;
plugin_state->barcode_state.barcodeTypeIndex + 1;
} else if(plugin_state->menuIndex == MENU_INDEX_PARITY) {
plugin_state->doParityCalculation = !plugin_state->doParityCalculation;
plugin_state->barcode_state.doParityCalculation =
!plugin_state->barcode_state.doParityCalculation;
}
break;
case InputKeyLeft:
if(plugin_state->menuIndex == MENU_INDEX_TYPE) {
plugin_state->barcodeTypeIndex = (plugin_state->barcodeTypeIndex == 0) ?
NUMBER_OF_BARCODE_TYPES - 1 :
plugin_state->barcodeTypeIndex - 1;
plugin_state->barcode_state.barcodeTypeIndex =
(plugin_state->barcode_state.barcodeTypeIndex == 0) ?
NUMBER_OF_BARCODE_TYPES - 1 :
plugin_state->barcode_state.barcodeTypeIndex - 1;
} else if(plugin_state->menuIndex == MENU_INDEX_PARITY) {
plugin_state->doParityCalculation = !plugin_state->doParityCalculation;
plugin_state->barcode_state.doParityCalculation =
!plugin_state->barcode_state.doParityCalculation;
}
break;
@@ -348,12 +355,13 @@ static bool handle_key_press_menu(InputKey key, PluginState* plugin_state) {
} else if(plugin_state->menuIndex == MENU_INDEX_EDIT) {
plugin_state->mode = EditMode;
} else if(plugin_state->menuIndex == MENU_INDEX_PARITY) {
plugin_state->doParityCalculation = !plugin_state->doParityCalculation;
plugin_state->barcode_state.doParityCalculation =
!plugin_state->barcode_state.doParityCalculation;
} else if(plugin_state->menuIndex == MENU_INDEX_TYPE) {
plugin_state->barcodeTypeIndex =
(plugin_state->barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ?
plugin_state->barcode_state.barcodeTypeIndex =
(plugin_state->barcode_state.barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ?
0 :
plugin_state->barcodeTypeIndex + 1;
plugin_state->barcode_state.barcodeTypeIndex + 1;
}
break;
@@ -430,6 +438,9 @@ int32_t barcode_generator_app(void* p) {
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
// save settings
SAVE_BARCODE_SETTINGS(&plugin_state->barcode_state);
free(plugin_state);
return 0;
}
}
@@ -1,3 +1,34 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <storage/storage.h>
#include <toolbox/saved_struct.h>
#define BARCODE_SETTINGS_FILE_NAME "apps/Misc/barcodegen.save"
#define BARCODE_SETTINGS_VER (1)
#define BARCODE_SETTINGS_PATH EXT_PATH(BARCODE_SETTINGS_FILE_NAME)
#define BARCODE_SETTINGS_MAGIC (0xC2)
#define SAVE_BARCODE_SETTINGS(x) \
saved_struct_save( \
BARCODE_SETTINGS_PATH, \
(x), \
sizeof(BarcodeState), \
BARCODE_SETTINGS_MAGIC, \
BARCODE_SETTINGS_VER)
#define LOAD_BARCODE_SETTINGS(x) \
saved_struct_load( \
BARCODE_SETTINGS_PATH, \
(x), \
sizeof(BarcodeState), \
BARCODE_SETTINGS_MAGIC, \
BARCODE_SETTINGS_VER)
#define BARCODE_HEIGHT 50
#define BARCODE_Y_START 3
#define BARCODE_TEXT_OFFSET 9
@@ -45,11 +76,15 @@ typedef struct {
typedef struct {
int barcodeNumeral[BARCODE_MAX_LENS]; //The current barcode number
bool doParityCalculation; //Should do parity check?
int barcodeTypeIndex;
} BarcodeState;
typedef struct {
BarcodeState barcode_state;
int editingIndex; //The index of the editing symbol
int menuIndex; //The index of the menu cursor
Mode mode; //View, edit or menu
bool doParityCalculation; //Should do parity check?
int barcodeTypeIndex;
} PluginState;
static const int DIGITS[10][4] = {
@@ -278,6 +278,7 @@ void dealer_tick(GameState* game_state) {
if(dealer_score >= DEALER_MAX) {
if(dealer_score > 21 || dealer_score < player_score) {
DOLPHIN_DEED(DolphinDeedPluginGameWin);
enqueue(
&(game_state->queue_state),
game_state,
@@ -571,6 +572,9 @@ int32_t blackjack_app(void* p) {
AppEvent event;
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
GameState* localstate = (GameState*)acquire_mutex_block(&state_mutex);
+4
View File
@@ -13,6 +13,7 @@
#include "level.h"
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
#define SOUND
@@ -995,6 +996,9 @@ int32_t doom_app() {
music_player_worker_load_rtttl_from_string(plugin_state->music_instance->worker, dsintro);
music_player_worker_start(plugin_state->music_instance->worker);
#endif
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
@@ -6,6 +6,7 @@
#include <gui/gui.h>
#include <gui/icon_animation_i.h>
#include <input/input.h>
#include <dolphin/dolphin.h>
#define TAG "Flappy"
#define DEBUG false
@@ -312,6 +313,9 @@ int32_t flappy_game_app(void* p) {
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
GameEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+5
View File
@@ -3,6 +3,7 @@
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <dolphin/dolphin.h>
#include "sandbox.h"
@@ -461,6 +462,10 @@ int32_t game15_app() {
sandbox_init(
FPS, (SandboxRenderCallback)render_callback, (SandboxEventHandler)game_event_handler);
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
sandbox_loop();
sandbox_free();
game_free();
+5 -1
View File
@@ -13,6 +13,7 @@
#include <gui/gui.h>
#include <input/input.h>
#include <storage/storage.h>
#include <dolphin/dolphin.h>
#include "digits.h"
#include "array_utils.h"
@@ -382,7 +383,7 @@ int32_t game_2048_app() {
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
FURI_LOG_E("2048Game", "cannot create mutex\r\n");
free(game_state);
return 255;
}
@@ -397,6 +398,9 @@ int32_t game_2048_app() {
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
bool is_finished = false;
while(!is_finished) {
FuriStatus event_status = furi_message_queue_get(event_queue, &input, FuriWaitForever);
@@ -15,6 +15,7 @@
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <gui/canvas_i.h>
#include <dolphin/dolphin.h>
#define Y_FIELD_SIZE 6
#define Y_LAST (Y_FIELD_SIZE - 1)
@@ -530,6 +531,9 @@ int32_t heap_defence_app(void* p) {
game->game_status = 0;
game->animation = AnimationPause;
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
GameEvent event = {0};
while(event.input.key != InputKeyBack) {
if(furi_message_queue_get(event_queue, &event, 100) != FuriStatusOk) {
@@ -1,7 +0,0 @@
# flipper-zero-hex-viewer
Hex Viewer application for Flipper Zero!
![Hex Viewer app!](https://habrastorage.org/r/w1560/getpro/habr/upload_files/46e/28a/d97/46e28ad973d144b123a4ce513c895d18.png)
[Link to FAP](https://nightly.link/QtRoS/flipper-zero-hex-viewer/actions/artifacts/448677581.zip) (firmware version 0.72)
@@ -1,5 +1,5 @@
App(
appid="HEX_Viewer",
appid="hex_viewer",
name="HEX Viewer",
apptype=FlipperAppType.EXTERNAL,
entry_point="hex_viewer_app",
@@ -13,5 +13,4 @@ App(
fap_icon="icons/hex_10px.png",
fap_category="Misc",
fap_icon_assets="icons",
fap_icon_assets_symbol="hex_viewer",
)
@@ -10,6 +10,7 @@ App(
fap_icon_assets_symbol="hid",
)
App(
appid="Bluetooth_Remote",
name="Bluetooth Remote",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 969 B

After

Width:  |  Height:  |  Size: 174 B

@@ -140,18 +140,18 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
},
{
{.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL},
{.width = 2, .icon = NULL, .key = "Ctl", .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT},
{.width = 2, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI},
{.width = 2, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
{.width = 2, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
{.width = 2, .icon = NULL, .key = "Esc", .value = HID_KEYBOARD_ESCAPE},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_ESCAPE},
{.width = 2, .icon = NULL, .key = "Del", .value = HID_KEYBOARD_DELETE_FORWARD},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_DELETE_FORWARD},
},
};
@@ -6,6 +6,8 @@
#define TAG "HidMouseJiggler"
#define LENGTH(x) (int)(sizeof(x) / sizeof((x)[0]))
struct HidMouseJiggler {
View* view;
Hid* hid;
@@ -15,10 +17,13 @@ struct HidMouseJiggler {
typedef struct {
bool connected;
bool running;
int interval_idx;
uint8_t counter;
HidTransport transport;
} HidMouseJigglerModel;
const int intervals[6] = {500, 2000, 5000, 10000, 30000, 60000};
static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMouseJigglerModel* model = context;
@@ -33,29 +38,39 @@ static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) {
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler");
elements_multiline_text_aligned(canvas, 17, 2, AlignLeft, AlignTop, "Mouse Jiggler");
// Timeout
elements_multiline_text(canvas, AlignLeft, 26, "Interval (ms):");
canvas_set_font(canvas, FontSecondary);
if(model->interval_idx != 0) canvas_draw_icon(canvas, 74, 19, &I_ButtonLeft_4x7);
if(model->interval_idx != LENGTH(intervals) - 1)
canvas_draw_icon(canvas, 80, 19, &I_ButtonRight_4x7);
FuriString* interval_str = furi_string_alloc_printf("%d", intervals[model->interval_idx]);
elements_multiline_text(canvas, 91, 26, furi_string_get_cstr(interval_str));
furi_string_free(interval_str);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto jiggle");
elements_multiline_text(canvas, AlignLeft, 40, "Press Start\nto jiggle");
canvas_set_font(canvas, FontSecondary);
// Ok
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
canvas_draw_icon(canvas, 63, 30, &I_Space_65x18);
if(model->running) {
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
elements_slightly_rounded_box(canvas, 66, 32, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
canvas_draw_icon(canvas, 74, 34, &I_Ok_btn_9x9);
if(model->running) {
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop");
elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Stop");
} else {
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start");
elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Start");
}
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit");
canvas_draw_icon(canvas, 74, 54, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 62, AlignLeft, AlignBottom, "Quit");
}
static void hid_mouse_jiggler_timer_callback(void* context) {
@@ -76,13 +91,6 @@ static void hid_mouse_jiggler_timer_callback(void* context) {
false);
}
static void hid_mouse_jiggler_enter_callback(void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
furi_timer_start(hid_mouse_jiggler->timer, 500);
}
static void hid_mouse_jiggler_exit_callback(void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
@@ -95,14 +103,30 @@ static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) {
bool consumed = false;
if(event->key == InputKeyOk) {
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{ model->running = !model->running; },
true);
consumed = true;
}
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{
if(event->type == InputTypePress && event->key == InputKeyOk) {
model->running = !model->running;
if(model->running) {
furi_timer_stop(hid_mouse_jiggler->timer);
furi_timer_start(hid_mouse_jiggler->timer, intervals[model->interval_idx]);
};
consumed = true;
}
if(event->type == InputTypePress && event->key == InputKeyRight && !model->running &&
model->interval_idx < LENGTH(intervals) - 1) {
model->interval_idx++;
consumed = true;
}
if(event->type == InputTypePress && event->key == InputKeyLeft && !model->running &&
model->interval_idx > 0) {
model->interval_idx--;
consumed = true;
}
},
true);
return consumed;
}
@@ -116,7 +140,6 @@ HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) {
hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel));
view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback);
view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback);
view_set_enter_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_enter_callback);
view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback);
hid_mouse_jiggler->hid = hid;
@@ -127,7 +150,10 @@ HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) {
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{ model->transport = hid->transport; },
{
model->transport = hid->transport;
model->interval_idx = 2;
},
true);
return hid_mouse_jiggler;
@@ -1,65 +0,0 @@
[Original link](https://github.com/Mywk/FlipperTemperatureSensor)
# Flipper Temperature Sensor
## Supported sensors
> HTU2xD, SHT2x, SI702x, SI700x, SI701x, AM2320
## What is this?
A small app for the [Flipper Zero](https://flipperzero.one) that reads the [I2C](https://en.wikipedia.org/wiki/I%C2%B2C) signal from a few temperature sensors and displays the current temperature and humidity.
I'm using a [Sparkfun HTU21D sensor](https://learn.sparkfun.com/tutorials/htu21d-humidity-sensor-hookup-guide), also tested with a clone and with the Si7021 variant.
![Flipper Temperature Sensor](images/Flipper.png)
![App](images/App.png)
<br/>
# How to Connect the HTU21D sensor
![Connection](images/Connection.png)
# How to install
If you have the FAP loader, just copy the fap file from the Releases into your Flipper apps folder and you should be able to launch it from the menu.
If you don't have the FAP loader you will have to bake this application together with your firmware (aka compile it all together).
# FAQ
## The app says the sensor is not found!
1- Are the four connectors correctly soldered?
2- Are the SCL and SDA connections correct? Re-check the "How to Connect the sensor" above.
3- For the HTU21D, on the sensor board, there should be three contacts in the center, for it to work correctly they must be soldered together (basically drop a blob of solder to connect the three of them). Without the solder it looks like this:
![Sensor](images/Sensor.png)
## Which Flipper versions was this app tested on?
Version 1.2
- RM11221439-0.71.2
- unlshd-015
- 0.71.1
Version 1.1
- RM10302252-0.70.1
- unlshd-012
- 0.68.2-1007-RM
- 0.68.1
- 0.67.2
## I can't build the app together with the firmware?
In the *application.fam*, don't forget to change the apptype, it should not be EXTERNAL but APP.
# How to compile
Place the temperature_sensor folder in the applications_user folder and compile using FBT.
Please refer to the [Flipper Build Tool documentation](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md).
@@ -1,14 +0,0 @@
App(
appid="HTU_Temperature_Sensor",
name="[HTU/+] Temperature Sensor",
apptype=FlipperAppType.EXTERNAL,
entry_point="temperature_sensor_app",
cdefines=["APP_TEMPERATURE_SENSOR"],
requires=[
"gui",
],
stack_size=2 * 1024,
order=90,
fap_icon="temperature_sensor.png",
fap_category="GPIO",
)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 914 KiB

@@ -1,387 +0,0 @@
/* Flipper App to read the values from a HTU2XD, SHT2X, SI702X, SI700X, SI701X or AM2320 Sensor */
/* Created by Mywk - https://github.com/Mywk - https://mywk.net */
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_i2c.h>
#include <math.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification_messages.h>
#include <string.h>
#define TS_DEFAULT_VALUE 0xFFFF
#define TS_AVAILABLE_SENSORS 2
// HTU2XD, SHT2X, SI702X, SI700X address
#define HTU2XD_SHT2X_SI702X_SI700X_ADDRESS (0x40 << 1)
// SI701X ADDRESS
#define SI701X_ADDRESS (0x41 << 1)
// HTU2XD, SHT2X, SI702X, SI700X commands
#define HTU21D_CMD_TEMPERATURE 0xE3
#define HTU21D_CMD_HUMIDITY 0xE5
// AM2320 address
#define AM2320_ADDRESS (0x5C << 1)
// Used for the temperature and humidity buffers
#define DATA_BUFFER_SIZE 8
// External I2C BUS
#define I2C_BUS &furi_hal_i2c_handle_external
// Typedef enums to make everything easier to read
typedef enum { TSSCmdNone, TSSCmdTemperature, TSSCmdHumidity } TSSCmdType;
typedef enum {
TSSInitializing,
TSSNoSensor,
TSSPendingUpdate,
} TSStatus;
typedef enum {
TSEventTypeTick,
TSEventTypeInput,
} TSEventType;
typedef struct {
TSEventType type;
InputEvent input;
} TSEvent;
// Possible return values for sensor_cmd
typedef enum {
TSCmdRet_Error,
TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X,
TSCmdRet_SI701X,
TSCmdRet_AM2320,
} TSCmdRet;
// External NotificationSequence RGB
extern const NotificationSequence sequence_blink_red_100;
extern const NotificationSequence sequence_blink_green_100;
extern const NotificationSequence sequence_blink_blue_100;
// Current status of the temperature sensor app
static TSStatus temperature_sensor_current_status = TSSInitializing;
// We keep track of the last cmd return
static TSCmdRet temperature_sensor_last_cmd_ret = TSCmdRet_Error;
// Temperature and Humidity data buffers, ready to print
char ts_data_buffer_temperature_c[DATA_BUFFER_SIZE];
char ts_data_buffer_temperature_f[DATA_BUFFER_SIZE];
char ts_data_buffer_relative_humidity[DATA_BUFFER_SIZE];
char ts_data_buffer_absolute_humidity[DATA_BUFFER_SIZE];
// <sumary>
// Executes an I2C cmd (trx)
// </sumary>
// <TODO>
// CRC
// </TODO>
// <returns>
// true if fetch was successful, false otherwise
// </returns>
static TSCmdRet temperature_sensor_cmd(TSSCmdType cmd, uint8_t* buffer) {
uint32_t timeout = furi_ms_to_ticks(100);
TSCmdRet ret = TSCmdRet_Error;
// Aquire I2C and check if device is ready, then release
furi_hal_i2c_acquire(I2C_BUS);
// Check if HTU2XD, SHT2X, SI702X, SI700X sensor is available
uint8_t isAddress40 =
furi_hal_i2c_is_device_ready(I2C_BUS, HTU2XD_SHT2X_SI702X_SI700X_ADDRESS, timeout);
uint8_t isAddress41 = 0;
// Check if SI701X sensor is available if necessary
if(!isAddress40) isAddress41 = furi_hal_i2c_is_device_ready(I2C_BUS, SI701X_ADDRESS, timeout);
if(isAddress40 || isAddress41) {
uint8_t address = isAddress40 ? HTU2XD_SHT2X_SI702X_SI700X_ADDRESS : SI701X_ADDRESS;
// Better safe than sorry delay
furi_delay_ms(15);
// Extra delay for the SI70XX
if(isAddress41) furi_delay_ms(50);
// Transmit either the temperature or the humidity command depending on TSSCmdType
uint8_t c = (cmd == TSSCmdTemperature) ? HTU21D_CMD_TEMPERATURE : HTU21D_CMD_HUMIDITY;
if(furi_hal_i2c_tx(I2C_BUS, address, &c, 1, timeout)) {
// Receive data (2 bytes)
if(furi_hal_i2c_rx(I2C_BUS, address, buffer, 2, timeout + 50))
ret = isAddress40 ? TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X : TSCmdRet_SI701X;
}
} else {
// The AM2320 goes to sleep after a period of inactivity, wake it up (check AM2320 datasheet for more info)
furi_hal_i2c_is_device_ready(I2C_BUS, AM2320_ADDRESS, timeout);
furi_delay_ms(30);
// Check if it's really available
if(furi_hal_i2c_is_device_ready(I2C_BUS, AM2320_ADDRESS, timeout)) {
// {Address, Register, Len}
const uint8_t request[3] = {0x03, 0x00, 0x04};
if(furi_hal_i2c_tx(I2C_BUS, AM2320_ADDRESS, request, 3, timeout)) {
// 6 bytes - usually 8 but we currently don't check the CRC
if(furi_hal_i2c_rx(I2C_BUS, (uint8_t)AM2320_ADDRESS, buffer, 6, timeout))
ret = TSCmdRet_AM2320;
}
}
}
furi_hal_i2c_release(I2C_BUS);
temperature_sensor_last_cmd_ret = ret;
return ret;
}
// <sumary>
// Fetches temperature and humidity from sensor
// </sumary>
// <params>
// temperature in C
// humidity in relative humidity
// </params>
// <remarks>
// Temperature and humidity must be preallocated
// </remarks>
// <TODO>
// CRC
// </TODO>
// <returns>
// true if fetch was successful, false otherwise
// </returns>
static bool temperature_sensor_fetch_data(double* temperature, double* humidity) {
bool ret = false;
uint16_t adc_raw;
uint8_t buffer[DATA_BUFFER_SIZE] = {0x00};
// Check if the sensor is the HTU21D by attempting to fetch the temperature
TSCmdRet cmdRet = temperature_sensor_cmd(TSSCmdTemperature, buffer);
if(cmdRet == TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X || cmdRet == TSCmdRet_SI701X) {
// Calculate temperature
adc_raw = ((uint16_t)(buffer[0] << 8) | (buffer[1]));
*temperature = (float)(adc_raw * 175.72 / 65536.00) - 46.85;
// Fetch humidity
if(temperature_sensor_cmd(TSSCmdHumidity, buffer)) {
// Calculate humidity
adc_raw = ((uint16_t)(buffer[0] << 8) | (buffer[1]));
*humidity = (float)(adc_raw * 125.0 / 65536.00) - 6.0;
ret = true;
}
} else if(cmdRet == TSCmdRet_AM2320) {
// The AM2320 returns all the data immediately so we just process it all
// Note: CRC isn't currently present in the buffer
// Temperature
float temp = (((buffer[4] & 0x7F) << 8) + buffer[5]) / 10;
*temperature = ((buffer[4] & 0x80) >> 7) == 1 ? temp * (-1) : temp;
// Humidity
temp = ((buffer[2] << 8) + buffer[3]) / 10;
*humidity = temp;
ret = true;
}
return ret;
}
// <sumary>
// Draw callback
// </sumary>
static void temperature_sensor_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
// Update title accordingly (this could be improved by checking the hardware id)
switch(temperature_sensor_last_cmd_ret) {
case TSCmdRet_Error:
canvas_draw_str(canvas, 2, 10, "Temperature Sensor");
break;
case TSCmdRet_HTU2XD_SHT2X_SI702X_SI700X:
canvas_draw_str(canvas, 2, 10, "HTU/SHT/SI70 Sensor");
break;
case TSCmdRet_SI701X:
canvas_draw_str(canvas, 2, 10, "SI701X Sensor");
break;
case TSCmdRet_AM2320:
canvas_draw_str(canvas, 2, 10, "AM2320 Sensor");
break;
default:
break;
}
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 62, "Press back to exit.");
switch(temperature_sensor_current_status) {
case TSSInitializing:
canvas_draw_str(canvas, 2, 30, "Initializing..");
break;
case TSSNoSensor:
canvas_draw_str(canvas, 2, 30, "No sensor found!");
break;
case TSSPendingUpdate: {
canvas_draw_str(canvas, 3, 24, "Temperature");
canvas_draw_str(canvas, 68, 24, "Humidity");
// Draw vertical lines
canvas_draw_line(canvas, 61, 16, 61, 50);
canvas_draw_line(canvas, 62, 16, 62, 50);
// Draw horizontal line
canvas_draw_line(canvas, 2, 27, 122, 27);
// Draw temperature and humidity values
canvas_draw_str(canvas, 8, 38, ts_data_buffer_temperature_c);
canvas_draw_str(canvas, 42, 38, "C");
canvas_draw_str(canvas, 8, 48, ts_data_buffer_temperature_f);
canvas_draw_str(canvas, 42, 48, "F");
canvas_draw_str(canvas, 68, 38, ts_data_buffer_relative_humidity);
canvas_draw_str(canvas, 100, 38, "%");
canvas_draw_str(canvas, 68, 48, ts_data_buffer_absolute_humidity);
canvas_draw_str(canvas, 100, 48, "g/m3");
} break;
default:
break;
}
}
// <sumary>
// Input callback
// </sumary>
static void temperature_sensor_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
TSEvent event = {.type = TSEventTypeInput, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
// <sumary>
// Timer callback
// </sumary>
static void temperature_sensor_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
TSEvent event = {.type = TSEventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
// <sumary>
// App entry point
// </sumary>
int32_t temperature_sensor_app(void* p) {
UNUSED(p);
// Declare our variables and assign variables a default value
TSEvent tsEvent;
bool sensorFound = false;
double celsius, fahrenheit, rel_humidity, abs_humidity = TS_DEFAULT_VALUE;
// Used for absolute humidity calculation
double vapour_pressure = 0;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TSEvent));
// Register callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, temperature_sensor_draw_callback, NULL);
view_port_input_callback_set(view_port, temperature_sensor_input_callback, event_queue);
// Create timer and register its callback
FuriTimer* timer =
furi_timer_alloc(temperature_sensor_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency());
// Register viewport
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Used to notify the user by blinking red (error) or blue (fetch successful)
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
while(1) {
furi_check(furi_message_queue_get(event_queue, &tsEvent, FuriWaitForever) == FuriStatusOk);
// Handle events
if(tsEvent.type == TSEventTypeInput) {
// Exit on back key
if(tsEvent.input.key ==
InputKeyBack) // We dont check for type here, we can check the type of keypress like: (event.input.type == InputTypeShort)
break;
} else if(tsEvent.type == TSEventTypeTick) {
// Update sensor data
// Fetch data and set the sensor current status accordingly
sensorFound = temperature_sensor_fetch_data(&celsius, &rel_humidity);
temperature_sensor_current_status = (sensorFound ? TSSPendingUpdate : TSSNoSensor);
if(sensorFound) {
// Blink blue
notification_message(notifications, &sequence_blink_blue_100);
if(celsius != TS_DEFAULT_VALUE && rel_humidity != TS_DEFAULT_VALUE) {
// Convert celsius to fahrenheit
fahrenheit = (celsius * 9 / 5) + 32;
// Calculate absolute humidity - For more info refer to https://github.com/Mywk/FlipperTemperatureSensor/issues/1
// Calculate saturation vapour pressure first
vapour_pressure =
(double)6.11 *
pow(10, (double)(((double)7.5 * celsius) / ((double)237.3 + celsius)));
// Then the vapour pressure in Pa
vapour_pressure = vapour_pressure * rel_humidity;
// Calculate absolute humidity
abs_humidity =
(double)2.16679 * (double)(vapour_pressure / ((double)273.15 + celsius));
// Fill our buffers here, not on the canvas draw callback
snprintf(ts_data_buffer_temperature_c, DATA_BUFFER_SIZE, "%.2f", celsius);
snprintf(ts_data_buffer_temperature_f, DATA_BUFFER_SIZE, "%.2f", fahrenheit);
snprintf(
ts_data_buffer_relative_humidity, DATA_BUFFER_SIZE, "%.2f", rel_humidity);
snprintf(
ts_data_buffer_absolute_humidity, DATA_BUFFER_SIZE, "%.2f", abs_humidity);
}
} else {
// Reset our variables to their default values
celsius = fahrenheit = rel_humidity = abs_humidity = TS_DEFAULT_VALUE;
// Blink red
notification_message(notifications, &sequence_blink_red_100);
}
}
furi_delay_ms(!sensorFound ? 100 : 500);
}
// Dobby is freee (free our variables, Flipper will crash if we don't do this!)
furi_timer_free(timer);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_GUI);
return 0;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

@@ -8,6 +8,8 @@
#include <dialogs/dialogs.h>
#include <m-string.h>
#include <dolphin/dolphin.h>
#include "assets.h"
#define PLAYFIELD_WIDTH 16
@@ -251,6 +253,9 @@ static bool game_won(Minesweeper* minesweeper_state) {
dialog_message_set_buttons(message, NULL, "Play again", NULL);
dialog_message_set_icon(message, NULL, 72, 17);
// Call dolphin deed when we win the game
DOLPHIN_DEED(DolphinDeedPluginGameWin);
DialogMessageButton choice = dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_string_free(tempStr);
@@ -405,6 +410,9 @@ int32_t minesweeper_app(void* p) {
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Call dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+10 -10
View File
@@ -44,15 +44,10 @@ static void render_callback(Canvas* const canvas, void* ctx) {
canvas_draw_frame(canvas, vol_bar_x_pos, vol_bar_y_pos, 4, 64);
canvas_draw_box(canvas, vol_bar_x_pos, vol_bar_y_pos + (64 - volume_h), 4, volume_h);
//dit bpm
canvas_draw_str_aligned(
canvas,
0,
10,
AlignLeft,
AlignCenter,
furi_string_get_cstr(
furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta)));
//dit bpms
FuriString* ditbpm = furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta);
canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignCenter, furi_string_get_cstr(ditbpm));
furi_string_free(ditbpm);
//button info
elements_button_center(canvas, "Press/Hold");
@@ -67,7 +62,7 @@ static void input_callback(InputEvent* input_event, void* ctx) {
static void morse_code_worker_callback(FuriString* words, void* context) {
MorseCode* morse_code = context;
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
morse_code->model->words = words;
furi_string_set(morse_code->model->words, words);
furi_mutex_release(morse_code->model_mutex);
view_port_update(morse_code->view_port);
}
@@ -109,6 +104,7 @@ void morse_code_free(MorseCode* instance) {
furi_mutex_free(instance->model_mutex);
furi_string_free(instance->model->words);
free(instance->model);
free(instance);
}
@@ -116,10 +112,12 @@ void morse_code_free(MorseCode* instance) {
int32_t morse_code_app() {
MorseCode* morse_code = morse_code_alloc();
InputEvent input;
morse_code_worker_start(morse_code->worker);
morse_code_worker_set_volume(
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) ==
FuriStatusOk) {
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
@@ -128,6 +126,7 @@ int32_t morse_code_app() {
break;
} else if(input.key == InputKeyBack && input.type == InputTypeShort) {
morse_code_worker_reset_text(morse_code->worker);
furi_string_reset(morse_code->model->words);
} else if(input.key == InputKeyOk) {
if(input.type == InputTypePress)
morse_code_worker_play(morse_code->worker, true);
@@ -160,6 +159,7 @@ int32_t morse_code_app() {
furi_mutex_release(morse_code->model_mutex);
view_port_update(morse_code->view_port);
}
morse_code_worker_stop(morse_code->worker);
morse_code_free(morse_code);
return 0;
@@ -35,6 +35,8 @@ void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration)
furi_string_push_back(instance->buffer, *DOT);
else if(duration <= (instance->dit_delta * 3))
furi_string_push_back(instance->buffer, *LINE);
else
furi_string_reset(instance->buffer);
if(furi_string_size(instance->buffer) > 5) furi_string_reset(instance->buffer);
FURI_LOG_D("MorseCode: Buffer", "%s", furi_string_get_cstr(instance->buffer));
}
@@ -87,9 +89,13 @@ static int32_t morse_code_worker_thread_callback(void* context) {
if(!pushed) {
if(end_tick + (instance->dit_delta * 3) < furi_get_tick()) {
//NEW LETTER
morse_code_worker_fill_letter(instance);
if(instance->callback)
instance->callback(instance->words, instance->callback_context);
if(!furi_string_empty(instance->buffer)) {
morse_code_worker_fill_letter(instance);
if(instance->callback)
instance->callback(instance->words, instance->callback_context);
} else {
spaced = true;
}
pushed = true;
}
}
@@ -170,6 +176,7 @@ void morse_code_worker_start(MorseCodeWorker* instance) {
void morse_code_worker_stop(MorseCodeWorker* instance) {
furi_assert(instance);
furi_assert(instance->is_running == true);
instance->play = false;
instance->is_running = false;
furi_thread_join(instance->thread);
FURI_LOG_D("MorseCode: Stop", "Stop");
@@ -1,12 +0,0 @@
App(
appid="MouseJiggler",
name="Mouse Jiggler",
apptype=FlipperAppType.EXTERNAL,
entry_point="mouse_jiggler_app",
cdefines=["APP_MOUSE_JIGGLER"],
requires=["gui"],
stack_size=1 * 1024,
order=150,
fap_icon="mouse_10px.png",
fap_category="Misc",
)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1,141 +0,0 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#define MOUSE_MOVE_SHORT 5
#define MOUSE_MOVE_LONG 20
typedef enum {
EventTypeInput,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} UsbMouseEvent;
typedef struct {
bool running;
} MouseJigglerState;
static void mouse_jiggler_render_callback(Canvas* canvas, void* ctx) {
const MouseJigglerState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) {
return;
}
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 12, "USB Mouse Jiggler");
if(!plugin_state->running) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 27, " -> STOPPED");
canvas_draw_str(canvas, 2, 51, "Press [ok] to start");
canvas_draw_str(canvas, 2, 63, "Press [back] to exit");
} else {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 27, " -> RUNNING");
canvas_draw_str(canvas, 2, 51, "Press [back] to stop");
}
release_mutex((ValueMutex*)ctx, plugin_state);
}
static void mouse_jiggler_input_callback(InputEvent* input_event, void* ctx) {
FuriMessageQueue* event_queue = ctx;
furi_assert(event_queue);
UsbMouseEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void mouse_jiggler_state_init(MouseJigglerState* const plugin_state) {
plugin_state->running = false;
}
int32_t mouse_jiggler_app(void* p) {
UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(UsbMouseEvent));
MouseJigglerState* plugin_state = malloc(sizeof(MouseJigglerState));
if(plugin_state == NULL) {
FURI_LOG_E("MouseJiggler", "MouseJigglerState: malloc error\r\n");
return 255;
}
mouse_jiggler_state_init(plugin_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(MouseJigglerState))) {
FURI_LOG_E("MouseJiggler", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(plugin_state);
return 255;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, mouse_jiggler_render_callback, &state_mutex);
view_port_input_callback_set(view_port, mouse_jiggler_input_callback, event_queue);
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
furi_hal_usb_set_config(&usb_hid, NULL);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
UsbMouseEvent event;
//bool status = 0;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
MouseJigglerState* plugin_state = (MouseJigglerState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyOk:
if(!plugin_state->running) {
plugin_state->running = true;
}
break;
case InputKeyBack:
if(!plugin_state->running) {
processing = false;
} else {
plugin_state->running = false;
}
break;
default:
break;
}
}
}
}
if(plugin_state->running) {
furi_hal_hid_mouse_move(MOUSE_MOVE_SHORT, 0);
furi_delay_ms(500);
furi_hal_hid_mouse_move(-MOUSE_MOVE_SHORT, 0);
furi_delay_ms(500);
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state);
}
furi_hal_usb_set_config(usb_mode_prev, NULL);
// remove & free all stuff created by app
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
return 0;
}
@@ -40,7 +40,7 @@ void pcsg_begin(POCSAGPagerApp* app, uint8_t* preset_data) {
furi_hal_subghz_reset();
furi_hal_subghz_idle();
furi_hal_subghz_load_custom_preset(preset_data);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
app->txrx->txrx_state = PCSGTxRxStateIDLE;
}
@@ -54,7 +54,7 @@ uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency) {
furi_hal_subghz_idle();
uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx();
furi_hal_subghz_rx();
@@ -195,6 +195,10 @@ bool pocsag_pager_scene_receiver_on_event(void* context, SceneManagerEvent event
pcsg_hopper_update(app);
pocsag_pager_scene_receiver_update_statusbar(app);
}
// Get current RSSI
float rssi = furi_hal_subghz_get_rssi();
pcsg_receiver_rssi(app->pcsg_receiver, rssi);
if(app->txrx->txrx_state == PCSGTxRxStateRx) {
notification_message(app->notifications, &sequence_blink_cyan_10);
}
@@ -12,6 +12,8 @@
#define MENU_ITEMS 4u
#define UNLOCK_CNT 3
#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f
typedef struct {
FuriString* item_str;
uint8_t type;
@@ -58,8 +60,24 @@ typedef struct {
uint16_t list_offset;
uint16_t history_item;
PCSGReceiverBarShow bar_show;
uint8_t u_rssi;
} PCSGReceiverModel;
void pcsg_receiver_rssi(PCSGReceiver* instance, float rssi) {
furi_assert(instance);
with_view_model(
instance->view,
PCSGReceiverModel * model,
{
if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) {
model->u_rssi = 0;
} else {
model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN);
}
},
true);
}
void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock lock) {
furi_assert(pcsg_receiver);
pcsg_receiver->lock_count = 0;
@@ -167,13 +185,23 @@ static void pcsg_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scr
canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11);
}
static void pcsg_view_rssi_draw(Canvas* canvas, PCSGReceiverModel* model) {
for(uint8_t i = 1; i < model->u_rssi; i++) {
if(i % 5) {
canvas_draw_dot(canvas, 46 + i, 50);
canvas_draw_dot(canvas, 47 + i, 51);
canvas_draw_dot(canvas, 46 + i, 52);
}
}
}
void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
elements_button_left(canvas, "Config");
canvas_draw_line(canvas, 46, 51, 125, 51);
//canvas_draw_line(canvas, 46, 51, 125, 51);
bool scrollbar = model->history_item > 4;
FuriString* str_buff;
@@ -207,10 +235,13 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning...");
canvas_draw_line(canvas, 46, 51, 125, 51);
//canvas_draw_line(canvas, 46, 51, 125, 51);
canvas_set_font(canvas, FontSecondary);
}
// Draw RSSI
pcsg_view_rssi_draw(canvas, model);
switch(model->bar_show) {
case PCSGReceiverBarShowLock:
canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8);
@@ -8,6 +8,8 @@ typedef struct PCSGReceiver PCSGReceiver;
typedef void (*PCSGReceiverCallback)(PCSGCustomEvent event, void* context);
void pcsg_receiver_rssi(PCSGReceiver* instance, float rssi);
void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock keyboard);
void pcsg_view_receiver_set_callback(
+2
View File
@@ -285,3 +285,5 @@ A big thank you to the RTL433 author, [Benjamin Larsson](https://github.com/merb
* As a sourve of documentation for protocols.
* As an awesome way to visualize and understand protocols, via [these great web tools](https://triq.org/).
* To have tons of fun with RTLSDR in general, now and in the past.
The application icon was designed by Stefano Liuzzo.
+27 -61
View File
@@ -3,32 +3,6 @@
#include "app.h"
/* If this define is enabled, ProtoView is going to mess with the
* otherwise opaque SubGhzWorker structure in order to disable
* its filter for samples shorter than a given amount (30us at the
* time I'm writing this comment).
*
* This structure must be taken in sync with the one of the firmware. */
#define PROTOVIEW_DISABLE_SUBGHZ_FILTER 0
#ifdef PROTOVIEW_DISABLE_SUBGHZ_FILTER
struct SubGhzWorker {
FuriThread* thread;
FuriStreamBuffer* stream;
volatile bool running;
volatile bool overrun;
LevelDuration filter_level_duration;
bool filter_running;
uint16_t filter_duration;
SubGhzWorkerOverrunCallback overrun_callback;
SubGhzWorkerPairCallback pair_callback;
void* context;
};
#endif
RawSamplesBuffer *RawSamples, *DetectedSamples;
extern const SubGhzProtocolRegistry protoview_protocol_registry;
@@ -42,6 +16,7 @@ extern const SubGhzProtocolRegistry protoview_protocol_registry;
* and setting color to black. */
static void render_callback(Canvas* const canvas, void* ctx) {
ProtoViewApp* app = ctx;
furi_mutex_acquire(app->view_updating_mutex, FuriWaitForever);
/* Clear screen. */
canvas_set_color(canvas, ColorWhite);
@@ -74,6 +49,7 @@ static void render_callback(Canvas* const canvas, void* ctx) {
/* Draw the alert box if set. */
ui_draw_alert_if_needed(canvas, app);
furi_mutex_release(app->view_updating_mutex);
}
/* Here all we do is putting the events into the queue that will be handled
@@ -91,6 +67,8 @@ static void input_callback(InputEvent* input_event, void* ctx) {
* special views ViewGoNext and ViewGoPrev in order to move to
* the logical next/prev view. */
static void app_switch_view(ProtoViewApp* app, ProtoViewCurrentView switchto) {
furi_mutex_acquire(app->view_updating_mutex, FuriWaitForever);
/* Switch to the specified view. */
ProtoViewCurrentView old = app->current_view;
if(switchto == ViewGoNext) {
@@ -106,22 +84,10 @@ static void app_switch_view(ProtoViewApp* app, ProtoViewCurrentView switchto) {
}
ProtoViewCurrentView new = app->current_view;
/* Set the current subview of the view we just left to zero. This is
* the main subview of the old view. When re re-enter the view we are
* lefting, we want to see the main thing again. */
app->current_subview[old] = 0;
/* Reset the view private data each time, before calling the enter/exit
* callbacks that may want to setup some state. */
memset(app->view_privdata, 0, PROTOVIEW_VIEW_PRIVDATA_LEN);
/* Call the enter/exit view callbacks if needed. */
/* Call the exit view callbacks. */
if(old == ViewDirectSampling) view_exit_direct_sampling(app);
if(new == ViewDirectSampling) view_enter_direct_sampling(app);
if(old == ViewBuildMessage) view_exit_build_message(app);
if(new == ViewBuildMessage) view_enter_build_message(app);
if(old == ViewInfo) view_exit_info(app);
/* The frequency/modulation settings are actually a single view:
* as long as the user stays between the two modes of this view we
* don't need to call the exit-view callback. */
@@ -129,7 +95,24 @@ static void app_switch_view(ProtoViewApp* app, ProtoViewCurrentView switchto) {
(old == ViewModulationSettings && new != ViewFrequencySettings))
view_exit_settings(app);
/* Reset the view private data each time, before calling the enter
* callbacks that may want to setup some state. */
memset(app->view_privdata, 0, PROTOVIEW_VIEW_PRIVDATA_LEN);
/* Call the enter view callbacks after all the exit callback
* of the old view was already executed. */
if(new == ViewDirectSampling) view_enter_direct_sampling(app);
if(new == ViewBuildMessage) view_enter_build_message(app);
/* Set the current subview of the view we just left to zero. This is
* the main subview of the old view. When we re-enter the view we are
* lefting, we want to see the main thing again. */
app->current_subview[old] = 0;
/* If there is an alert on screen, dismiss it: if the user is
* switching view she already read it. */
ui_dismiss_alert(app);
furi_mutex_release(app->view_updating_mutex);
}
/* Allocate the application state and initialize a number of stuff.
@@ -143,7 +126,7 @@ ProtoViewApp* protoview_app_alloc() {
//init setting
app->setting = subghz_setting_alloc();
subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user.txt"));
subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user"));
// GUI
app->gui = furi_record_open(RECORD_GUI);
@@ -158,6 +141,7 @@ ProtoViewApp* protoview_app_alloc() {
app->show_text_input = false;
app->alert_dismiss_time = 0;
app->current_view = ViewRawPulses;
app->view_updating_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
for(int j = 0; j < ViewLast; j++) app->current_subview[j] = 0;
app->direct_sampling_enabled = false;
app->view_privdata = malloc(PROTOVIEW_VIEW_PRIVDATA_LEN);
@@ -174,25 +158,11 @@ ProtoViewApp* protoview_app_alloc() {
// Init Worker & Protocol
app->txrx = malloc(sizeof(ProtoViewTxRx));
/* Setup rx worker and environment. */
/* Setup rx state. */
app->txrx->freq_mod_changed = false;
app->txrx->debug_timer_sampling = false;
app->txrx->last_g0_change_time = DWT->CYCCNT;
app->txrx->last_g0_value = false;
app->txrx->worker = subghz_worker_alloc();
#ifdef PROTOVIEW_DISABLE_SUBGHZ_FILTER
app->txrx->worker->filter_running = 0;
#endif
app->txrx->environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry(
app->txrx->environment, (void*)&protoview_protocol_registry);
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
subghz_worker_set_overrun_callback(
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
app->frequency = subghz_setting_get_default_frequency(app->setting);
app->modulation = 0; /* Defaults to ProtoViewModulations[0]. */
@@ -219,17 +189,13 @@ void protoview_app_free(ProtoViewApp* app) {
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_message_queue_free(app->event_queue);
furi_mutex_free(app->view_updating_mutex);
app->gui = NULL;
// Frequency setting.
subghz_setting_free(app->setting);
// Worker stuff.
if(!app->txrx->debug_timer_sampling) {
subghz_receiver_free(app->txrx->receiver);
subghz_environment_free(app->txrx->environment);
subghz_worker_free(app->txrx->worker);
}
free(app->txrx);
// Raw samples buffers.
@@ -259,7 +225,7 @@ static void timer_callback(void* ctx) {
}
if(delta < RawSamples->total / 2) return;
app->signal_last_scan_idx = RawSamples->idx;
scan_for_signal(app, RawSamples);
scan_for_signal(app, RawSamples, ProtoViewModulations[app->modulation].duration_filter);
}
/* This is the navigation callback we use in the view dispatcher used
+25 -12
View File
@@ -17,9 +17,6 @@
#include <gui/modules/text_input.h>
#include <notification/notification_messages.h>
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/subghz_worker.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/registry.h>
#include "raw_samples.h"
@@ -28,7 +25,7 @@
#define BITMAP_SEEK_NOT_FOUND UINT32_MAX // Returned by function as sentinel
#define PROTOVIEW_VIEW_PRIVDATA_LEN 64 // View specific private data len
#define DEBUG_MSG 1
#define DEBUG_MSG 0
/* Forward declarations. */
@@ -69,8 +66,11 @@ typedef struct {
const char* name; // Name to show to the user.
const char* id; // Identifier in the Flipper API/file.
FuriHalSubGhzPreset preset; // The preset ID.
uint8_t* custom; // If not null, a set of registers for
// the CC1101, specifying a custom preset.
uint8_t* custom; /* If not null, a set of registers for
the CC1101, specifying a custom preset.*/
uint32_t duration_filter; /* Ignore pulses and gaps that are less
than the specified microseconds. This
depends on the data rate. */
} ProtoViewModulation;
extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
@@ -82,9 +82,6 @@ struct ProtoViewTxRx {
bool freq_mod_changed; /* The user changed frequency and/or modulation
from the interface. There is to restart the
radio with the right parameters. */
SubGhzWorker* worker; /* Our background worker. */
SubGhzEnvironment* environment;
SubGhzReceiver* receiver;
TxRxState txrx_state; /* Receiving, idle or sleeping? */
/* Timer sampling mode state. */
@@ -108,6 +105,10 @@ struct ProtoViewApp {
ViewPort* view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */
ProtoViewCurrentView current_view; /* Active left-right view ID. */
FuriMutex* view_updating_mutex; /* The Flipper GUI calls the screen redraw
callback in a different thread. We
use this mutex to protect the redraw
from changes in app->view_privdata. */
int current_subview[ViewLast]; /* Active up-down subview ID. */
FuriMessageQueue* event_queue; /* Keypress events go here. */
@@ -238,7 +239,7 @@ typedef struct ProtoViewDecoder {
extern RawSamplesBuffer *RawSamples, *DetectedSamples;
/* app_radio.c */
/* app_subghz.c */
void radio_begin(ProtoViewApp* app);
uint32_t radio_rx(ProtoViewApp* app);
void radio_idle(ProtoViewApp* app);
@@ -247,11 +248,12 @@ void radio_sleep(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp* app);
void raw_sampling_worker_stop(ProtoViewApp* app);
void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder, void* ctx);
void protoview_rx_callback(bool level, uint32_t duration, void* context);
/* signal.c */
uint32_t duration_delta(uint32_t a, uint32_t b);
void reset_current_signal(ProtoViewApp* app);
void scan_for_signal(ProtoViewApp* app, RawSamplesBuffer* source);
void scan_for_signal(ProtoViewApp* app, RawSamplesBuffer* source, uint32_t min_duration);
bool bitmap_get(uint8_t* b, uint32_t blen, uint32_t bitpos);
void bitmap_set(uint8_t* b, uint32_t blen, uint32_t bitpos, bool val);
void bitmap_copy(
@@ -271,6 +273,15 @@ uint32_t bitmap_seek_bits(
uint32_t startpos,
uint32_t maxbits,
const char* bits);
bool bitmap_match_bitmap(
uint8_t* b1,
uint32_t b1len,
uint32_t b1off,
uint8_t* b2,
uint32_t b2len,
uint32_t b2off,
uint32_t cmplen);
void bitmap_to_string(char* dst, uint8_t* b, uint32_t blen, uint32_t off, uint32_t len);
uint32_t convert_from_line_code(
uint8_t* buf,
uint64_t buflen,
@@ -339,7 +350,7 @@ void fieldset_add_int(ProtoViewFieldSet* fs, const char* name, int64_t val, uint
void fieldset_add_uint(ProtoViewFieldSet* fs, const char* name, uint64_t uval, uint8_t bits);
void fieldset_add_hex(ProtoViewFieldSet* fs, const char* name, uint64_t uval, uint8_t bits);
void fieldset_add_bin(ProtoViewFieldSet* fs, const char* name, uint64_t uval, uint8_t bits);
void fieldset_add_str(ProtoViewFieldSet* fs, const char* name, const char* s);
void fieldset_add_str(ProtoViewFieldSet* fs, const char* name, const char* s, size_t len);
void fieldset_add_bytes(
ProtoViewFieldSet* fs,
const char* name,
@@ -359,3 +370,5 @@ void field_set_from_field(ProtoViewField* dst, ProtoViewField* src);
/* crc.c */
uint8_t crc8(const uint8_t* data, size_t len, uint8_t init, uint8_t poly);
uint8_t sum_bytes(const uint8_t* data, size_t len, uint8_t init);
uint8_t xor_bytes(const uint8_t* data, size_t len, uint8_t init);
+37 -29
View File
@@ -9,31 +9,32 @@
#include <furi_hal_spi.h>
#include <furi_hal_interrupt.h>
void raw_sampling_worker_start(ProtoViewApp* app);
void raw_sampling_worker_stop(ProtoViewApp* app);
void raw_sampling_timer_start(ProtoViewApp* app);
void raw_sampling_timer_stop(ProtoViewApp* app);
ProtoViewModulation ProtoViewModulations[] = {
{"OOK 650Khz", "FuriHalSubGhzPresetOok650Async", FuriHalSubGhzPresetOok650Async, NULL},
{"OOK 270Khz", "FuriHalSubGhzPresetOok270Async", FuriHalSubGhzPresetOok270Async, NULL},
{"OOK 650Khz", "FuriHalSubGhzPresetOok650Async", FuriHalSubGhzPresetOok650Async, NULL, 30},
{"OOK 270Khz", "FuriHalSubGhzPresetOok270Async", FuriHalSubGhzPresetOok270Async, NULL, 30},
{"2FSK 2.38Khz",
"FuriHalSubGhzPreset2FSKDev238Async",
FuriHalSubGhzPreset2FSKDev238Async,
NULL},
NULL,
30},
{"2FSK 47.6Khz",
"FuriHalSubGhzPreset2FSKDev476Async",
FuriHalSubGhzPreset2FSKDev476Async,
NULL},
{"TPMS 1 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs},
{"TPMS 2 (OOK)", NULL, 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs},
{"TPMS 3 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs},
{"TPMS 4 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms4_fsk_async_regs},
{NULL, NULL, 0, NULL} /* End of list sentinel. */
NULL,
30},
{"TPMS 1 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs, 30},
{"TPMS 2 (OOK)", NULL, 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs, 30},
{"TPMS 3 (GFSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms3_gfsk_async_regs, 30},
{"OOK 40kBaud", NULL, 0, (uint8_t*)protoview_subghz_40k_ook_async_regs, 15},
{"FSK 40kBaud", NULL, 0, (uint8_t*)protoview_subghz_40k_fsk_async_regs, 15},
{NULL, NULL, 0, NULL, 0} /* End of list sentinel. */
};
/* Called after the application initialization in order to setup the
* subghz system and put it into idle state. If the user wants to start
* receiving we will call radio_rx() to start a receiving worker and
* associated thread. */
* subghz system and put it into idle state. */
void radio_begin(ProtoViewApp* app) {
furi_assert(app);
furi_hal_subghz_reset();
@@ -51,13 +52,23 @@ void radio_begin(ProtoViewApp* app) {
} else {
furi_hal_subghz_load_custom_preset(ProtoViewModulations[app->modulation].custom);
}
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
app->txrx->txrx_state = TxRxStateIDLE;
}
/* ================================= Reception ============================== */
/* Setup subghz to start receiving using a background worker. */
/* We avoid the subghz provided abstractions and put the data in our
* simple abstraction: the RawSamples circular buffer. */
void protoview_rx_callback(bool level, uint32_t duration, void* context) {
UNUSED(context);
/* Add data to the circular buffer. */
raw_samples_add(RawSamples, level, duration);
// FURI_LOG_E(TAG, "FEED: %d %d", (int)level, (int)duration);
return;
}
/* Setup the CC1101 to start receiving using a background worker. */
uint32_t radio_rx(ProtoViewApp* app) {
furi_assert(app);
if(!furi_hal_subghz_is_frequency_valid(app->frequency)) {
@@ -69,12 +80,11 @@ uint32_t radio_rx(ProtoViewApp* app) {
furi_hal_subghz_idle(); /* Put it into idle state in case it is sleeping. */
uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_E(TAG, "Switched to frequency: %lu", value);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx();
furi_hal_subghz_rx();
if(!app->txrx->debug_timer_sampling) {
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker);
subghz_worker_start(app->txrx->worker);
furi_hal_subghz_start_async_rx(protoview_rx_callback, NULL);
} else {
raw_sampling_worker_start(app);
}
@@ -82,16 +92,13 @@ uint32_t radio_rx(ProtoViewApp* app) {
return value;
}
/* Stop subghz worker (if active), put radio on idle state. */
/* Stop receiving (if active) and put the radio on idle state. */
void radio_rx_end(ProtoViewApp* app) {
furi_assert(app);
if(app->txrx->txrx_state == TxRxStateRx) {
if(!app->txrx->debug_timer_sampling) {
if(subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker);
furi_hal_subghz_stop_async_rx();
}
furi_hal_subghz_stop_async_rx();
} else {
raw_sampling_worker_stop(app);
}
@@ -104,8 +111,8 @@ void radio_rx_end(ProtoViewApp* app) {
void radio_sleep(ProtoViewApp* app) {
furi_assert(app);
if(app->txrx->txrx_state == TxRxStateRx) {
/* We can't go from having an active RX worker to sleeping.
* Stop the RX subsystems first. */
/* Stop the asynchronous receiving system before putting the
* chip into sleep. */
radio_rx_end(app);
}
furi_hal_subghz_sleep();
@@ -127,8 +134,9 @@ void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder
furi_hal_subghz_idle();
uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_E(TAG, "Switched to frequency: %lu", value);
furi_hal_gpio_write(&gpio_cc1101_g0, false);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(furi_hal_subghz.cc1101_g0_pin, false);
furi_hal_gpio_init(
furi_hal_subghz.cc1101_g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_start_async_tx(data_feeder, ctx);
while(!furi_hal_subghz_is_async_tx_complete()) furi_delay_ms(10);
@@ -149,7 +157,7 @@ void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder
void protoview_timer_isr(void* ctx) {
ProtoViewApp* app = ctx;
bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
bool level = furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin);
if(app->txrx->last_g0_value != level) {
uint32_t now = DWT->CYCCNT;
uint32_t dur = now - app->txrx->last_g0_change_time;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

After

Width:  |  Height:  |  Size: 132 B

@@ -5,7 +5,7 @@ App(
entry_point="protoview_app_entry",
cdefines=["APP_PROTOVIEW"],
requires=["gui"],
stack_size=8 * 1024,
stack_size=8*1024,
order=50,
fap_icon="appicon.png",
fap_category="Tools",
+17
View File
@@ -1,3 +1,6 @@
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license. */
#include <stdint.h>
#include <stddef.h>
@@ -17,3 +20,17 @@ uint8_t crc8(const uint8_t* data, size_t len, uint8_t init, uint8_t poly) {
}
return crc;
}
/* Sum all the specified bytes modulo 256.
* Initialize the sum with 'init' (usually 0). */
uint8_t sum_bytes(const uint8_t* data, size_t len, uint8_t init) {
for(size_t i = 0; i < len; i++) init += data[i];
return init;
}
/* Perform the bitwise xor of all the specified bytes.
* Initialize xor value with 'init' (usually 0). */
uint8_t xor_bytes(const uint8_t* data, size_t len, uint8_t init) {
for(size_t i = 0; i < len; i++) init ^= data[i];
return init;
}
+78 -25
View File
@@ -1,5 +1,4 @@
#include <cc1101.h>
/* ========================== DATA RATE SETTINGS ===============================
*
* This is how to configure registers MDMCFG3 and MDMCFG4.
@@ -131,8 +130,8 @@ static const uint8_t protoview_subghz_tpms2_ook_async_regs[][2] = {
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, /*0x93*/ 0x32}, // Data rate is 10kBaud
{CC1101_MDMCFG4, /*0x18*/ 0x17}, // Rx BW filter is 650.000kHz
{CC1101_MDMCFG3, 0x93}, // Data rate is 10kBaud
{CC1101_MDMCFG4, 0x18}, // Rx BW filter is 650.000kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
@@ -164,8 +163,57 @@ static const uint8_t protoview_subghz_tpms2_ook_async_regs[][2] = {
{0, 0},
{0, 0}};
/* GFSK 19k dev, 325 Khz filter, 20kBaud. Different AGI settings.
* Works well with Toyota. */
static uint8_t protoview_subghz_tpms3_gfsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02}, // 2 is the channel spacing exponet: not used
{CC1101_MDMCFG2, 0x10}, // GFSK without any other check
{CC1101_MDMCFG3, 0x93}, // Data rate is 20kBaud
{CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz
{CC1101_DEVIATN, 0x34}, // Deviation 19.04 Khz.
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0, 0x80},
{CC1101_AGCCTRL1, 0x58},
{CC1101_AGCCTRL2, 0x87},
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
/* End */
{0, 0},
/* CC1101 2FSK PATABLE. */
{0xC0, 0},
{0, 0},
{0, 0},
{0, 0}};
/* 40 KBaud, 2FSK, 28 kHz deviation, 270 Khz bandwidth filter. */
static uint8_t protoview_subghz_tpms3_fsk_async_regs[][2] = {
static uint8_t protoview_subghz_40k_fsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
@@ -215,50 +263,55 @@ static uint8_t protoview_subghz_tpms3_fsk_async_regs[][2] = {
{0, 0},
{0, 0}};
/* FSK 19k dev, 325 Khz filter, 20kBaud. Works well with Toyota. */
static uint8_t protoview_subghz_tpms4_fsk_async_regs[][2] = {
/* This is like the default Flipper OOK 640Khz bandwidth preset, but
* the bandwidth is changed to 40kBaud, in order to receive signals
* with a pulse width ~25us/30us. */
static const uint8_t protoview_subghz_40k_ook_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* FIFO and internals */
{CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02}, // 2 is the channel spacing exponet: not used
{CC1101_MDMCFG2, 0x10}, // GFSK without any other check
{CC1101_MDMCFG3, 0x93}, // Data rate is 20kBaud
{CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz
{CC1101_DEVIATN, 0x34}, // Deviation 19.04 Khz.
// Modem Configuration
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x93}, // Data rate is 40kBaud
{CC1101_MDMCFG4, 0x1A}, // Rx BW filter is 650.000kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0, 0x80},
{CC1101_AGCCTRL1, 0x58},
{CC1101_AGCCTRL2, 0x87},
{CC1101_AGCCTRL0,
0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
{CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1]
{CC1101_FREND1, 0xB6}, //
/* End */
{0, 0},
/* CC1101 2FSK PATABLE. */
{0xC0, 0},
/* CC1101 OOK PATABLE. */
{0, 0xC0},
{0, 0},
{0, 0},
{0, 0}};
-115
View File
@@ -1,115 +0,0 @@
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license. */
#include <inttypes.h>
#include <lib/flipper_format/flipper_format_i.h>
#include <furi/core/string.h>
#include <lib/subghz/registry.h>
#include <lib/subghz/protocols/base.h>
#include "raw_samples.h"
#define TAG "PROTOVIEW-protocol"
const SubGhzProtocol subghz_protocol_protoview;
/* The feed() method puts data in the RawSamples global (protected by
* a mutex). */
extern RawSamplesBuffer* RawSamples;
/* This is totally dummy: we just define the decoder base for the async
* system to work but we don't really use it if not to collect raw
* data via the feed() method. */
typedef struct SubGhzProtocolDecoderprotoview {
SubGhzProtocolDecoderBase base;
} SubGhzProtocolDecoderprotoview;
void* subghz_protocol_decoder_protoview_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderprotoview* instance = malloc(sizeof(SubGhzProtocolDecoderprotoview));
instance->base.protocol = &subghz_protocol_protoview;
return instance;
}
void subghz_protocol_decoder_protoview_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderprotoview* instance = context;
free(instance);
}
void subghz_protocol_decoder_protoview_reset(void* context) {
furi_assert(context);
}
/* That's the only thig we really use of the protocol decoder
* implementation. We avoid the subghz provided abstractions and put
* the data in our simple abstraction: the RawSamples circular buffer. */
void subghz_protocol_decoder_protoview_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
UNUSED(context);
/* Add data to the circular buffer. */
raw_samples_add(RawSamples, level, duration);
// FURI_LOG_E(TAG, "FEED: %d %d", (int)level, (int)duration);
return;
}
/* The only scope of this method is to avoid duplicated messages in the
* Subghz history, which we don't use. */
uint8_t subghz_protocol_decoder_protoview_get_hash_data(void* context) {
furi_assert(context);
return 123;
}
/* Not used. */
bool subghz_protocol_decoder_protoview_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
UNUSED(context);
UNUSED(flipper_format);
UNUSED(preset);
return false;
}
/* Not used. */
bool subghz_protocol_decoder_protoview_deserialize(void* context, FlipperFormat* flipper_format) {
UNUSED(context);
UNUSED(flipper_format);
return false;
}
void subhz_protocol_decoder_protoview_get_string(void* context, FuriString* output) {
furi_assert(context);
furi_string_cat_printf(output, "Protoview");
}
const SubGhzProtocolDecoder subghz_protocol_protoview_decoder = {
.alloc = subghz_protocol_decoder_protoview_alloc,
.free = subghz_protocol_decoder_protoview_free,
.reset = subghz_protocol_decoder_protoview_reset,
.feed = subghz_protocol_decoder_protoview_feed,
.get_hash_data = subghz_protocol_decoder_protoview_get_hash_data,
.serialize = subghz_protocol_decoder_protoview_serialize,
.deserialize = subghz_protocol_decoder_protoview_deserialize,
.get_string = subhz_protocol_decoder_protoview_get_string,
};
/* Well, we don't really target a specific protocol. So let's put flags
* that make sense. */
const SubGhzProtocol subghz_protocol_protoview = {
.name = "Protoview",
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
.decoder = &subghz_protocol_protoview_decoder,
};
/* Our table has just the single dummy protocol we defined for the
* sake of data collection. */
const SubGhzProtocol* protoview_protocol_registry_items[] = {
&subghz_protocol_protoview,
};
const SubGhzProtocolRegistry protoview_protocol_registry = {
.items = protoview_protocol_registry_items,
.size = COUNT_OF(protoview_protocol_registry_items)};
+9 -4
View File
@@ -314,11 +314,16 @@ void fieldset_add_bin(ProtoViewFieldSet* fs, const char* name, uint64_t uval, ui
fieldset_add_field(fs, f);
}
/* Allocate and append a string field. */
void fieldset_add_str(ProtoViewFieldSet* fs, const char* name, const char* s) {
/* Allocate and append a string field. The string 's' does not need to point
* to a null terminated string, but must have at least 'len' valid bytes
* starting from the pointer. The field object will be correctly null
* terminated. */
void fieldset_add_str(ProtoViewFieldSet* fs, const char* name, const char* s, size_t len) {
ProtoViewField* f = field_new(FieldTypeStr, name);
f->str = strdup(s);
f->len = strlen(s);
f->len = len;
f->str = malloc(len + 1);
memcpy(f->str, s, len);
f->str[len] = 0;
fieldset_add_field(fs, f);
}
@@ -1,4 +1,7 @@
/* PT/SC remotes. Usually 443.92 Mhz OOK.
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* PT/SC remotes. Usually 443.92 Mhz OOK.
*
* This line code is used in many remotes such as Princeton chips
* named PT2262, Silian Microelectronics SC5262 and others.
@@ -11,10 +14,15 @@
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if(numbits < 30) return false;
const char* sync_patterns[3] = {
"10000000000000000000000000000001", /* 30 zero bits. */
"100000000000000000000000000000001", /* 31 zero bits. */
"1000000000000000000000000000000001", /* 32 zero bits. */
/* Test different pulse + gap + first byte possibilities. */
const char* sync_patterns[6] = {
"100000000000000000000000000000011101", /* 30 times gap + one. */
"100000000000000000000000000000010001", /* 30 times gap + zero. */
"1000000000000000000000000000000011101", /* 31 times gap + one. */
"1000000000000000000000000000000010001", /* 31 times gap + zero. */
"10000000000000000000000000000000011101", /* 32 times gap + one. */
"10000000000000000000000000000000010001", /* 32 times gap + zero. */
};
uint32_t off;
@@ -24,11 +32,11 @@ static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoView
if(off != BITMAP_SEEK_NOT_FOUND) break;
}
if(off == BITMAP_SEEK_NOT_FOUND) return false;
if(DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu", off);
if(DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble id:%d at: %lu", j, off);
info->start_off = off;
// Seek data setction. Why -1? Last bit is data.
off += strlen(sync_patterns[j]) - 1;
// Seek data setction. Why -5? Last 5 half-bit-times are data.
off += strlen(sync_patterns[j]) - 5;
uint8_t d[3]; /* 24 bits of data. */
uint32_t decoded = convert_from_line_code(d, sizeof(d), bits, numbytes, off, "1000", "1110");
@@ -1,4 +1,7 @@
/* Microchip HCS200/HCS300/HSC301 KeeLoq, rolling code remotes.
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Microchip HCS200/HCS300/HSC301 KeeLoq, rolling code remotes.
*
* Usually 443.92 Mhz OOK, ~200us or ~400us pulse len, depending
* on the configuration.
@@ -1,4 +1,7 @@
/* Oregon remote termometers. Usually 443.92 Mhz OOK.
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Oregon remote termometers. Usually 443.92 Mhz OOK.
*
* The protocol is described here:
* https://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf
@@ -0,0 +1,205 @@
#include "../app.h"
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* ----------------------------------------------------------
* ProtoView chat protocol. This is just a fun test protocol
* that can be used between two Flippers in order to send
* and receive text messages.
* ----------------------------------------------------------
*
* Protocol description
* ====================
*
* The protocol works with different data rates. However here is defined
* to use a short pulse/gap duration of 300us and a long pulse/gap
* duration of 600us. Even with the Flipper hardware, the protocol works
* with 100/200us, but becomes less reliable and standard presets can't
* be used because of the higher data rate.
*
* In the following description we have that:
*
* "1" represents a pulse of one-third bit time (300us)
* "0" represents a gap of one-third bit time (300us)
*
* The message starts with a preamble + a sync pattern:
*
* preamble = 1010101010101010 x 3
* sync = 1100110011001010
*
* The a variable amount of bytes follow, where each bit
* is encoded in the following way:
*
* zero 100 (300 us pulse, 600 us gap)
* one 110 (600 us pulse, 300 us gap)
*
* Bytes are sent MSB first, so receiving, in sequence, bits
* 11100001, means byte E1.
*
* This is the data format:
*
* +--+------+-------+--+--+--+
* |SL|Sender|Message|FF|AA|CS|
* +--+------+-------+--+--+--+
* | | |
* | | \_ N bytes of message terminated by FF AA + 1 byte of checksum
* | |
* | \_ SL bytes of sender name
* \
* \_ 1 byte of sender len, 8 bit unsigned integer.
*
*
* Checksum = sum of bytes modulo 256, with checksum set
* to 0 for the computation.
*
* Design notes
* ============
*
* The protocol is designed in order to have certain properties:
*
* 1. Pulses and gaps can only be 100 or 200 microseconds, so the
* message can be described, encoded and decoded with only two
* fixed durations.
*
* 2. The preamble + sync is designed to have a well recognizable
* pattern that can't be reproduced just for accident inside
* the encoded pattern. There is no combinatio of encoded bits
* leading to the preamble+sync. Also the sync pattern final
* part can't be mistaken for actual bits of data, since it
* contains alternating short pulses/gaps at 100us.
*
* 3. Data encoding wastes some bandwidth in order to be more
* robust. Even so, with a 300us clock period, a single bit
* bit takes 900us, reaching a data transfer of 138 characters per
* second. More than enough for the simple chat we have here.
*/
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
const char* sync_pattern = "1010101010101010" // Preamble
"1100110011001010"; // Sync
uint8_t sync_len = 32;
/* This is a variable length message, however the minimum length
* requires a sender len byte (of value zero) and the terminator
* FF 00 plus checksum: a total of 4 bytes. */
if(numbits - sync_len < 8 * 4) return false;
uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Chat preamble+sync found");
/* If there is room on the left, let's mark the start of the message
* a bit before: we don't try to detect all the preamble, but only
* the first part, however it is likely present. */
if(off >= 16) {
off -= 16;
sync_len += 16;
}
info->start_off = off;
off += sync_len; /* Skip preamble and sync. */
uint8_t raw[64] = {(uint8_t)'.'};
uint32_t decoded =
convert_from_line_code(raw, sizeof(raw), bits, numbytes, off, "100", "110"); /* PWM */
FURI_LOG_E(TAG, "Chat decoded bits: %lu", decoded);
if(decoded < 8 * 4) return false; /* Min message len. */
// The message needs to have a two bytes terminator before
// the checksum.
uint32_t j;
for(j = 0; j < sizeof(raw) - 1; j++)
if(raw[j] == 0xff && raw[j + 1] == 0xaa) break;
if(j == sizeof(raw) - 1) {
FURI_LOG_E(TAG, "Chat: terminator not found");
return false; // No terminator found.
}
uint32_t datalen = j + 3; // If the terminator was found at j, then
// we need to sum three more bytes to have
// the len: FF itself, AA, checksum.
info->pulses_count = sync_len + 8 * 3 * datalen;
// Check if the control sum matches.
if(sum_bytes(raw, datalen - 1, 0) != raw[datalen - 1]) {
FURI_LOG_E(TAG, "Chat: checksum mismatch");
return false;
}
// Check if the length of the sender looks sane
uint8_t senderlen = raw[0];
if(senderlen >= sizeof(raw)) {
FURI_LOG_E(TAG, "Chat: invalid sender length");
return false; // Overflow
}
fieldset_add_str(info->fieldset, "sender", (char*)raw + 1, senderlen);
fieldset_add_str(
info->fieldset, "message", (char*)raw + 1 + senderlen, datalen - senderlen - 4);
return true;
}
/* Give fields and defaults for the signal creator. */
static void get_fields(ProtoViewFieldSet* fieldset) {
fieldset_add_str(fieldset, "sender", "Carol", 5);
fieldset_add_str(fieldset, "message", "Anyone hearing?", 15);
}
/* Create a signal. */
static void build_message(RawSamplesBuffer* samples, ProtoViewFieldSet* fs) {
uint32_t te = 300; /* Short pulse duration in microseconds.
Our protocol needs three symbol times to send
a bit, so 300 us per bit = 3.33 kBaud. */
// Preamble: 24 alternating 300us pulse/gap pairs.
for(int j = 0; j < 24; j++) {
raw_samples_add(samples, true, te);
raw_samples_add(samples, false, te);
}
// Sync: 3 alternating 600 us pulse/gap pairs.
for(int j = 0; j < 3; j++) {
raw_samples_add(samples, true, te * 2);
raw_samples_add(samples, false, te * 2);
}
// Sync: plus 2 alternating 300 us pluse/gap pairs.
for(int j = 0; j < 2; j++) {
raw_samples_add(samples, true, te);
raw_samples_add(samples, false, te);
}
// Data: build the array.
uint32_t datalen = 1 + fs->fields[0]->len + // Userlen + Username
fs->fields[1]->len + 3; // Message + FF + 00 + CRC
uint8_t *data = malloc(datalen), *p = data;
*p++ = fs->fields[0]->len;
memcpy(p, fs->fields[0]->str, fs->fields[0]->len);
p += fs->fields[0]->len;
memcpy(p, fs->fields[1]->str, fs->fields[1]->len);
p += fs->fields[1]->len;
*p++ = 0xff;
*p++ = 0xaa;
*p = sum_bytes(data, datalen - 1, 0);
// Emit bits
for(uint32_t j = 0; j < datalen * 8; j++) {
if(bitmap_get(data, datalen, j)) {
raw_samples_add(samples, true, te * 2);
raw_samples_add(samples, false, te);
} else {
raw_samples_add(samples, true, te);
raw_samples_add(samples, false, te * 2);
}
}
free(data);
}
ProtoViewDecoder ProtoViewChatDecoder = {
.name = "ProtoView chat",
.decode = decode,
.get_fields = get_fields,
.build_message = build_message};
@@ -1,4 +1,7 @@
/* Citroen TPMS. Usually 443.92 Mhz FSK.
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Citroen TPMS. Usually 443.92 Mhz FSK.
*
* Preamble of ~14 high/low 52 us pulses
* Sync of high 100us pulse then 50us low
@@ -1,4 +1,7 @@
/* Ford tires TPMS. Usually 443.92 Mhz FSK (in Europe).
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Ford tires TPMS. Usually 443.92 Mhz FSK (in Europe).
*
* 52 us short pules
* Preamble: 0101010101010101010101010101
@@ -1,4 +1,7 @@
/* Renault tires TPMS. Usually 443.92 Mhz FSK.
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Renault tires TPMS. Usually 443.92 Mhz FSK.
*
* Preamble + sync + Manchester bits. ~48us short pulse.
* 9 Bytes in total not counting the preamble. */
@@ -1,4 +1,7 @@
/* Schrader TPMS. Usually 443.92 Mhz OOK, 120us pulse len.
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Schrader TPMS. Usually 443.92 Mhz OOK, 120us pulse len.
*
* 500us high pulse + Preamble + Manchester coded bits where:
* 1 = 10
@@ -1,4 +1,7 @@
/* Schrader variant EG53MA4 TPMS.
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Schrader variant EG53MA4 TPMS.
* Usually 443.92 Mhz OOK, 100us pulse len.
*
* Preamble: alternating pulse/gap, 100us.
@@ -1,4 +1,7 @@
/* Toyota tires TPMS. Usually 443.92 Mhz FSK (In Europe).
/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* Toyota tires TPMS. Usually 443.92 Mhz FSK (In Europe).
*
* Preamble + sync + 64 bits of data. ~48us short pulse length.
*
@@ -0,0 +1,326 @@
#include "../app.h"
/* Copyright (C) 2023 Salvatore Sanfilippo -- All Rights Reserved
* See the LICENSE file for information about the license.
*
* ----------------------------------------------------------------------------
* The "unknown" decoder fires as the last one, once we are sure no other
* decoder was able to identify the signal. The goal is to detect the
* preamble and line code used in the received signal, then turn the
* decoded bits into bytes.
*
* The techniques used for the detection are described in the comments
* below.
* ----------------------------------------------------------------------------
*/
/* Scan the signal bitmap looking for a PWM modulation. In this case
* for PWM we are referring to two exact patterns of high and low
* signal (each bit in the bitmap is worth the smallest gap/pulse duration
* we detected) that repeat each other in a given segment of the message.
*
* This modulation is quite common, for instance sometimes zero and
* one are rappresented by a 700us pulse followed by 350 gap,
* and 350us pulse followed by a 700us gap. So the signal bitmap received
* by the decoder would contain 110 and 100 symbols.
*
* The way this function work is commented inline.
*
* The function returns the number of consecutive symbols found, having
* a symbol length of 'symlen' (3 in the above example), and stores
* in *s1i the offset of the first symbol found, and in *s2i the offset
* of the second symbol. The function can't tell which is one and which
* zero. */
static uint32_t find_pwm(
uint8_t* bits,
uint32_t numbytes,
uint32_t numbits,
uint32_t symlen,
uint32_t* s1i,
uint32_t* s2i) {
uint32_t best_count = 0; /* Max number of symbols found in this try. */
uint32_t best_idx1 = 0; /* First symbol offset of longest sequence found.
* This is also the start sequence offset. */
uint32_t best_idx2 = 0; /* Second symbol offset. */
/* Try all the possible symbol offsets that are less of our
* symbol len. This is likely not really useful but we take
* a conservative approach. Because if have have, for instance,
* repeating symbols "100" and "110", they will form a sequence
* that is choerent at different offsets, but out-of-sync.
*
* Anyway at the end of the function we try to fix the sync. */
for(uint32_t off = 0; off < symlen; off++) {
uint32_t c = 0; // Number of contiguous symbols found.
uint32_t c1 = 0, c2 = 0; // Occurrences of first/second symbol.
*s1i = off; // Assume we start at one symbol boundaty.
*s2i = UINT32_MAX; // Second symbol first index still unknown.
uint32_t next = off;
/* We scan the whole bitmap in one pass, resetting the state
* each time we find a pattern that is not one of the two
* symbols we found so far. */
while(next < numbits - symlen) {
bool match1 = bitmap_match_bitmap(bits, numbytes, next, bits, numbytes, *s1i, symlen);
if(!match1 && *s2i == UINT32_MAX) {
/* It's not the first sybol. We don't know how the
* second look like. Assume we found an occurrence of
* the second symbol. */
*s2i = next;
}
bool match2 = bitmap_match_bitmap(bits, numbytes, next, bits, numbytes, *s2i, symlen);
/* One or the other should match. */
if(match1 || match2) {
c++;
if(match1) c1++;
if(match2) c2++;
if(c > best_count && c1 >= best_count / 5 && // Require enough presence of both
c2 >= best_count / 5) // zero and one.
{
best_count = c;
best_idx1 = *s1i;
best_idx2 = *s2i;
}
next += symlen;
} else {
/* No match. Continue resetting the signal info. */
c = 0; // Start again to count contiguous symbols.
c1 = 0;
c2 = 0;
*s1i = next; // First symbol always at start.
*s2i = UINT32_MAX; // Second symbol unknown.
}
}
}
/* We don't know if we are really synchronized with the bits at this point.
* For example if zero bit is 100 and one bit is 110 in a specific
* line code, our detector could randomly believe it's 001 and 101.
* However PWD line codes normally start with a pulse in both symbols.
* If that is the case, let's align. */
uint32_t shift;
for(shift = 0; shift < symlen; shift++) {
if(bitmap_get(bits, numbytes, best_idx1 + shift) &&
bitmap_get(bits, numbytes, best_idx2 + shift))
break;
}
if(shift != symlen) {
best_idx1 += shift;
best_idx2 += shift;
}
*s1i = best_idx1;
*s2i = best_idx2;
return best_count;
}
/* Find the longest sequence that looks like Manchester coding.
*
* Manchester coding requires each pairs of bits to be either
* 01 or 10. We'll have to try odd and even offsets to be
* sure to find it.
*
* Note that this will also detect differential Manchester, but
* will report it as Manchester. I can't think of any way to
* distinguish between the two line codes, because shifting them
* one symbol will make one to look like the other.
*
* Only option could be to decode the message with both line
* codes and use statistical properties (common byte values)
* to determine what's more likely, but this looks very fragile.
*
* Fortunately differential Manchester is more rarely used,
* so we can assume Manchester most of the times. Yet we are left
* with the indetermination about zero being pulse-gap or gap-pulse
* or the other way around.
*
* If the 'only_raising' parameter is true, the function detects
* only sequences going from gap to pulse: this is useful in order
* to locate preambles of alternating gaps and pulses. */
static uint32_t find_alternating_bits(
uint8_t* bits,
uint32_t numbytes,
uint32_t numbits,
uint32_t* start,
bool only_raising) {
uint32_t best_count = 0; // Max number of symbols found
uint32_t best_off = 0; // Max symbols start offset.
for(int odd = 0; odd < 2; odd++) {
uint32_t count = 0; // Symbols found so far
uint32_t start_off = odd;
uint32_t j = odd;
while(j < numbits - 1) {
bool bit1 = bitmap_get(bits, numbytes, j);
bool bit2 = bitmap_get(bits, numbytes, j + 1);
if((!only_raising && bit1 != bit2) || (only_raising && !bit1 && bit2)) {
count++;
if(count > best_count) {
best_count = count;
best_off = start_off;
}
} else {
/* End of sequence. Continue with the next
* part of the signal. */
count = 0;
start_off = j + 2;
}
j += 2;
}
}
*start = best_off;
return best_count;
}
/* Wrapper to find Manchester code. */
static uint32_t
find_manchester(uint8_t* bits, uint32_t numbytes, uint32_t numbits, uint32_t* start) {
return find_alternating_bits(bits, numbytes, numbits, start, false);
}
/* Wrapper to find preamble sections. */
static uint32_t
find_preamble(uint8_t* bits, uint32_t numbytes, uint32_t numbits, uint32_t* start) {
return find_alternating_bits(bits, numbytes, numbits, start, true);
}
typedef enum {
LineCodeNone,
LineCodeManchester,
LineCodePWM3,
LineCodePWM4,
} LineCodeGuess;
static char* get_linecode_name(LineCodeGuess lc) {
switch(lc) {
case LineCodeNone:
return "none";
case LineCodeManchester:
return "Manchester";
case LineCodePWM3:
return "PWM3";
case LineCodePWM4:
return "PWM4";
}
return "unknown";
}
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
/* No decoder was able to detect this message. Let's try if we can
* find some structure. To start, we'll see if it looks like is
* manchester coded, or PWM with symbol len of 3 or 4. */
/* For PWM, start1 and start2 are the offsets at which the two
* sequences composing the message appear the first time.
* So start1 is also the message start offset. Start2 is not used
* for Manchester, that does not have two separated symbols like
* PWM. */
uint32_t start1 = 0, start2 = 0;
uint32_t msgbits; // Number of message bits in the bitmap, so
// this will be the number of symbols, not actual
// bits after the message is decoded.
uint32_t tmp1, tmp2; // Temp vars to store the start.
uint32_t minbits = 16; // Less than that gets undetected.
uint32_t pwm_len; // Bits per symbol, in the case of PWM.
LineCodeGuess linecode = LineCodeNone;
// Try PWM3
uint32_t pwm3_bits = find_pwm(bits, numbytes, numbits, 3, &tmp1, &tmp2);
if(pwm3_bits >= minbits) {
linecode = LineCodePWM3;
start1 = tmp1;
start2 = tmp2;
pwm_len = 3;
msgbits = pwm3_bits * pwm_len;
}
// Try PWM4
uint32_t pwm4_bits = find_pwm(bits, numbytes, numbits, 4, &tmp1, &tmp2);
if(pwm4_bits >= minbits && pwm4_bits > pwm3_bits) {
linecode = LineCodePWM4;
start1 = tmp1;
start2 = tmp2;
pwm_len = 4;
msgbits = pwm3_bits * pwm_len;
}
// Try Manchester
uint32_t manchester_bits = find_manchester(bits, numbytes, numbits, &tmp1);
if(manchester_bits > minbits && manchester_bits > pwm3_bits && manchester_bits > pwm4_bits) {
linecode = LineCodeManchester;
start1 = tmp1;
msgbits = manchester_bits * 2;
FURI_LOG_E(TAG, "MANCHESTER START: %lu", tmp1);
}
if(linecode == LineCodeNone) return false;
/* Often there is a preamble before the signal. We'll try to find
* it, and if it is not too far away from our signal, we'll claim
* our signal starts at the preamble. */
uint32_t preamble_len = find_preamble(bits, numbytes, numbits, &tmp1);
uint32_t min_preamble_len = 10;
uint32_t max_preamble_distance = 32;
uint32_t preamble_start = 0;
bool preamble_found = false;
/* Note that because of the following checks, if the Manchester detector
* detected the preamble bits as data, we are ok with that, since it
* means that the synchronization is not designed to "break" the bits
* flow. In this case we ignore the preamble and return all as data. */
if(preamble_len >= min_preamble_len && // Not too short.
tmp1 < start1 && // Should be before the data.
start1 - tmp1 <= max_preamble_distance) // Not too far.
{
preamble_start = tmp1;
preamble_found = true;
}
info->start_off = preamble_found ? preamble_start : start1;
info->pulses_count = (start1 + msgbits) - info->start_off;
info->pulses_count += 20; /* Add a few more, so that if the user resends
* the message, it is more likely we will
* transfer all that is needed, like a message
* terminator (that we don't detect). */
if(preamble_found) FURI_LOG_E(TAG, "PREAMBLE AT: %lu", preamble_start);
FURI_LOG_E(TAG, "START: %lu", info->start_off);
FURI_LOG_E(TAG, "MSGBITS: %lu", msgbits);
FURI_LOG_E(TAG, "DATASTART: %lu", start1);
FURI_LOG_E(TAG, "PULSES: %lu", info->pulses_count);
/* We think there is a message and we know where it starts and the
* line code used. We can turn it into bits and bytes. */
uint32_t decoded;
uint8_t data[32];
uint32_t datalen;
char symbol1[5], symbol2[5];
if(linecode == LineCodePWM3 || linecode == LineCodePWM4) {
bitmap_to_string(symbol1, bits, numbytes, start1, pwm_len);
bitmap_to_string(symbol2, bits, numbytes, start2, pwm_len);
} else if(linecode == LineCodeManchester) {
memcpy(symbol1, "01", 3);
memcpy(symbol2, "10", 3);
}
decoded = convert_from_line_code(data, sizeof(data), bits, numbytes, start1, symbol1, symbol2);
datalen = (decoded + 7) / 8;
char* linecode_name = get_linecode_name(linecode);
fieldset_add_str(info->fieldset, "line code", linecode_name, strlen(linecode_name));
fieldset_add_uint(info->fieldset, "data bits", decoded, 8);
if(preamble_found) fieldset_add_uint(info->fieldset, "preamble len", preamble_len, 8);
fieldset_add_str(info->fieldset, "first symbol", symbol1, strlen(symbol1));
fieldset_add_str(info->fieldset, "second symbol", symbol2, strlen(symbol2));
for(uint32_t j = 0; j < datalen; j++) {
char label[16];
snprintf(label, sizeof(label), "data[%lu]", j);
fieldset_add_bytes(info->fieldset, label, data + j, 2);
}
return true;
}
ProtoViewDecoder UnknownDecoder =
{.name = "Unknown", .decode = decode, .get_fields = NULL, .build_message = NULL};
+83 -36
View File
@@ -5,6 +5,43 @@
bool decode_signal(RawSamplesBuffer* s, uint64_t len, ProtoViewMsgInfo* info);
/* =============================================================================
* Protocols table.
*
* Supported protocols go here, with the relevant implementation inside
* protocols/<name>.c
* ===========================================================================*/
extern ProtoViewDecoder Oregon2Decoder;
extern ProtoViewDecoder B4B1Decoder;
extern ProtoViewDecoder RenaultTPMSDecoder;
extern ProtoViewDecoder ToyotaTPMSDecoder;
extern ProtoViewDecoder SchraderTPMSDecoder;
extern ProtoViewDecoder SchraderEG53MA4TPMSDecoder;
extern ProtoViewDecoder CitroenTPMSDecoder;
extern ProtoViewDecoder FordTPMSDecoder;
extern ProtoViewDecoder KeeloqDecoder;
extern ProtoViewDecoder ProtoViewChatDecoder;
extern ProtoViewDecoder UnknownDecoder;
ProtoViewDecoder* Decoders[] = {
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */
&ToyotaTPMSDecoder, /* Toyota TPMS. */
&SchraderTPMSDecoder, /* Schrader TPMS. */
&SchraderEG53MA4TPMSDecoder, /* Schrader EG53MA4 TPMS. */
&CitroenTPMSDecoder, /* Citroen TPMS. */
&FordTPMSDecoder, /* Ford TPMS. */
&KeeloqDecoder, /* Keeloq remote. */
&ProtoViewChatDecoder, /* Protoview simple text messages. */
/* Warning: the following decoder must stay at the end of the
* list. Otherwise would detect most signals and prevent the actaul
* decoders from handling them. */
&UnknownDecoder, /* General protocol detector. */
NULL};
/* =============================================================================
* Raw signal detection
* ===========================================================================*/
@@ -39,23 +76,28 @@ void reset_current_signal(ProtoViewApp* app) {
* For instance Oregon2 sensors, in the case of protocol 2.1 will send
* pulses of ~400us (RF on) VS ~580us (RF off). */
#define SEARCH_CLASSES 3
uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx) {
uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx, uint32_t min_duration) {
struct {
uint32_t dur[2]; /* dur[0] = low, dur[1] = high */
uint32_t count[2]; /* Associated observed frequency. */
} classes[SEARCH_CLASSES];
memset(classes, 0, sizeof(classes));
uint32_t minlen = 30, maxlen = 4000; /* Depends on data rate, here we
allow for high and low. */
// Set a min/max duration limit for samples to be considered part of a
// coherent signal. The maximum length is fixed while the minimum
// is passed as argument, as depends on the data rate and in general
// on the signal to analyze.
uint32_t max_duration = 4000;
uint32_t len = 0; /* Observed len of coherent samples. */
s->short_pulse_dur = 0;
for(uint32_t j = idx; j < idx + 500; j++) {
for(uint32_t j = idx; j < idx + s->total; j++) {
bool level;
uint32_t dur;
raw_samples_get(s, j, &level, &dur);
if(dur < minlen || dur > maxlen) break; /* return. */
if(dur < min_duration || dur > max_duration) break; /* return. */
/* Let's see if it matches a class we already have or if we
* can populate a new (yet empty) class. */
@@ -142,7 +184,7 @@ void notify_signal_detected(ProtoViewApp* app, bool decoded) {
* in order to find a coherent signal. If a signal that does not appear to
* be just noise is found, it is set in DetectedSamples global signal
* buffer, that is what is rendered on the screen. */
void scan_for_signal(ProtoViewApp* app, RawSamplesBuffer* source) {
void scan_for_signal(ProtoViewApp* app, RawSamplesBuffer* source, uint32_t min_duration) {
/* We need to work on a copy: the source buffer may be populated
* by the background thread receiving data. */
RawSamplesBuffer* copy = raw_samples_alloc();
@@ -157,7 +199,7 @@ void scan_for_signal(ProtoViewApp* app, RawSamplesBuffer* source) {
uint32_t i = 0;
while(i < copy->total - 1) {
uint32_t thislen = search_coherent_signal(copy, i);
uint32_t thislen = search_coherent_signal(copy, i, min_duration);
/* For messages that are long enough, attempt decoding. */
if(thislen > minlen) {
@@ -179,8 +221,11 @@ void scan_for_signal(ProtoViewApp* app, RawSamplesBuffer* source) {
/* Accept this signal as the new signal if either it's longer
* than the previous undecoded one, or the previous one was
* unknown and this is decoded. */
if((thislen > app->signal_bestlen && app->signal_decoded == false) ||
(app->signal_decoded == false && decoded)) {
bool oldsignal_not_decoded = app->signal_decoded == false ||
app->msg_info->decoder == &UnknownDecoder;
if(oldsignal_not_decoded &&
(thislen > app->signal_bestlen || (decoded && info->decoder != &UnknownDecoder))) {
free_msg_info(app->msg_info);
app->msg_info = info;
app->signal_bestlen = thislen;
@@ -194,7 +239,7 @@ void scan_for_signal(ProtoViewApp* app, RawSamplesBuffer* source) {
DetectedSamples->short_pulse_dur);
adjust_raw_view_scale(app, DetectedSamples->short_pulse_dur);
notify_signal_detected(app, decoded);
if(app->msg_info->decoder != &UnknownDecoder) notify_signal_detected(app, decoded);
} else {
/* If the structure was not filled, discard it. Otherwise
* now the owner is app->msg_info. */
@@ -387,6 +432,33 @@ uint32_t bitmap_seek_bits(
return BITMAP_SEEK_NOT_FOUND;
}
/* Compare bitmaps b1 and b2 (possibly overlapping or the same bitmap),
* at the specified offsets, for cmplen bits. Returns true if the
* exact same bits are found, otherwise false. */
bool bitmap_match_bitmap(
uint8_t* b1,
uint32_t b1len,
uint32_t b1off,
uint8_t* b2,
uint32_t b2len,
uint32_t b2off,
uint32_t cmplen) {
for(uint32_t j = 0; j < cmplen; j++) {
bool bit1 = bitmap_get(b1, b1len, b1off + j);
bool bit2 = bitmap_get(b2, b2len, b2off + j);
if(bit1 != bit2) return false;
}
return true;
}
/* Convert 'len' bitmap bits of the bitmap 'bitmap' into a null terminated
* string, stored at 'dst', that must have space at least for len+1 bytes.
* The bits are extracted from the specified offset. */
void bitmap_to_string(char* dst, uint8_t* b, uint32_t blen, uint32_t off, uint32_t len) {
for(uint32_t j = 0; j < len; j++) dst[j] = bitmap_get(b, blen, off + j) ? '1' : '0';
dst[len] = 0;
}
/* Set the pattern 'pat' into the bitmap 'b' of max length 'blen' bytes,
* starting from the specified offset.
*
@@ -503,7 +575,7 @@ uint32_t convert_from_line_code(
}
/* Convert the differential Manchester code to bits. This is similar to
* convert_from_line_code() but specific for Manchester. The user must
* convert_from_line_code() but specific for diff-Manchester. The user must
* supply the value of the previous symbol before this stream, since
* in differential codings the next bits depend on the previous one.
*
@@ -528,31 +600,6 @@ uint32_t convert_from_diff_manchester(
return decoded;
}
/* Supported protocols go here, with the relevant implementation inside
* protocols/<name>.c */
extern ProtoViewDecoder Oregon2Decoder;
extern ProtoViewDecoder B4B1Decoder;
extern ProtoViewDecoder RenaultTPMSDecoder;
extern ProtoViewDecoder ToyotaTPMSDecoder;
extern ProtoViewDecoder SchraderTPMSDecoder;
extern ProtoViewDecoder SchraderEG53MA4TPMSDecoder;
extern ProtoViewDecoder CitroenTPMSDecoder;
extern ProtoViewDecoder FordTPMSDecoder;
extern ProtoViewDecoder KeeloqDecoder;
ProtoViewDecoder* Decoders[] = {
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */
&ToyotaTPMSDecoder, /* Toyota TPMS. */
&SchraderTPMSDecoder, /* Schrader TPMS. */
&SchraderEG53MA4TPMSDecoder, /* Schrader EG53MA4 TPMS. */
&CitroenTPMSDecoder, /* Citroen TPMS. */
&FordTPMSDecoder, /* Ford TPMS. */
&KeeloqDecoder, /* Keeloq remote. */
NULL};
/* Free the message info and allocated data. */
void free_msg_info(ProtoViewMsgInfo* i) {
if(i == NULL) return;
+1 -1
View File
@@ -205,7 +205,7 @@ static void process_input_set_fields(ProtoViewApp* app, InputEvent input) {
privdata->decoder->build_message(rs, privdata->fieldset);
app->signal_decoded = false; // So that the new signal will be
// accepted as the current signal.
scan_for_signal(app, rs);
scan_for_signal(app, rs, 5);
raw_samples_free(rs);
ui_show_alert(app, "Done: press back key", 3000);
}
@@ -2,63 +2,161 @@
* See the LICENSE file for information about the license. */
#include "app.h"
#include <cc1101.h>
static void direct_sampling_timer_start(ProtoViewApp* app);
static void direct_sampling_timer_stop(ProtoViewApp* app);
#define CAPTURED_BITMAP_BITS (128 * 64)
#define CAPTURED_BITMAP_BYTES (CAPTURED_BITMAP_BITS / 8)
#define DEFAULT_USEC_PER_PIXEL 50
#define USEC_PER_PIXEL_SMALL_CHANGE 5
#define USEC_PER_PIXEL_LARGE_CHANGE 25
#define USEC_PER_PIXEL_MIN 5
#define USEC_PER_PIXEL_MAX 300
typedef struct {
uint8_t* captured; // Bitmap with the last captured screen.
uint32_t captured_idx; // Current index to write into the bitmap
uint32_t usec_per_pixel; // Number of useconds a pixel should represent
bool show_usage_info;
} DirectSamplingViewPrivData;
/* Read directly from the G0 CC1101 pin, and draw a black or white
* dot depending on the level. */
void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app) {
if(!app->direct_sampling_enabled) {
DirectSamplingViewPrivData* privdata = app->view_privdata;
if(!app->direct_sampling_enabled && privdata->show_usage_info) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 9, "Direct sampling is a special");
canvas_draw_str(canvas, 2, 18, "mode that displays the signal");
canvas_draw_str(canvas, 2, 27, "captured in real time. Like in");
canvas_draw_str(canvas, 2, 36, "a old CRT TV. It's very slow.");
canvas_draw_str(canvas, 2, 45, "Can crash your Flipper.");
canvas_draw_str(canvas, 2, 9, "Direct sampling displays the");
canvas_draw_str(canvas, 2, 18, "the captured signal in real");
canvas_draw_str(canvas, 2, 27, "time, like in a CRT TV set.");
canvas_draw_str(canvas, 2, 36, "Use UP/DOWN to change the");
canvas_draw_str(canvas, 2, 45, "resolution (usec/pixel).");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 14, 60, "To enable press OK");
canvas_draw_str(canvas, 5, 60, "To start/stop, press OK");
return;
}
privdata->show_usage_info = false;
/* Draw on screen. */
int idx = 0;
for(int y = 0; y < 64; y++) {
for(int x = 0; x < 128; x++) {
bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
bool level = bitmap_get(privdata->captured, CAPTURED_BITMAP_BYTES, idx++);
if(level) canvas_draw_dot(canvas, x, y);
/* Busy loop: this is a terrible approach as it blocks
* everything else, but for now it's the best we can do
* to obtain direct data with some spacing. */
uint32_t x = 250;
while(x--)
;
}
}
char buf[32];
snprintf(buf, sizeof(buf), "%lu usec/px", privdata->usec_per_pixel);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_with_border(canvas, 36, 60, "Direct sampling", ColorWhite, ColorBlack);
canvas_draw_str_with_border(canvas, 1, 60, buf, ColorWhite, ColorBlack);
}
/* Handle input */
void process_input_direct_sampling(ProtoViewApp* app, InputEvent input) {
DirectSamplingViewPrivData* privdata = app->view_privdata;
if(input.type == InputTypePress && input.key == InputKeyOk) {
app->direct_sampling_enabled = !app->direct_sampling_enabled;
}
if((input.key == InputKeyUp || input.key == InputKeyDown) &&
(input.type == InputTypePress || input.type == InputTypeRepeat)) {
uint32_t change = input.type == InputTypePress ? USEC_PER_PIXEL_SMALL_CHANGE :
USEC_PER_PIXEL_LARGE_CHANGE;
if(input.key == InputKeyUp) change = -change;
privdata->usec_per_pixel += change;
if(privdata->usec_per_pixel < USEC_PER_PIXEL_MIN)
privdata->usec_per_pixel = USEC_PER_PIXEL_MIN;
else if(privdata->usec_per_pixel > USEC_PER_PIXEL_MAX)
privdata->usec_per_pixel = USEC_PER_PIXEL_MAX;
/* Update the timer frequency. */
direct_sampling_timer_stop(app);
direct_sampling_timer_start(app);
}
}
/* Enter view. Stop the subghz thread to prevent access as we read
* the CC1101 data directly. */
void view_enter_direct_sampling(ProtoViewApp* app) {
/* Set view defaults. */
DirectSamplingViewPrivData* privdata = app->view_privdata;
privdata->usec_per_pixel = DEFAULT_USEC_PER_PIXEL;
privdata->captured = malloc(CAPTURED_BITMAP_BYTES);
privdata->show_usage_info = true;
if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
subghz_worker_stop(app->txrx->worker);
furi_hal_subghz_stop_async_rx();
/* To read data asynchronously directly from the view, we need
* to put the CC1101 back into reception mode (the previous call
* to stop the async RX will put it into idle) and configure the
* G0 pin for reading. */
furi_hal_subghz_rx();
furi_hal_gpio_init(furi_hal_subghz.cc1101_g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
} else {
raw_sampling_worker_stop(app);
}
// Start the timer to capture raw data
direct_sampling_timer_start(app);
}
/* Exit view. Restore the subghz thread. */
void view_exit_direct_sampling(ProtoViewApp* app) {
DirectSamplingViewPrivData* privdata = app->view_privdata;
if(privdata->captured) free(privdata->captured);
app->direct_sampling_enabled = false;
direct_sampling_timer_stop(app);
/* Restart normal data feeding. */
if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
subghz_worker_start(app->txrx->worker);
furi_hal_subghz_start_async_rx(protoview_rx_callback, NULL);
} else {
raw_sampling_worker_start(app);
}
app->direct_sampling_enabled = false;
}
/* =========================== Timer implementation ========================= */
static void ds_timer_isr(void* ctx) {
ProtoViewApp* app = ctx;
DirectSamplingViewPrivData* privdata = app->view_privdata;
if(app->direct_sampling_enabled) {
bool level = furi_hal_gpio_read(furi_hal_subghz.cc1101_g0_pin);
bitmap_set(privdata->captured, CAPTURED_BITMAP_BYTES, privdata->captured_idx, level);
privdata->captured_idx = (privdata->captured_idx + 1) % CAPTURED_BITMAP_BITS;
}
LL_TIM_ClearFlag_UPDATE(TIM2);
}
static void direct_sampling_timer_start(ProtoViewApp* app) {
DirectSamplingViewPrivData* privdata = app->view_privdata;
LL_TIM_InitTypeDef tim_init = {
.Prescaler = 63, /* CPU frequency is ~64Mhz. */
.CounterMode = LL_TIM_COUNTERMODE_UP,
.Autoreload = privdata->usec_per_pixel};
LL_TIM_Init(TIM2, &tim_init);
LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_DisableCounter(TIM2);
LL_TIM_SetCounter(TIM2, 0);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, ds_timer_isr, app);
LL_TIM_EnableIT_UPDATE(TIM2);
LL_TIM_EnableCounter(TIM2);
}
static void direct_sampling_timer_stop(ProtoViewApp* app) {
UNUSED(app);
FURI_CRITICAL_ENTER();
LL_TIM_DisableCounter(TIM2);
LL_TIM_DisableIT_UPDATE(TIM2);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
LL_TIM_DeInit(TIM2);
FURI_CRITICAL_EXIT();
}
@@ -88,7 +88,7 @@ void process_input_settings(ProtoViewApp* app, InputEvent input) {
if(input.key == InputKeyUp) {
modid = modid == 0 ? count - 1 : modid - 1;
} else if(input.key == InputKeyDown) {
modid = (modid + 1) % (count ? count : 1);
modid = (modid + 1) % count;
} else {
return;
}
+8 -1
View File
@@ -255,7 +255,9 @@ bool place_on_top(Card* where, Card what) {
int8_t b_letter = (int8_t)what.character;
if(a_letter == 12) a_letter = -1;
if(b_letter == 12) b_letter = -1;
if(where->disabled && b_letter != -1) return false;
if((a_letter + 1) == b_letter) {
where->disabled = what.disabled;
where->pip = what.pip;
@@ -275,6 +277,7 @@ void tick(GameState* game_state, NotificationApp* notification) {
if(game_state->state == GameStatePlay) {
if(game_state->top_cards[0].character == 11 && game_state->top_cards[1].character == 11 &&
game_state->top_cards[2].character == 11 && game_state->top_cards[3].character == 11) {
DOLPHIN_DEED(DolphinDeedPluginGameWin);
game_state->state = GameStateAnimate;
return;
}
@@ -488,6 +491,10 @@ int32_t solitaire_app(void* p) {
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
AppEvent event;
// Call Dolphin deed on game start
DOLPHIN_DEED(DolphinDeedPluginGameStart);
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 150);
GameState* localstate = (GameState*)acquire_mutex_block(&state_mutex);
@@ -566,4 +573,4 @@ free_and_exit:
free(game_state);
furi_message_queue_free(event_queue);
return return_code;
}
}
-72
View File
@@ -1,72 +0,0 @@
# SubGHz Bruteforcer Plugin for Flipper Zero
SubGhz Bruteforcer from [Unleashed Firmware](https://github.com/DarkFlippers/unleashed-firmware)
### Disclaimer
This software is for experimental purposes only and is not meant for any illegal activity/purposes.
We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law.
### Supported Protocols:
#### CAME
- CAME 12bit 303MHz
- CAME 12bit 307MHz
- CAME 12bit 315MHz
- CAME 12bit 433MHz
- CAME 12bit 868MHz
#### NICE
- NICE 12bit 433MHz
- NICE 12bit 868MHz
#### Ansonic
- Ansonic 12bit 433.075MHz
- Ansonic 12bit 433.920MHz
- Ansonic 12bit 434.075MHz
#### Holtek
- Holtek HT12X 12bit 433.920MHz
#### Chamberlain
- Chamberlain 9bit 300MHz
- Chamberlain 9bit 315MHz
- Chamberlain 9bit 390MHz
- Chamberlain 9bit 433MHz
- Chamberlain 8bit 300MHz
- Chamberlain 8bit 315MHz
- Chamberlain 8bit 390MHz
- Chamberlain 7bit 300MHz
- Chamberlain 7bit 315MHz
- Chamberlain 7bit 390MHz
#### Linear
- Linear 10bit 300MHz
- Linear 10bit 310MHz
#### UNILARM
- UNILARM 25bit 330MHz
- UNILARM 25bit 433MHz
#### SMC5326
- SMC5326 25bit 330MHz
- SMC5326 25bit 433MHz
#### PT2260
- PT2260 24bit 315MHz
- PT2260 24bit 330MHz
- PT2260 24bit 390MHz
- PT2260 24bit 433MHz
#### Additional
- BF Existing dump works for most other static protocols supported by Flipper Zero
@@ -1,13 +0,0 @@
App(
appid="SubGHz_Bruteforcer",
name="Sub-GHz Bruteforcer",
apptype=FlipperAppType.EXTERNAL,
entry_point="subbrute_app",
cdefines=["APP_SUB_BRUTE"],
requires=["gui", "dialogs"],
stack_size=2 * 1024,
order=11,
fap_icon="images/subbrute_10px.png",
fap_category="Tools",
fap_icon_assets="images",
)
@@ -1,59 +0,0 @@
#include "gui_top_buttons.h"
void elements_button_top_left(Canvas* canvas, const char* str) {
const Icon* icon = &I_ButtonUp_7x4;
const uint8_t button_height = 12;
const uint8_t vertical_offset = 3;
const uint8_t horizontal_offset = 3;
const uint8_t string_width = canvas_string_width(canvas, str);
const uint8_t icon_h_offset = 3;
const uint8_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset;
const uint8_t icon_v_offset = icon_get_height(icon) + vertical_offset;
const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset;
const uint8_t x = 0;
const uint8_t y = 0 + button_height;
uint8_t line_x = x + button_width;
uint8_t line_y = y - button_height;
canvas_draw_box(canvas, x, line_y, button_width, button_height);
canvas_draw_line(canvas, line_x + 0, line_y, line_x + 0, y - 1);
canvas_draw_line(canvas, line_x + 1, line_y, line_x + 1, y - 2);
canvas_draw_line(canvas, line_x + 2, line_y, line_x + 2, y - 3);
canvas_invert_color(canvas);
canvas_draw_icon(canvas, x + horizontal_offset, y - icon_v_offset, icon);
canvas_draw_str(
canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str);
canvas_invert_color(canvas);
}
void elements_button_top_right(Canvas* canvas, const char* str) {
const Icon* icon = &I_ButtonDown_7x4;
const uint8_t button_height = 12;
const uint8_t vertical_offset = 3;
const uint8_t horizontal_offset = 3;
const uint8_t string_width = canvas_string_width(canvas, str);
const uint8_t icon_h_offset = 3;
const uint8_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset;
const uint8_t icon_v_offset = icon_get_height(icon) + vertical_offset + 1;
const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset;
const uint8_t x = canvas_width(canvas);
const uint8_t y = 0 + button_height;
uint8_t line_x = x - button_width;
uint8_t line_y = y - button_height;
canvas_draw_box(canvas, line_x, line_y, button_width, button_height);
canvas_draw_line(canvas, line_x - 1, line_y, line_x - 1, y - 1);
canvas_draw_line(canvas, line_x - 2, line_y, line_x - 2, y - 2);
canvas_draw_line(canvas, line_x - 3, line_y, line_x - 3, y - 3);
canvas_invert_color(canvas);
canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str);
canvas_draw_icon(
canvas, x - horizontal_offset - icon_get_width(icon), y - icon_v_offset, icon);
canvas_invert_color(canvas);
}
@@ -1,21 +0,0 @@
#pragma once
#include <input/input.h>
#include <gui/elements.h>
#include <gui/icon.h>
#include <gui/icon_animation.h>
#include <assets_icons.h>
/**
* Thanks to the author of metronome
* @param canvas
* @param str
*/
void elements_button_top_left(Canvas* canvas, const char* str);
/**
* Thanks to the author of metronome
* @param canvas
* @param str
*/
void elements_button_top_right(Canvas* canvas, const char* str);
@@ -1,437 +0,0 @@
#include "subbrute_worker_private.h"
#include <string.h>
#include <toolbox/stream/stream.h>
#include <flipper_format.h>
#include <flipper_format_i.h>
#include <lib/subghz/protocols/protocol_items.h>
#define TAG "SubBruteWorker"
#define SUBBRUTE_TX_TIMEOUT 5
#define SUBBRUTE_MANUAL_TRANSMIT_INTERVAL 400
SubBruteWorker* subbrute_worker_alloc() {
SubBruteWorker* instance = malloc(sizeof(SubBruteWorker));
instance->state = SubBruteWorkerStateIDLE;
instance->step = 0;
instance->worker_running = false;
instance->initiated = false;
instance->last_time_tx_data = 0;
instance->load_index = 0;
instance->thread = furi_thread_alloc();
furi_thread_set_name(instance->thread, "SubBruteAttackWorker");
furi_thread_set_stack_size(instance->thread, 2048);
furi_thread_set_context(instance->thread, instance);
furi_thread_set_callback(instance->thread, subbrute_worker_thread);
instance->context = NULL;
instance->callback = NULL;
instance->decoder_result = NULL;
instance->transmitter = NULL;
instance->environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry(
instance->environment, (void*)&subghz_protocol_registry);
instance->transmit_mode = false;
return instance;
}
void subbrute_worker_free(SubBruteWorker* instance) {
furi_assert(instance);
// I don't know how to free this
instance->decoder_result = NULL;
if(instance->transmitter != NULL) {
subghz_transmitter_free(instance->transmitter);
instance->transmitter = NULL;
}
subghz_environment_free(instance->environment);
instance->environment = NULL;
furi_thread_free(instance->thread);
free(instance);
}
uint64_t subbrute_worker_get_step(SubBruteWorker* instance) {
return instance->step;
}
bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step) {
furi_assert(instance);
if(!subbrute_worker_can_manual_transmit(instance)) {
FURI_LOG_W(TAG, "Cannot set step during running mode");
return false;
}
instance->step = step;
return true;
}
bool subbrute_worker_init_default_attack(
SubBruteWorker* instance,
SubBruteAttacks attack_type,
uint64_t step,
const SubBruteProtocol* protocol,
uint8_t extra_repeats) {
furi_assert(instance);
if(instance->worker_running) {
FURI_LOG_W(TAG, "Init Worker when it's running");
subbrute_worker_stop(instance);
}
instance->attack = attack_type;
instance->frequency = protocol->frequency;
instance->preset = protocol->preset;
instance->file = protocol->file;
instance->step = step;
instance->bits = protocol->bits;
instance->te = protocol->te;
instance->repeat = protocol->repeat + extra_repeats;
instance->load_index = 0;
instance->file_key = 0;
instance->two_bytes = false;
instance->max_value =
subbrute_protocol_calc_max_value(instance->attack, instance->bits, instance->two_bytes);
instance->initiated = true;
instance->state = SubBruteWorkerStateReady;
subbrute_worker_send_callback(instance);
#ifdef FURI_DEBUG
FURI_LOG_I(
TAG,
"subbrute_worker_init_default_attack: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld",
subbrute_protocol_name(instance->attack),
instance->bits,
subbrute_protocol_preset(instance->preset),
subbrute_protocol_file(instance->file),
instance->te,
instance->repeat,
instance->max_value);
#endif
return true;
}
bool subbrute_worker_init_file_attack(
SubBruteWorker* instance,
uint64_t step,
uint8_t load_index,
uint64_t file_key,
SubBruteProtocol* protocol,
uint8_t extra_repeats,
bool two_bytes) {
furi_assert(instance);
if(instance->worker_running) {
FURI_LOG_W(TAG, "Init Worker when it's running");
subbrute_worker_stop(instance);
}
instance->attack = SubBruteAttackLoadFile;
instance->frequency = protocol->frequency;
instance->preset = protocol->preset;
instance->file = protocol->file;
instance->step = step;
instance->bits = protocol->bits;
instance->te = protocol->te;
instance->load_index = load_index;
instance->repeat = protocol->repeat + extra_repeats;
instance->file_key = file_key;
instance->two_bytes = two_bytes;
instance->max_value =
subbrute_protocol_calc_max_value(instance->attack, instance->bits, instance->two_bytes);
instance->initiated = true;
instance->state = SubBruteWorkerStateReady;
subbrute_worker_send_callback(instance);
#ifdef FURI_DEBUG
FURI_LOG_I(
TAG,
"subbrute_worker_init_file_attack: %s, bits: %d, preset: %s, file: %s, te: %ld, repeat: %d, max_value: %lld, key: %llX",
subbrute_protocol_name(instance->attack),
instance->bits,
subbrute_protocol_preset(instance->preset),
subbrute_protocol_file(instance->file),
instance->te,
instance->repeat,
instance->max_value,
instance->file_key);
#endif
return true;
}
bool subbrute_worker_start(SubBruteWorker* instance) {
furi_assert(instance);
if(!instance->initiated) {
FURI_LOG_W(TAG, "Worker not init!");
return false;
}
if(instance->worker_running) {
FURI_LOG_W(TAG, "Worker is already running!");
return false;
}
if(instance->state != SubBruteWorkerStateReady &&
instance->state != SubBruteWorkerStateFinished) {
FURI_LOG_W(TAG, "Worker cannot start, invalid device state: %d", instance->state);
return false;
}
instance->worker_running = true;
furi_thread_start(instance->thread);
return true;
}
void subbrute_worker_stop(SubBruteWorker* instance) {
furi_assert(instance);
if(!instance->worker_running) {
return;
}
instance->worker_running = false;
furi_thread_join(instance->thread);
furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
furi_hal_subghz_sleep();
}
bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step) {
furi_assert(instance);
if(!instance->initiated) {
FURI_LOG_W(TAG, "Worker not init!");
return false;
}
if(instance->worker_running) {
FURI_LOG_W(TAG, "Worker in running state!");
return false;
}
if(instance->state != SubBruteWorkerStateReady &&
instance->state != SubBruteWorkerStateFinished) {
FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
return false;
}
uint32_t ticks = furi_get_tick();
if((ticks - instance->last_time_tx_data) < SUBBRUTE_MANUAL_TRANSMIT_INTERVAL) {
#if FURI_DEBUG
FURI_LOG_D(TAG, "Need to wait, current: %ld", ticks - instance->last_time_tx_data);
#endif
return false;
}
instance->last_time_tx_data = ticks;
instance->step = step;
bool result;
instance->protocol_name = subbrute_protocol_file(instance->file);
FlipperFormat* flipper_format = flipper_format_string_alloc();
Stream* stream = flipper_format_get_raw_stream(flipper_format);
stream_clean(stream);
if(instance->attack == SubBruteAttackLoadFile) {
subbrute_protocol_file_payload(
stream,
step,
instance->bits,
instance->te,
instance->repeat,
instance->load_index,
instance->file_key,
instance->two_bytes);
} else {
subbrute_protocol_default_payload(
stream, instance->file, step, instance->bits, instance->te, instance->repeat);
}
// size_t written = stream_write_string(stream, payload);
// if(written <= 0) {
// FURI_LOG_W(TAG, "Error creating packet! EXIT");
// result = false;
// } else {
subbrute_worker_subghz_transmit(instance, flipper_format);
result = true;
#if FURI_DEBUG
FURI_LOG_D(TAG, "Manual transmit done");
#endif
// }
flipper_format_free(flipper_format);
// furi_string_free(payload);
return result;
}
bool subbrute_worker_is_running(SubBruteWorker* instance) {
return instance->worker_running;
}
bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance) {
furi_assert(instance);
if(!instance->initiated) {
FURI_LOG_W(TAG, "Worker not init!");
return false;
}
return !instance->worker_running && instance->state != SubBruteWorkerStateIDLE &&
instance->state != SubBruteWorkerStateTx &&
((furi_get_tick() - instance->last_time_tx_data) > SUBBRUTE_MANUAL_TRANSMIT_INTERVAL);
}
void subbrute_worker_set_callback(
SubBruteWorker* instance,
SubBruteWorkerCallback callback,
void* context) {
furi_assert(instance);
instance->callback = callback;
instance->context = context;
}
void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format) {
while(instance->transmit_mode) {
furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
}
instance->transmit_mode = true;
if(instance->transmitter != NULL) {
subghz_transmitter_free(instance->transmitter);
instance->transmitter = NULL;
}
instance->transmitter =
subghz_transmitter_alloc_init(instance->environment, instance->protocol_name);
subghz_transmitter_deserialize(instance->transmitter, flipper_format);
furi_hal_subghz_reset();
furi_hal_subghz_load_preset(instance->preset);
furi_hal_subghz_set_frequency_and_path(instance->frequency);
furi_hal_subghz_start_async_tx(subghz_transmitter_yield, instance->transmitter);
while(!furi_hal_subghz_is_async_tx_complete()) {
furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
}
furi_hal_subghz_stop_async_tx();
furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
furi_hal_subghz_sleep();
subghz_transmitter_free(instance->transmitter);
instance->transmitter = NULL;
instance->transmit_mode = false;
}
void subbrute_worker_send_callback(SubBruteWorker* instance) {
if(instance->callback != NULL) {
instance->callback(instance->context, instance->state);
}
}
/**
* Entrypoint for worker
*
* @param context SubBruteWorker*
* @return 0 if ok
*/
int32_t subbrute_worker_thread(void* context) {
furi_assert(context);
SubBruteWorker* instance = (SubBruteWorker*)context;
if(!instance->worker_running) {
FURI_LOG_W(TAG, "Worker is not set to running state!");
return -1;
}
if(instance->state != SubBruteWorkerStateReady &&
instance->state != SubBruteWorkerStateFinished) {
FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
return -2;
}
#ifdef FURI_DEBUG
FURI_LOG_I(TAG, "Worker start");
#endif
SubBruteWorkerState local_state = instance->state = SubBruteWorkerStateTx;
subbrute_worker_send_callback(instance);
instance->protocol_name = subbrute_protocol_file(instance->file);
FlipperFormat* flipper_format = flipper_format_string_alloc();
Stream* stream = flipper_format_get_raw_stream(flipper_format);
while(instance->worker_running) {
stream_clean(stream);
if(instance->attack == SubBruteAttackLoadFile) {
subbrute_protocol_file_payload(
stream,
instance->step,
instance->bits,
instance->te,
instance->repeat,
instance->load_index,
instance->file_key,
instance->two_bytes);
} else {
subbrute_protocol_default_payload(
stream,
instance->file,
instance->step,
instance->bits,
instance->te,
instance->repeat);
}
#ifdef FURI_DEBUG
//FURI_LOG_I(TAG, "Payload: %s", furi_string_get_cstr(payload));
//furi_delay_ms(SUBBRUTE_MANUAL_TRANSMIT_INTERVAL / 4);
#endif
// size_t written = stream_write_stream_write_string(stream, payload);
// if(written <= 0) {
// FURI_LOG_W(TAG, "Error creating packet! BREAK");
// instance->worker_running = false;
// local_state = SubBruteWorkerStateIDLE;
// furi_string_free(payload);
// break;
// }
subbrute_worker_subghz_transmit(instance, flipper_format);
if(instance->step + 1 > instance->max_value) {
#ifdef FURI_DEBUG
FURI_LOG_I(TAG, "Worker finished to end");
#endif
local_state = SubBruteWorkerStateFinished;
// furi_string_free(payload);
break;
}
instance->step++;
// furi_string_free(payload);
furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
}
flipper_format_free(flipper_format);
instance->worker_running = false; // Because we have error states
instance->state = local_state == SubBruteWorkerStateTx ? SubBruteWorkerStateReady :
local_state;
subbrute_worker_send_callback(instance);
#ifdef FURI_DEBUG
FURI_LOG_I(TAG, "Worker stop");
#endif
return 0;
}
@@ -1,42 +0,0 @@
#pragma once
#include "../subbrute_protocols.h"
typedef enum {
SubBruteWorkerStateIDLE,
SubBruteWorkerStateReady,
SubBruteWorkerStateTx,
SubBruteWorkerStateFinished
} SubBruteWorkerState;
typedef void (*SubBruteWorkerCallback)(void* context, SubBruteWorkerState state);
typedef struct SubBruteWorker SubBruteWorker;
SubBruteWorker* subbrute_worker_alloc();
void subbrute_worker_free(SubBruteWorker* instance);
uint64_t subbrute_worker_get_step(SubBruteWorker* instance);
bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step);
bool subbrute_worker_is_running(SubBruteWorker* instance);
bool subbrute_worker_init_default_attack(
SubBruteWorker* instance,
SubBruteAttacks attack_type,
uint64_t step,
const SubBruteProtocol* protocol,
uint8_t extra_repeats);
bool subbrute_worker_init_file_attack(
SubBruteWorker* instance,
uint64_t step,
uint8_t load_index,
uint64_t file_key,
SubBruteProtocol* protocol,
uint8_t extra_repeats,
bool two_bytes);
bool subbrute_worker_start(SubBruteWorker* instance);
void subbrute_worker_stop(SubBruteWorker* instance);
bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step);
bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance);
void subbrute_worker_set_callback(
SubBruteWorker* instance,
SubBruteWorkerCallback callback,
void* context);
@@ -1,48 +0,0 @@
#pragma once
#include "subbrute_worker.h"
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/environment.h>
struct SubBruteWorker {
SubBruteWorkerState state;
volatile bool worker_running;
volatile bool initiated;
volatile bool transmit_mode;
// Current step
uint64_t step;
// SubGhz
FuriThread* thread;
SubGhzProtocolDecoderBase* decoder_result;
SubGhzEnvironment* environment;
SubGhzTransmitter* transmitter;
const char* protocol_name;
// Initiated values
SubBruteAttacks attack; // Attack state
uint32_t frequency;
FuriHalSubGhzPreset preset;
SubBruteFileProtocol file;
uint8_t bits;
uint32_t te;
uint8_t repeat;
uint8_t load_index; // Index of group to bruteforce in loaded file
uint64_t file_key;
uint64_t max_value; // Max step
bool two_bytes;
// Manual transmit
uint32_t last_time_tx_data;
// Callback for changed states
SubBruteWorkerCallback callback;
void* context;
};
int32_t subbrute_worker_thread(void* context);
void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format);
void subbrute_worker_send_callback(SubBruteWorker* instance);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

@@ -1 +0,0 @@
3
Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,7 +0,0 @@
ADD_SCENE(subbrute, load_file, LoadFile)
ADD_SCENE(subbrute, load_select, LoadSelect)
ADD_SCENE(subbrute, run_attack, RunAttack)
ADD_SCENE(subbrute, save_name, SaveName)
ADD_SCENE(subbrute, save_success, SaveSuccess)
ADD_SCENE(subbrute, setup_attack, SetupAttack)
ADD_SCENE(subbrute, start, Start)
@@ -1,91 +0,0 @@
#include "../subbrute_i.h"
#include "subbrute_scene.h"
#define TAG "SubBruteSceneLoadFile"
void subbrute_scene_load_file_on_enter(void* context) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
// Input events and views are managed by file_browser
FuriString* app_folder;
FuriString* load_path;
load_path = furi_string_alloc();
app_folder = furi_string_alloc_set(SUBBRUTE_PATH);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, SUBBRUTE_FILE_EXT, &I_sub1_10px);
SubBruteFileResult load_result = SubBruteFileResultUnknown;
// TODO: DELETE IT
#ifdef SUBBRUTE_FAST_TRACK
bool res = true;
furi_string_printf(load_path, "%s", "/ext/subghz/princeton.sub");
#else
bool res =
dialog_file_browser_show(instance->dialogs, load_path, app_folder, &browser_options);
#endif
#ifdef FURI_DEBUG
FURI_LOG_D(
TAG,
"load_path: %s, app_folder: %s",
furi_string_get_cstr(load_path),
furi_string_get_cstr(app_folder));
#endif
if(res) {
load_result =
subbrute_device_load_from_file(instance->device, furi_string_get_cstr(load_path));
if(load_result == SubBruteFileResultOk) {
uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
load_result = subbrute_device_attack_set(
instance->device, SubBruteAttackLoadFile, extra_repeats);
if(load_result == SubBruteFileResultOk) {
if(!subbrute_worker_init_file_attack(
instance->worker,
instance->device->current_step,
instance->device->bit_index,
instance->device->key_from_file,
instance->device->file_protocol_info,
extra_repeats,
instance->device->two_bytes)) {
furi_crash("Invalid attack set!");
}
// Ready to run!
FURI_LOG_I(TAG, "Ready to run");
res = true;
}
}
if(load_result == SubBruteFileResultOk) {
scene_manager_next_scene(instance->scene_manager, SubBruteSceneLoadSelect);
} else {
FURI_LOG_E(TAG, "Returned error: %d", load_result);
FuriString* dialog_msg;
dialog_msg = furi_string_alloc();
furi_string_cat_printf(
dialog_msg, "Cannot parse\nfile: %s", subbrute_device_error_get_desc(load_result));
dialog_message_show_storage_error(instance->dialogs, furi_string_get_cstr(dialog_msg));
furi_string_free(dialog_msg);
scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, SubBruteSceneStart);
}
} else {
scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, SubBruteSceneStart);
}
furi_string_free(app_folder);
furi_string_free(load_path);
}
void subbrute_scene_load_file_on_exit(void* context) {
UNUSED(context);
}
bool subbrute_scene_load_file_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
@@ -1,82 +0,0 @@
#include "../subbrute_i.h"
#include "subbrute_scene.h"
#define TAG "SubBruteSceneStart"
void subbrute_scene_load_select_callback(SubBruteCustomEvent event, void* context) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
view_dispatcher_send_custom_event(instance->view_dispatcher, event);
}
void subbrute_scene_load_select_on_enter(void* context) {
furi_assert(context);
#ifdef FURI_DEBUG
FURI_LOG_I(TAG, "subbrute_scene_load_select_on_enter");
#endif
SubBruteState* instance = (SubBruteState*)context;
SubBruteMainView* view = instance->view_main;
instance->current_view = SubBruteViewMain;
subbrute_main_view_set_callback(view, subbrute_scene_load_select_callback, instance);
subbrute_main_view_set_index(
view, 7, true, instance->device->two_bytes, instance->device->key_from_file);
view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
}
void subbrute_scene_load_select_on_exit(void* context) {
UNUSED(context);
#ifdef FURI_DEBUG
FURI_LOG_I(TAG, "subbrute_scene_load_select_on_exit");
#endif
}
bool subbrute_scene_load_select_on_event(void* context, SceneManagerEvent event) {
SubBruteState* instance = (SubBruteState*)context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubBruteCustomEventTypeIndexSelected) {
/*#ifdef FURI_DEBUG && !SUBBRUTE_FAST_TRACK
view_dispatcher_stop(instance->view_dispatcher);
consumed = true;
#else*/
instance->device->current_step = 0;
instance->device->bit_index = subbrute_main_view_get_index(instance->view_main);
instance->device->two_bytes = subbrute_main_view_get_two_bytes(instance->view_main);
uint8_t extra_repeats = subbrute_main_view_get_extra_repeats(instance->view_main);
instance->device->max_value = subbrute_protocol_calc_max_value(
instance->device->attack,
instance->device->bit_index,
instance->device->two_bytes);
if(!subbrute_worker_init_file_attack(
instance->worker,
instance->device->current_step,
instance->device->bit_index,
instance->device->key_from_file,
instance->device->file_protocol_info,
extra_repeats,
instance->device->two_bytes)) {
furi_crash("Invalid attack set!");
}
scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
/*#endif*/
consumed = true;
} /* else if(event.event == SubBruteCustomEventTypeChangeStepUp) {
instance->device->two_bytes = true;
} else if(event.event == SubBruteCustomEventTypeChangeStepDown) {
instance->device->two_bytes = false;
}*/
} else if(event.type == SceneManagerEventTypeBack) {
if(!scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, SubBruteSceneStart)) {
scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
}
consumed = true;
}
return consumed;
}
@@ -1,104 +0,0 @@
#include "../subbrute_i.h"
#include "subbrute_scene.h"
#define TAG "SubBruteSceneRunAttack"
static void subbrute_scene_run_attack_callback(SubBruteCustomEvent event, void* context) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
view_dispatcher_send_custom_event(instance->view_dispatcher, event);
}
static void
subbrute_scene_run_attack_device_state_changed(void* context, SubBruteWorkerState state) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
if(state == SubBruteWorkerStateIDLE) {
// Can't be IDLE on this step!
view_dispatcher_send_custom_event(instance->view_dispatcher, SubBruteCustomEventTypeError);
} else if(state == SubBruteWorkerStateFinished) {
view_dispatcher_send_custom_event(
instance->view_dispatcher, SubBruteCustomEventTypeTransmitFinished);
}
}
void subbrute_scene_run_attack_on_exit(void* context) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
notification_message(instance->notifications, &sequence_blink_stop);
subbrute_worker_stop(instance->worker);
}
void subbrute_scene_run_attack_on_enter(void* context) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
SubBruteAttackView* view = instance->view_attack;
instance->current_view = SubBruteViewAttack;
subbrute_attack_view_set_callback(view, subbrute_scene_run_attack_callback, instance);
view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
subbrute_worker_set_callback(
instance->worker, subbrute_scene_run_attack_device_state_changed, instance);
if(!subbrute_worker_is_running(instance->worker)) {
subbrute_worker_set_step(instance->worker, instance->device->current_step);
if(!subbrute_worker_start(instance->worker)) {
view_dispatcher_send_custom_event(
instance->view_dispatcher, SubBruteCustomEventTypeError);
} else {
notification_message(instance->notifications, &sequence_single_vibro);
notification_message(instance->notifications, &sequence_blink_start_yellow);
}
}
}
bool subbrute_scene_run_attack_on_event(void* context, SceneManagerEvent event) {
SubBruteState* instance = (SubBruteState*)context;
SubBruteAttackView* view = instance->view_attack;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
uint64_t step = subbrute_worker_get_step(instance->worker);
instance->device->current_step = step;
subbrute_attack_view_set_current_step(view, step);
if(event.event == SubBruteCustomEventTypeTransmitFinished) {
notification_message(instance->notifications, &sequence_display_backlight_on);
notification_message(instance->notifications, &sequence_double_vibro);
scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
} else if(
event.event == SubBruteCustomEventTypeTransmitNotStarted ||
event.event == SubBruteCustomEventTypeBackPressed) {
if(subbrute_worker_is_running(instance->worker)) {
// Notify
notification_message(instance->notifications, &sequence_single_vibro);
}
// Stop transmit
scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, SubBruteSceneSetupAttack);
} else if(event.event == SubBruteCustomEventTypeError) {
notification_message(instance->notifications, &sequence_error);
// Stop transmit
scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, SubBruteSceneSetupAttack);
} else if(event.event == SubBruteCustomEventTypeUpdateView) {
//subbrute_attack_view_set_current_step(view, instance->device->current_step);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeTick) {
uint64_t step = subbrute_worker_get_step(instance->worker);
instance->device->current_step = step;
subbrute_attack_view_set_current_step(view, step);
consumed = true;
}
return consumed;
}
@@ -1,84 +0,0 @@
#include "../subbrute_i.h"
#include "subbrute_scene.h"
#include <lib/toolbox/random_name.h>
#define TAG "SubBruteSceneSaveFile"
void subbrute_scene_save_name_on_enter(void* context) {
SubBruteState* instance = (SubBruteState*)context;
// Setup view
TextInput* text_input = instance->text_input;
set_random_name(instance->text_store, sizeof(instance->text_store));
text_input_set_header_text(text_input, "Name of file");
text_input_set_result_callback(
text_input,
subbrute_text_input_callback,
instance,
instance->text_store,
SUBBRUTE_MAX_LEN_NAME,
true);
furi_string_reset(instance->file_path);
furi_string_set_str(instance->file_path, SUBBRUTE_PATH);
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
furi_string_get_cstr(instance->file_path), SUBBRUTE_FILE_EXT, "");
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
view_dispatcher_switch_to_view(instance->view_dispatcher, SubBruteViewTextInput);
}
bool subbrute_scene_save_name_on_event(void* context, SceneManagerEvent event) {
SubBruteState* instance = (SubBruteState*)context;
bool consumed = false;
if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(instance->scene_manager);
return true;
} else if(
event.type == SceneManagerEventTypeCustom &&
event.event == SubBruteCustomEventTypeTextEditDone) {
#ifdef FURI_DEBUG
FURI_LOG_D(TAG, "Saving: %s", instance->text_store);
#endif
bool success = false;
if(strcmp(instance->text_store, "")) {
furi_string_reset(instance->file_path);
furi_string_cat_printf(
instance->file_path,
"%s/%s%s",
SUBBRUTE_PATH,
instance->text_store,
SUBBRUTE_FILE_EXT);
if(subbrute_device_save_file(
instance->device, furi_string_get_cstr(instance->file_path))) {
scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveSuccess);
success = true;
consumed = true;
}
}
if(!success) {
dialog_message_show_storage_error(instance->dialogs, "Error during saving!");
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, SubBruteSceneSetupAttack);
}
}
return consumed;
}
void subbrute_scene_save_name_on_exit(void* context) {
SubBruteState* instance = (SubBruteState*)context;
// Clear view
void* validator_context = text_input_get_validator_callback_context(instance->text_input);
text_input_set_validator(instance->text_input, NULL, NULL);
validator_is_file_free(validator_context);
text_input_reset(instance->text_input);
furi_string_reset(instance->file_path);
}
@@ -1,51 +0,0 @@
#include "../subbrute_i.h"
#include "subbrute_scene.h"
void subbrute_scene_save_success_on_enter(void* context) {
furi_assert(context);
SubBruteState* instance = context;
// Setup view
Popup* popup = instance->popup;
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
popup_set_context(popup, instance);
popup_set_callback(popup, subbrute_popup_closed_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(instance->view_dispatcher, SubBruteViewPopup);
}
bool subbrute_scene_save_success_on_event(void* context, SceneManagerEvent event) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
//SubBruteMainView* view = instance->view_main;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubBruteCustomEventTypePopupClosed) {
if(!scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, SubBruteSceneSetupAttack)) {
scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
}
return true;
}
}
return false;
}
void subbrute_scene_save_success_on_exit(void* context) {
furi_assert(context);
SubBruteState* instance = (SubBruteState*)context;
// Clear view
Popup* popup = instance->popup;
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_icon(popup, 0, 0, NULL);
popup_set_callback(popup, NULL);
popup_set_context(popup, NULL);
popup_set_timeout(popup, 0);
popup_disable_timeout(popup);
}

Some files were not shown because too many files have changed in this diff Show More