Merge branch 'dev' of https://github.com/ClaraCrazy/Flipper-Xtreme into fix-bad_kb_bt-flipper_app-conflict
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 44 KiB |
@@ -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
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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!
|
||||
|
||||

|
||||
|
||||
[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",
|
||||
|
||||
|
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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
# How to Connect the HTU21D sensor
|
||||

|
||||
|
||||
|
||||
# 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:
|
||||
|
||||

|
||||
|
||||
## 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",
|
||||
)
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
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;
|
||||
}
|
||||
|
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);
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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)};
|
||||
@@ -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};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
Before Width: | Height: | Size: 102 B |
|
Before Width: | Height: | Size: 102 B |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 97 B |
|
Before Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 90 B |
|
Before Width: | Height: | Size: 76 B |
|
Before Width: | Height: | Size: 79 B |
|
Before Width: | Height: | Size: 90 B |
@@ -1 +0,0 @@
|
||||
3
|
||||
|
Before Width: | Height: | Size: 299 B |
|
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);
|
||||
}
|
||||