Genie recorder
This commit is contained in:
parent
88db25b8de
commit
fec280d3a5
17
subghz/apps/genie-recorder/README.md
Normal file
17
subghz/apps/genie-recorder/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Genie garage door recorder
|
||||||
|
|
||||||
|
## Description
|
||||||
|
This program was written to allow the Flipper Zero to press buttons on a Genie garage door opened and record the rolling code. The goal is to capture all 65,536 signals (which hopefully repeats when it gets to the end). Our click speed is current 2000ms per click + however long it takes to get the signal. So if we assume it's 1000-1500/hr = about 3 days?
|
||||||
|
|
||||||
|
## Running
|
||||||
|
Step 1. Deploy custom firmware (keeloq.c with te_short=200, te_long=400, te_delta=70)
|
||||||
|
Step 2. Connect a genie garage door opener to pins A7 and GND.
|
||||||
|
Step 3. Edit FREQUENCY if it is not broadcasting at 315MHz when button is held down.
|
||||||
|
Step 4. Run application
|
||||||
|
Step 5. Choose "Start" to start recording signals.
|
||||||
|
- You should see the current broadcast count (how many times the button was pressed)
|
||||||
|
- You should see the received signal count (how many times the signal was received)
|
||||||
|
- You should see the most recent Keeloq code received
|
||||||
|
Step 6. Let it run for a few weeks (the goal is to capture 65,536+ signals)
|
||||||
|
Step 7. Press the BACK button twice to exit the application.
|
||||||
|
Step 8. Copy the file "\apps_data\genie\keys.txt" from the SD card to your computer.
|
13
subghz/apps/genie-recorder/application.fam
Normal file
13
subghz/apps/genie-recorder/application.fam
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
App(
|
||||||
|
appid="genie_record",
|
||||||
|
name="Genie Door Recorder",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="genie_record_app",
|
||||||
|
requires=["gui", "subghz"],
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
fap_version=(1, 1),
|
||||||
|
fap_icon="genie.png",
|
||||||
|
fap_category="Sub-GHz",
|
||||||
|
fap_icon_assets="assets",
|
||||||
|
fap_description="Genie Door Signal w/Recorder",
|
||||||
|
)
|
258
subghz/apps/genie-recorder/genie.c
Normal file
258
subghz/apps/genie-recorder/genie.c
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <gui/modules/widget.h>
|
||||||
|
|
||||||
|
#include "genie_subghz_receive.h"
|
||||||
|
#include "genie_file.h"
|
||||||
|
|
||||||
|
#define TAG "GenieRecord"
|
||||||
|
|
||||||
|
//#define FREQUENCY 390000000
|
||||||
|
#define FREQUENCY 315000000
|
||||||
|
#define CLICK_SPEED 2000
|
||||||
|
const GpioPin* const pin_remote = &gpio_ext_pa7;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GenieSubmenuIndexStart,
|
||||||
|
GenieSubmenuIndexAbout,
|
||||||
|
} GenieSubmenuIndex;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GenieViewSubmenu,
|
||||||
|
GenieViewStart,
|
||||||
|
GenieViewAbout,
|
||||||
|
} GenieView;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
Submenu* submenu;
|
||||||
|
View* view;
|
||||||
|
Widget* widget_about;
|
||||||
|
GenieSubGhz* genie_subghz;
|
||||||
|
FuriTimer* timer;
|
||||||
|
bool processing;
|
||||||
|
bool pressed;
|
||||||
|
uint32_t counter;
|
||||||
|
uint32_t save_counter;
|
||||||
|
FuriString* key;
|
||||||
|
} GenieApp;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GenieApp* ref;
|
||||||
|
} GenieAppRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback for navigation events
|
||||||
|
* @details This function is called when user press back button. We return VIEW_NONE to
|
||||||
|
* indicate that we want to exit the application.
|
||||||
|
* @param context The context
|
||||||
|
* @return next view id
|
||||||
|
*/
|
||||||
|
uint32_t genie_navigation_exit_callback(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
return VIEW_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback for navigation events
|
||||||
|
* @details This function is called when user press back button. We return VIEW_NONE to
|
||||||
|
* indicate that we want to exit the application.
|
||||||
|
* @param context The context
|
||||||
|
* @return next view id
|
||||||
|
*/
|
||||||
|
uint32_t genie_navigation_submenu_callback(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
return GenieViewSubmenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_submenu_callback(void* context, uint32_t index) {
|
||||||
|
GenieApp* app = (GenieApp*)context;
|
||||||
|
switch(index) {
|
||||||
|
case GenieSubmenuIndexStart:
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, GenieViewStart);
|
||||||
|
break;
|
||||||
|
case GenieSubmenuIndexAbout:
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, GenieViewAbout);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_view_draw_callback(Canvas* canvas, void* model) {
|
||||||
|
GenieApp* app = ((GenieAppRef*)model)->ref;
|
||||||
|
canvas_draw_str(canvas, 5, 15, "Genie Sub-Ghz Recorder!!!");
|
||||||
|
canvas_draw_str(canvas, 5, 30, "Make sure A7 is connected");
|
||||||
|
canvas_draw_str(canvas, 100, 58, (app->pressed) ? "SEND" : "");
|
||||||
|
FuriString* buffer = furi_string_alloc();
|
||||||
|
furi_string_printf(buffer, "%ld", app->counter);
|
||||||
|
canvas_draw_str(canvas, 50, 45, furi_string_get_cstr(buffer));
|
||||||
|
furi_string_printf(buffer, "%ld", app->save_counter);
|
||||||
|
canvas_draw_str(canvas, 96, 45, furi_string_get_cstr(buffer));
|
||||||
|
canvas_draw_str(canvas, 5, 55, furi_string_get_cstr(app->key));
|
||||||
|
furi_string_free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool genie_view_input_callback(InputEvent* event, void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t last_count() {
|
||||||
|
uint32_t count = genie_load();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_count(uint32_t count, FuriString* key) {
|
||||||
|
FURI_LOG_I(TAG, "%ld,%s", count, furi_string_get_cstr(key));
|
||||||
|
genie_save(count, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __gui_redraw() {
|
||||||
|
// Redraw screen
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_direct_draw_acquire(gui);
|
||||||
|
gui_direct_draw_release(gui);
|
||||||
|
}
|
||||||
|
|
||||||
|
void press_button(GenieApp* app) {
|
||||||
|
furi_hal_gpio_write(pin_remote, false);
|
||||||
|
app->pressed = true;
|
||||||
|
__gui_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void release_button(GenieApp* app) {
|
||||||
|
furi_hal_gpio_write(pin_remote, true);
|
||||||
|
app->pressed = false;
|
||||||
|
__gui_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_packet(FuriString* buffer, void* context) {
|
||||||
|
GenieApp* app = (GenieApp*)context;
|
||||||
|
app->processing = true;
|
||||||
|
release_button(app);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "PACKET:\r\n%s", furi_string_get_cstr(buffer));
|
||||||
|
if(furi_string_search_str(buffer, "KeeLoq 64bit") < furi_string_size(buffer)) {
|
||||||
|
FURI_LOG_I(TAG, "KeeLoq 64bit found");
|
||||||
|
size_t key_index = furi_string_search_str(buffer, "Key:");
|
||||||
|
if(key_index < furi_string_size(buffer)) {
|
||||||
|
FURI_LOG_I(TAG, "Key found");
|
||||||
|
furi_string_set_n(app->key, buffer, key_index + 4, 16);
|
||||||
|
app->save_counter++;
|
||||||
|
save_count(app->counter, app->key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app->processing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_tick(void* context) {
|
||||||
|
GenieApp* app = (GenieApp*)context;
|
||||||
|
if(!app->processing) {
|
||||||
|
if(app->pressed) {
|
||||||
|
release_button(app);
|
||||||
|
} else {
|
||||||
|
app->counter++;
|
||||||
|
press_button(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_enter_callback(void* context) {
|
||||||
|
GenieApp* app = (GenieApp*)context;
|
||||||
|
start_listening(app->genie_subghz, FREQUENCY, genie_packet, context);
|
||||||
|
furi_timer_start(app->timer, furi_ms_to_ticks(CLICK_SPEED));
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_exit_callback(void* context) {
|
||||||
|
GenieApp* app = (GenieApp*)context;
|
||||||
|
app->processing = false;
|
||||||
|
release_button(app);
|
||||||
|
stop_listening(app->genie_subghz);
|
||||||
|
furi_timer_stop(app->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
GenieApp* genie_app_alloc() {
|
||||||
|
GenieApp* app = (GenieApp*)malloc(sizeof(GenieApp));
|
||||||
|
app->genie_subghz = genie_subghz_alloc();
|
||||||
|
app->timer = furi_timer_alloc(genie_tick, FuriTimerTypePeriodic, app);
|
||||||
|
app->counter = last_count();
|
||||||
|
app->save_counter = app->counter;
|
||||||
|
app->key = furi_string_alloc();
|
||||||
|
|
||||||
|
furi_hal_gpio_init_simple(pin_remote, GpioModeOutputOpenDrain);
|
||||||
|
release_button(app);
|
||||||
|
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
app->submenu = submenu_alloc();
|
||||||
|
submenu_add_item(app->submenu, "Start", GenieSubmenuIndexStart, genie_submenu_callback, app);
|
||||||
|
submenu_add_item(app->submenu, "About", GenieSubmenuIndexAbout, genie_submenu_callback, app);
|
||||||
|
view_set_previous_callback(submenu_get_view(app->submenu), genie_navigation_exit_callback);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, GenieViewSubmenu, submenu_get_view(app->submenu));
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, GenieViewSubmenu);
|
||||||
|
|
||||||
|
app->view = view_alloc();
|
||||||
|
view_set_draw_callback(app->view, genie_view_draw_callback);
|
||||||
|
view_set_input_callback(app->view, genie_view_input_callback);
|
||||||
|
view_set_previous_callback(app->view, genie_navigation_submenu_callback);
|
||||||
|
view_set_context(app->view, app);
|
||||||
|
view_set_enter_callback(app->view, genie_enter_callback);
|
||||||
|
view_set_exit_callback(app->view, genie_exit_callback);
|
||||||
|
view_dispatcher_add_view(app->view_dispatcher, GenieViewStart, app->view);
|
||||||
|
view_allocate_model(app->view, ViewModelTypeLockFree, sizeof(GenieAppRef));
|
||||||
|
GenieAppRef* r = (GenieAppRef*)view_get_model(app->view);
|
||||||
|
r->ref = app;
|
||||||
|
|
||||||
|
app->widget_about = widget_alloc();
|
||||||
|
widget_add_text_scroll_element(
|
||||||
|
app->widget_about,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
128,
|
||||||
|
64,
|
||||||
|
"Genie garage door recorder.\n---\nConnect door to pin A7!\n\nauthor: @codeallnight\nhttps://discord.com/invite/NsjCvqwPAd\nhttps://youtube.com/@MrDerekJamison");
|
||||||
|
view_set_previous_callback(
|
||||||
|
widget_get_view(app->widget_about), genie_navigation_submenu_callback);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, GenieViewAbout, widget_get_view(app->widget_about));
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_app_free(GenieApp* app) {
|
||||||
|
genie_subghz_free(app->genie_subghz);
|
||||||
|
furi_timer_free(app->timer);
|
||||||
|
furi_hal_gpio_init_simple(pin_remote, GpioModeAnalog);
|
||||||
|
furi_string_free(app->key);
|
||||||
|
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, GenieViewAbout);
|
||||||
|
widget_free(app->widget_about);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, GenieViewStart);
|
||||||
|
view_free(app->view);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, GenieViewSubmenu);
|
||||||
|
submenu_free(app->submenu);
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t genie_record_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
GenieApp* app = genie_app_alloc();
|
||||||
|
view_dispatcher_run(app->view_dispatcher);
|
||||||
|
genie_app_free(app);
|
||||||
|
return 0;
|
||||||
|
}
|
BIN
subghz/apps/genie-recorder/genie.png
Normal file
BIN
subghz/apps/genie-recorder/genie.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
115
subghz/apps/genie-recorder/genie_file.c
Normal file
115
subghz/apps/genie-recorder/genie_file.c
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#include <storage/storage.h>
|
||||||
|
#include <flipper_format.h>
|
||||||
|
|
||||||
|
#define GENIE_APPS_DATA_FOLDER EXT_PATH("apps_data")
|
||||||
|
#define GENIE_SAVE_FOLDER \
|
||||||
|
GENIE_APPS_DATA_FOLDER "/" \
|
||||||
|
"genie"
|
||||||
|
#define GENIE_SAVE_NAME "keys"
|
||||||
|
#define GENIE_SAVE_EXTENSION ".txt"
|
||||||
|
|
||||||
|
#ifdef TAG
|
||||||
|
#undef TAG
|
||||||
|
#endif
|
||||||
|
#define TAG "GenieFile"
|
||||||
|
|
||||||
|
static void ensure_dir_exists(Storage* storage, char* dir) {
|
||||||
|
if(!storage_dir_exists(storage, dir)) {
|
||||||
|
FURI_LOG_I(TAG, "Creating directory: %s", dir);
|
||||||
|
storage_simply_mkdir(storage, dir);
|
||||||
|
} else {
|
||||||
|
FURI_LOG_D(TAG, "Directory exists: %s", dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensure_save_folder_exists(Storage* storage) {
|
||||||
|
ensure_dir_exists(storage, GENIE_APPS_DATA_FOLDER);
|
||||||
|
ensure_dir_exists(storage, GENIE_SAVE_FOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool genie_save(uint32_t count, FuriString* key) {
|
||||||
|
bool success = false;
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
FuriString* file_path = furi_string_alloc();
|
||||||
|
|
||||||
|
FuriString* buffer = furi_string_alloc();
|
||||||
|
furi_string_printf(buffer, "%08lX,%s\r\n", count, furi_string_get_cstr(key));
|
||||||
|
|
||||||
|
furi_string_printf(
|
||||||
|
file_path, "%s/%s%s", GENIE_SAVE_FOLDER, GENIE_SAVE_NAME, GENIE_SAVE_EXTENSION);
|
||||||
|
|
||||||
|
File* file = NULL;
|
||||||
|
do {
|
||||||
|
if(!storage) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to access storage");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_save_folder_exists(storage);
|
||||||
|
|
||||||
|
file = storage_file_alloc(storage);
|
||||||
|
if(storage_file_open(file, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
||||||
|
FURI_LOG_D(TAG, "Writing to file: %s", furi_string_get_cstr(file_path));
|
||||||
|
|
||||||
|
if(!storage_file_write(file, furi_string_get_cstr(buffer), furi_string_size(buffer))) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to write to file");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
if(file) {
|
||||||
|
storage_file_close(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(file_path);
|
||||||
|
furi_string_free(buffer);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t genie_load() {
|
||||||
|
uint32_t count = 0;
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
FuriString* file_path = furi_string_alloc();
|
||||||
|
|
||||||
|
FuriString* buffer = furi_string_alloc();
|
||||||
|
|
||||||
|
furi_string_printf(
|
||||||
|
file_path, "%s/%s%s", GENIE_SAVE_FOLDER, GENIE_SAVE_NAME, GENIE_SAVE_EXTENSION);
|
||||||
|
|
||||||
|
File* file = NULL;
|
||||||
|
do {
|
||||||
|
if(!storage) {
|
||||||
|
FURI_LOG_E(TAG, "Failed to access storage");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_save_folder_exists(storage);
|
||||||
|
|
||||||
|
file = storage_file_alloc(storage);
|
||||||
|
if(storage_file_open(
|
||||||
|
file, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
|
FURI_LOG_D(TAG, "Reading file: %s", furi_string_get_cstr(file_path));
|
||||||
|
|
||||||
|
char data[8 + 1 + 16 + 2 + 1] = {0};
|
||||||
|
|
||||||
|
while(storage_file_read(file, data, 8 + 1 + 16 + 2)) {
|
||||||
|
sscanf(data, "%08lX", &count);
|
||||||
|
FURI_LOG_D(TAG, "Read: %s, count: %ld", data, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
if(file) {
|
||||||
|
storage_file_close(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(file_path);
|
||||||
|
furi_string_free(buffer);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
return count;
|
||||||
|
}
|
6
subghz/apps/genie-recorder/genie_file.h
Normal file
6
subghz/apps/genie-recorder/genie_file.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
bool genie_save(uint32_t count, FuriString* key);
|
||||||
|
uint32_t genie_load();
|
143
subghz/apps/genie-recorder/genie_subghz_receive.c
Normal file
143
subghz/apps/genie-recorder/genie_subghz_receive.c
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#include "genie_subghz_receive.h"
|
||||||
|
|
||||||
|
#ifdef TAG
|
||||||
|
#undef TAG
|
||||||
|
#endif
|
||||||
|
#define TAG "GenieSubGHzReceive"
|
||||||
|
|
||||||
|
static SubGhzEnvironment* load_environment() {
|
||||||
|
SubGhzEnvironment* environment = subghz_environment_alloc();
|
||||||
|
subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME);
|
||||||
|
subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME);
|
||||||
|
subghz_environment_set_came_atomo_rainbow_table_file_name(
|
||||||
|
environment, SUBGHZ_CAME_ATOMO_DIR_NAME);
|
||||||
|
subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
|
||||||
|
environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
|
||||||
|
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||||
|
environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
|
||||||
|
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
GenieSubGhz* genie_subghz_alloc() {
|
||||||
|
GenieSubGhz* subghz = malloc(sizeof(GenieSubGhz));
|
||||||
|
subghz->status = SUBGHZ_RECEIVER_UNINITIALIZED;
|
||||||
|
subghz->environment = load_environment();
|
||||||
|
subghz->stream = furi_stream_buffer_alloc(sizeof(LevelDuration) * 1024, sizeof(LevelDuration));
|
||||||
|
furi_check(subghz->stream);
|
||||||
|
subghz->overrun = false;
|
||||||
|
return subghz;
|
||||||
|
}
|
||||||
|
|
||||||
|
void genie_subghz_free(GenieSubGhz* subghz) {
|
||||||
|
subghz_environment_free(subghz->environment);
|
||||||
|
furi_stream_buffer_free(subghz->stream);
|
||||||
|
free(subghz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rx_callback(SubGhzReceiver* receiver, SubGhzProtocolDecoderBase* decoder_base, void* cxt) {
|
||||||
|
GenieSubGhz* context = (GenieSubGhz*)cxt;
|
||||||
|
FuriString* buffer = furi_string_alloc();
|
||||||
|
subghz_protocol_decoder_base_get_string(decoder_base, buffer);
|
||||||
|
subghz_receiver_reset(receiver);
|
||||||
|
FURI_LOG_I(TAG, "PACKET:\r\n%s", furi_string_get_cstr(buffer));
|
||||||
|
if(context->callback) {
|
||||||
|
context->callback(buffer, context->callback_context);
|
||||||
|
}
|
||||||
|
furi_string_free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rx_capture_callback(bool level, uint32_t duration, void* context) {
|
||||||
|
GenieSubGhz* instance = context;
|
||||||
|
|
||||||
|
LevelDuration level_duration = level_duration_make(level, duration);
|
||||||
|
if(instance->overrun) {
|
||||||
|
instance->overrun = false;
|
||||||
|
level_duration = level_duration_reset();
|
||||||
|
}
|
||||||
|
size_t ret =
|
||||||
|
furi_stream_buffer_send(instance->stream, &level_duration, sizeof(LevelDuration), 0);
|
||||||
|
if(sizeof(LevelDuration) != ret) {
|
||||||
|
instance->overrun = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t listen_rx(void* ctx) {
|
||||||
|
GenieSubGhz* context = (GenieSubGhz*)ctx;
|
||||||
|
context->status = SUBGHZ_RECEIVER_LISTENING;
|
||||||
|
LevelDuration level_duration;
|
||||||
|
FURI_LOG_I(TAG, "listen_rx started...");
|
||||||
|
while(context->status == SUBGHZ_RECEIVER_LISTENING) {
|
||||||
|
int ret = furi_stream_buffer_receive(
|
||||||
|
context->stream, &level_duration, sizeof(LevelDuration), 10);
|
||||||
|
|
||||||
|
if(ret == sizeof(LevelDuration)) {
|
||||||
|
if(level_duration_is_reset(level_duration)) {
|
||||||
|
subghz_receiver_reset(context->receiver);
|
||||||
|
} else {
|
||||||
|
bool level = level_duration_get_level(level_duration);
|
||||||
|
uint32_t duration = level_duration_get_duration(level_duration);
|
||||||
|
subghz_receiver_decode(context->receiver, level, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FURI_LOG_I(TAG, "listen_rx exiting...");
|
||||||
|
context->status = SUBGHZ_RECEIVER_NOTLISTENING;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_listening(
|
||||||
|
GenieSubGhz* context,
|
||||||
|
uint32_t frequency,
|
||||||
|
SubghzPacketCallback callback,
|
||||||
|
void* callback_context) {
|
||||||
|
context->status = SUBGHZ_RECEIVER_INITIALIZING;
|
||||||
|
|
||||||
|
context->callback = callback;
|
||||||
|
context->callback_context = callback_context;
|
||||||
|
subghz_devices_init();
|
||||||
|
const SubGhzDevice* device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
|
||||||
|
if(!subghz_devices_is_frequency_valid(device, frequency)) {
|
||||||
|
FURI_LOG_E(TAG, "Frequency not in range. %lu\r\n", frequency);
|
||||||
|
subghz_devices_deinit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->receiver = subghz_receiver_alloc_init(context->environment);
|
||||||
|
subghz_receiver_set_filter(context->receiver, SubGhzProtocolFlag_Decodable);
|
||||||
|
subghz_receiver_set_rx_callback(context->receiver, rx_callback, context);
|
||||||
|
|
||||||
|
subghz_devices_begin(device);
|
||||||
|
subghz_devices_reset(device);
|
||||||
|
subghz_devices_load_preset(device, FuriHalSubGhzPresetOok650Async, NULL);
|
||||||
|
frequency = subghz_devices_set_frequency(device, frequency);
|
||||||
|
|
||||||
|
furi_hal_power_suppress_charge_enter();
|
||||||
|
|
||||||
|
subghz_devices_start_async_rx(device, rx_capture_callback, context);
|
||||||
|
|
||||||
|
FURI_LOG_I(TAG, "Listening at frequency: %lu\r\n", frequency);
|
||||||
|
|
||||||
|
context->thread = furi_thread_alloc_ex("RX", 1024, listen_rx, context);
|
||||||
|
furi_thread_start(context->thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_listening(GenieSubGhz* context) {
|
||||||
|
if(context->status == SUBGHZ_RECEIVER_UNINITIALIZED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->status = SUBGHZ_RECEIVER_UNINITIALING;
|
||||||
|
FURI_LOG_D(TAG, "Stopping listening...");
|
||||||
|
furi_thread_join(context->thread);
|
||||||
|
furi_thread_free(context->thread);
|
||||||
|
furi_hal_power_suppress_charge_exit();
|
||||||
|
|
||||||
|
const SubGhzDevice* device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
|
||||||
|
subghz_devices_stop_async_rx(device);
|
||||||
|
|
||||||
|
subghz_receiver_free(context->receiver);
|
||||||
|
subghz_devices_deinit();
|
||||||
|
context->status = SUBGHZ_RECEIVER_UNINITIALIZED;
|
||||||
|
}
|
40
subghz/apps/genie-recorder/genie_subghz_receive.h
Normal file
40
subghz/apps/genie-recorder/genie_subghz_receive.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <lib/subghz/receiver.h>
|
||||||
|
#include <lib/subghz/protocols/protocol_items.h>
|
||||||
|
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
|
||||||
|
#include <lib/subghz/devices/devices.h>
|
||||||
|
|
||||||
|
typedef void (*SubghzPacketCallback)(FuriString* buffer, void* context);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SUBGHZ_RECEIVER_INITIALIZING,
|
||||||
|
SUBGHZ_RECEIVER_LISTENING,
|
||||||
|
SUBGHZ_RECEIVER_NOTLISTENING,
|
||||||
|
SUBGHZ_RECEIVER_UNINITIALING,
|
||||||
|
SUBGHZ_RECEIVER_UNINITIALIZED,
|
||||||
|
} SubghzReceiverState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SubGhzEnvironment* environment;
|
||||||
|
FuriStreamBuffer* stream;
|
||||||
|
FuriThread* thread;
|
||||||
|
SubGhzReceiver* receiver;
|
||||||
|
bool overrun;
|
||||||
|
SubghzReceiverState status;
|
||||||
|
SubghzPacketCallback callback;
|
||||||
|
void* callback_context;
|
||||||
|
} GenieSubGhz;
|
||||||
|
|
||||||
|
GenieSubGhz* genie_subghz_alloc();
|
||||||
|
void genie_subghz_free(GenieSubGhz* subghz);
|
||||||
|
|
||||||
|
void start_listening(
|
||||||
|
GenieSubGhz* context,
|
||||||
|
uint32_t frequency,
|
||||||
|
SubghzPacketCallback callback,
|
||||||
|
void* callback_context);
|
||||||
|
void stop_listening(GenieSubGhz* context);
|
Loading…
Reference in New Issue
Block a user