Genie Recorder v3

This commit is contained in:
Derek Jamison 2024-02-04 13:13:58 -06:00
parent 95d939cd2a
commit b995a55ef3
46 changed files with 1825 additions and 445 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

View File

@ -1,136 +1,89 @@
# Genie garage door recorder
IMPORTANT: This project currently **requires changes to the firmware** to be able to detect Genie signals. Please see the [Installing](#installing) section for more details.
<p/><p/>
WARNING: For my remote, the codes wrapped after 65,536 codes were sent. I'm not sure if this is the case for ALL Genie brand remotes. If it doesn't wrap, it's possible that the remote could stop working (if the manufacture implemented OVR bits).
<p/><p/>
WARNING: This could desync your remote from the receiver. Be sure you know the process to sync the remote back to the receiver. For my remote, I had to press the learn button on the receiver, then press the button on the remote.
<p/><p/>
WARNING: Don't run this app near your garage. There is no reason to open the physical garage door & you will likely burn out the motor.
## Overview
Document sections:
- [Description](#description)
- [Installing](#installing)
- [Determine Genie Frequency](#determine-genie-frequency)
Version 3.x no longer requires changes to the firmware! The application includes the Genie protocol encoder/decoder, thanks to @MMX for the suggestion.
- [Warnings](#warnings)
- [Connecting to remote](#connecting-to-remote)
- [Capture codes](#capture-codes)
- [Sub-GHz Read](#sub-ghz-read)
- [Sub-GHz Saved](#sub-ghz-saved)
- [Troubleshooting](#troubleshooting)
- [Sub-GHz application](#sub-ghz-application)
- [Read](#read)
- [Details](#details)
- [No Keys](#no-keys)
- [Key Missing](#key-missing)
- [Sending signals](#sending-signals)
- [.GNE file format](#gne-file-format)
## 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. In practice, it typically takes 2 days to record all of your signals.
This program was written to allow the Flipper Zero to press a button on a Genie garage door remote 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. In practice, it typically takes 2 days to record all of your signals.
Once completed, the built-in Sub-GHz Read application will know how to playback your Genie garage door remote.
The application also has the ability to send the next code to the garage door opener. This starts from the first captured signal.
## Installing
- Step 1. Clone a firmware repro using ``git clone --recursive``.
- Step 2. Copy [these files](https://github.com/jamisonderek/flipper-zero-tutorials/tree/main/subghz/apps/genie-recorder) into your firmware ``applications_user\genie-recorder`` folder.
- Step 3. Move the files from ``applications_user\genie-recorder\lib\subghz\protocols`` to ``lib\subghz\protocols`` folder.
- Step 4. Add ``&subghz_protocol_genie,`` below the line ``&subghz_protocol_keeloq,`` in the ``lib\subghz\protocols\protocol_items.c`` file. (NOTE: You can add it lower in the file if you prefer.)
- Step 5. Add ``#include "genie.h`` below the line ``#include "keeloq.h`` in the ``lib\subghz\protocols\protocol_items.h`` file.
- Step 6. Build your firmware & deploy onto Flipper ``./fbt FORCE=1 flash_usb_full`` -- "[Debug]Flash (USB, with Resources)".
This application is intended to **replace** the remote you captured. You should not use your original remote again once you are using the Flipper Zero or they will get out of sync. It is recommended that you capture a backup remote you are not using.
## Determine Genie Frequency
- Step 1. On your Flipper Zero, load ``Sub-GHz`` app.
- Step 2. Choose ``Read`` to start scanning.
- Step 3. Press the LEFT button to edit the Config.
- Step 4. Change the ``Frequency`` to 315000000.
- Step 5. Press the BACK button to continue scanning on the new frequency.
- Step 6. Press a button on your remote to see if it is detected.
- Step 7. If it is NOT detected, try 390000000.
The files are stored in `SD Card/apps_data/genie`. You can copy the `007F1991.gne` file to your Flipper if you want to use a default remote. On your receiver you will need to sync to the Flipper.
## Warnings
WARNING: For my remote, the codes wrapped after 65,536 codes were sent. I'm not sure if this is the case for ALL Genie brand remotes. If it doesn't wrap, it's possible that the remote could stop working (if the manufacture uses OVR bits).
WARNING: This could desync your remote from the receiver. Be sure you know the process to sync the remote back to the receiver. For my remote, I had to press the learn button on the receiver, then press the button on the remote.
WARNING: Don't run this app near your garage. There is no reason to open the physical garage door & you will likely burn out the motor.
## Connecting to remote
<img src="wiring.png" width="45%"/>
<img src="wiring-2.jpg" width="40%"/>
<p/>
WARNING: Do this **at your own risk**. You could damage your remote if done improperly or if your remote doesn't support capturing all of the signals.
WARNING: Do this **at your own risk**. You could damage your remote or Flipper Zero if done improperly.
- Step 1. Open the case off of your garage door remote.
- Step 2. Connect GND from Flipper to GND pin on the remote (Shown in green traces).
- Step 3. Connect A7 from Flipper to the signal pin on the remote (Shown in orange traces - top button, cyan - middle button).
- Step 1. Remove the case of your garage door remote.
- Step 2. Connect GND from Flipper to GND pin on the remote (Shown in green traces for my remote).
- Step 3. Connect A7 from Flipper to the signal pin on the remote (Shown in orange traces - top button, cyan - middle button for my remote).
- Step 4. Put in a fresh battery.
- Risky Option: Remove the battery and connect 3V3 from Flipper to the battery positive bar on the remote. Be 100% sure that GND on the Flipper is going to GND on the remote (and not the signal pin) and that no wires are shorting. If you are not 100% sure, then DON'T DO THIS! You could damage the remote and the Flipper Zero.
- Risky Option: Remove the battery and connect 3V3 from Flipper to the battery positive bar on the remote. Be 100% sure that GND on the Flipper is going to GND on the remote (and not the signal pin) and that no wires are shorting. If you are not 100% sure, then DON'T DO THIS! You could damage the remote and the Flipper Zero.
## Capture codes
- Step 1. Be sure you have followed the steps in the [Installing](#installing) section.
- Step 2. Be sure you have followed the steps in the [Connecting to remote](#connecting-to-remote) section.
- Step 3. Be sure you are not near your garage door (or that the door is unplugged). The application will be pushing the button many times and could burn out the motor.
- Step 4. Run the Genie Recorder application
- Step 5. Choose "Config" and set the frequency to the frequency you determined above.
- Step 6. 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 how many codes still need to be received.
- Every time a signal is recevied, it is written to a log file. If the application exits and restarts, it will resume the counters where it left off, but be sure to NOT press the remote because the counter will be off.
- Step 7. Let it run for 2-3 days (the goal is to capture between 49,160-65,536 signals). If you capture less, it will still work but when it wraps back to the beginning those codes will be rejected by your garage door opener. To successfully wrap, the remote needs 49,160 button presses ("16,380 remaining codes" or less). To be near-sync with the remote, it is recommended you capture all of the codes.
- Step 8. Press the BACK button twice to exit the application.
## Sub-GHz Read
- Step 1. Be sure you have followed the steps in the [Capturing codes](#capture-codes) section.
- Step 2. On your Flipper, press the BACK button until you are at the Desktop.
- Step 3. On your Flipper, press the OK button and choose "Sub-GHz".
- Step 4. Choose "Read" to start scanning.
- Step 5. Press the LEFT button to edit the Config.
- Step 6. Change the ``Frequency`` to the frequency you determined above.
- Step 7. Press the BACK button to continue scanning on the new frequency.
- Step 8. Press a button on your Genie remote. If read successfully you should hear a beep and see "Genie" followed by some code.
- Step 9. Press the OK button to see the details of the code.
- Step 10. Most firmware will have a "Save" button. Press "Save" to save the code to the SD Card. Press "Send" to send the code to the garage door opener.
- Step 11. If you press "Send", it should generate the next code and open the garage door.
- Step 1. Be sure you have followed the steps in the [Connecting to remote](#connecting-to-remote) section.
- Step 2. Be sure you are not near your garage door (or that the door is unplugged). The application will be pushing the button many times and could burn out the motor!
- Step 3. Run the Genie Recorder application.
- Step 4. Choose `Config` and set the frequency to the frequency your remote runs at. If you don't know, start with `315MHz` then use `390MHz` if it doesn't work. NOTE: You do not need to do anything with the `Genie File` setting at this time.
- Step 5. Press `Back` button to return to the main application.
- Step 6. Choose `Learn` to start capturing and learning signals.
- `Remaining code` - how many codes still need to be captured.
- `Click` - how many times a click has been sent to the remote.
- `Prev` - how many codes were previously captured (no codes captured this session yet).
- `Got` - total number of codes captured.
- 16 digit hex code - the last code that was captured.
- Step 7. If you get the message `NO SIGNAL FROM REMOTE?` see the [troubleshooting](#troubleshooting) section.
- Step 8. Let it run for 2-3 days (the goal is to capture at least 50,000 signals). If you capture less, it will still work but when it wraps back to the beginning those codes will be rejected by your garage door opener and you would have to resync. To successfully wrap, the remote needs 50,000 button presses. It is recommended you capture all of the codes.
- Step 9. Press the BACK button to return to the main application.
## Sending signals
## Sub-GHz Saved
- Step 1. Be sure you have followed the steps in the [Capturing codes](#capture-codes) section.
- Step 2. On your Flipper, press the BACK button until you are at the Desktop.
- Step 3. On your Flipper, press the OK button and choose "Sub-GHz".
- Step 4. Choose "Saved".
- Step 5. Select the file that you saved during the [Sub-GHz Read](#sub-ghz-read) section steps.
- Step 6. Choose "Emulate".
- Step 7. Press the OK button to generate the next code and open the garage door!
- NOTE: If the receiver is out of sync, but within the 16K window, you may need to press the OK button twice. This will open the garage door and resync the counter on the receiver.
- Step 2. Run the Genie Recorder application.
- Step 3. Choose `Config` and set the frequency to the frequency your remote runs at.
- Step 4. Choose `Genie File` and select the .GNE file that was saved during the [Capture codes](#capture-codes) section steps.
- Step 5. Press `Back` button to return to the main application.
- Step 6. Choose `Send` to load the send screen.
- `NO .GNE FILE LOADED` - Confirm you selected a valid file in step 4.
- `Remote not fully captured` - You should continue to [capture signals](#capture-codes) but you can use the application.
- `Press OK to send signal` - Press the OK button to send the next code to the garage door opener.
- `Long press OK to reset code.` - This will desync the Flipper from the receiver. It will start over, transmitting the first code.
## Troubleshooting
- If the LED on the remote is not blinking, be sure you have followed the steps in the [Connecting to remote](#connecting-to-remote) section.
- If the application is not detecting the remote, but the LED on the remote is blinking, then perhaps you missed the step in [Installing](#installing) section or the frequency is incorrect.
- If the application is not detecting the remote, but the LED on the remote is blinking, then ensure the frequency is correct.
## Sub-GHz application
### Read
<img src="subghz/subghz-read.png" width="45%"/>
<p/>
If the frequency is correct, then you should see the "Genie" entry when you press a button on your remote. If the frequency is correct (typically 315MHz or 390MHz) then perhaps you missed the step in [Installing](#installing) where you copy the files into the ``lib\subghz\protocols`` directory, or the step where you edit the ``protocol_items`` files?
### Details
<img src="subghz/subghz-read-details-good.png" width="45%"/>
<p/>
When a code and the next code is found in the .GNE file, you should see the KEY and the NEXT CODE. Official firmware does not support the "Send" feature for rolling codes.
### No Keys
<img src="subghz/subghz-read-details-nokeys.png" width="45%"/>
<p/>
When the .GNE file does not exists, you will get the "No Keys" error message. You need to make sure you run the Genie Recorder application to capture the codes first.
### Key Missing
<img src="subghz/subghz-read-details-keymissing.png" width="45%"/>
<p/>
When the .GNE file exists, but the key is not found (or the next code is not found), you will get the "Key Missing" error message. You need to make sure you run the Genie Recorder application to capture the codes first.
- If sending signal is not working, then ensure the frequency is correct.
## .GNE file format
The file format is as follows:
- 2 bytes: "G*" (0x47 0x2A)
- 2 bytes: "G\*" (0x47 0x2A)
- 1 byte: Major version (0x02)
- 1 byte: Minor version (e.g. 0x05)
- 4 bytes: Fix code from remote (matches filename.GNE)
@ -139,4 +92,4 @@ The file format is as follows:
- 4 bytes: Reserved for future use.
- 4 bytes x 65536: The dynamic part of the code (0x00000000 if no code has been received)
The file can be found in the ``SD Card\apps_data\genie`` folder on your Flipper Zero. The name of the file will match the ending 8 characters of the fix code.
The file can be found in the `SD Card\apps_data\genie` folder on your Flipper Zero. The name of the file will match the ending 8 characters of the fix code.

View File

@ -1,13 +1,13 @@
App(
appid="genie_record_v2",
name="Genie Door Recorder v2",
appid="genie_record_v3",
name="Genie Door Recorder v3",
apptype=FlipperAppType.EXTERNAL,
entry_point="genie_record_app",
requires=["gui", "subghz"],
stack_size=4 * 1024,
fap_version=(2, 5),
fap_version=(3, 0),
fap_icon="genie.png",
fap_category="Sub-GHz",
fap_icon_assets="assets",
fap_description="This application connects to a Genie garage door remote and capture all of the codes. The Sub-GHz app can then be used to replay the codes to open the door.",
fap_description="This application extracts the codes from a Genie garage door remote into a .GNE file. It also plays back a .GNE file to a Genie garage door opener.",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -7,316 +7,19 @@
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include "genie_subghz_receive.h"
#include "genie_about.h"
#include "genie_app.h"
#include "genie_file.h"
#include "genie_learn.h"
#include "genie_subghz_receive.h"
#include "genie_submenu.h"
#define TAG "GenieRecord"
#define CLICK_SPEED 2000
const GpioPin* const pin_remote = &gpio_ext_pa7;
typedef enum {
GenieSubmenuIndexConfig,
GenieSubmenuIndexStart,
GenieSubmenuIndexAbout,
} GenieSubmenuIndex;
typedef enum {
GenieViewSubmenu,
GenieViewConfig,
GenieViewStart,
GenieViewAbout,
} GenieView;
typedef struct {
ViewDispatcher* view_dispatcher;
Submenu* submenu;
View* view;
Widget* widget_about;
VariableItemList* variable_item_list_config;
GenieSubGhz* genie_subghz;
uint32_t frequency;
FuriTimer* timer;
bool processing;
bool pressed;
uint32_t click_counter;
uint32_t rx_counter;
uint32_t genie_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
*/
static 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
*/
static uint32_t genie_navigation_submenu_callback(void* context) {
UNUSED(context);
return GenieViewSubmenu;
}
static void genie_submenu_callback(void* context, uint32_t index) {
GenieApp* app = (GenieApp*)context;
switch(index) {
case GenieSubmenuIndexConfig:
view_dispatcher_switch_to_view(app->view_dispatcher, GenieViewConfig);
break;
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;
}
}
static uint32_t setting_frequency_values[] = {315000000, 390000000};
static char* setting_frequency_names[] = {"315 MHz", "390MHz"};
static void genie_setting_frequency_change(VariableItem* item) {
GenieApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, setting_frequency_names[index]);
app->frequency = setting_frequency_values[index];
}
static void genie_view_draw_callback(Canvas* canvas, void* model) {
GenieApp* app = ((GenieAppRef*)model)->ref;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 5, 10, "Genie Sub-Ghz Recorder!!!");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 5, 20, "A7/GND to Genie remote");
canvas_draw_str(canvas, 100, 58, (app->pressed) ? "SEND" : "");
char buffer[30] = {0};
snprintf(buffer, COUNT_OF(buffer), "Click %ld", app->click_counter);
canvas_draw_str(canvas, 1, 45, buffer);
snprintf(
buffer,
COUNT_OF(buffer),
"Got %ld",
app->genie_save_counter > 0 ? app->genie_save_counter : app->rx_counter);
canvas_draw_str(canvas, 75, 45, buffer);
if(app->genie_save_counter < 0x10000) {
snprintf(buffer, COUNT_OF(buffer), "Remaining codes %ld", 65536 - app->genie_save_counter);
canvas_draw_str(canvas, 1, 32, buffer);
} else {
canvas_draw_str(canvas, 1, 30, "Found all codes!");
}
canvas_draw_str(canvas, 5, 55, furi_string_get_cstr(app->key));
}
static bool genie_view_input_callback(InputEvent* event, void* context) {
UNUSED(context);
UNUSED(event);
return false;
}
static uint32_t last_count() {
uint32_t count = genie_load();
return count;
}
static uint32_t save_count(uint32_t count, FuriString* key, bool is_genie) {
FURI_LOG_D(TAG, "%ld,%s", count, furi_string_get_cstr(key));
genie_save(count, key);
if(is_genie) {
return genie_save_bin(furi_string_get_cstr(key));
}
return 0;
}
static void __gui_redraw() {
// Redraw screen
Gui* gui = furi_record_open(RECORD_GUI);
gui_direct_draw_acquire(gui);
gui_direct_draw_release(gui);
}
static void press_button(GenieApp* app) {
furi_hal_gpio_write(pin_remote, false);
app->pressed = true;
__gui_redraw();
}
static void release_button(GenieApp* app) {
furi_hal_gpio_write(pin_remote, true);
app->pressed = false;
__gui_redraw();
}
static void genie_packet(FuriString* buffer, void* context) {
GenieApp* app = (GenieApp*)context;
app->processing = true;
/*
if(furi_string_search_str(buffer, "KeeLoq 64bit") < furi_string_size(buffer)) {
release_button(app);
FURI_LOG_D(TAG, "KeeLoq 64bit packet");
size_t key_index = furi_string_search_str(buffer, "Key:");
if(key_index < furi_string_size(buffer)) {
furi_string_set_n(app->key, buffer, key_index + 4, 16);
app->rx_counter++;
save_count(app->click_counter, app->key, false);
}
} else
*/
if(furi_string_search_str(buffer, "Genie 64bit") < furi_string_size(buffer)) {
release_button(app);
FURI_LOG_D(TAG, "Genie 64bit packet");
size_t key_index = furi_string_search_str(buffer, "Key:");
if(key_index < furi_string_size(buffer)) {
furi_string_set_n(app->key, buffer, key_index + 4, 16);
app->rx_counter++;
app->genie_save_counter = save_count(app->click_counter, app->key, true);
}
}
app->processing = false;
}
static void genie_tick(void* context) {
GenieApp* app = (GenieApp*)context;
if(!app->processing) {
if(app->genie_save_counter > 0xFFFF) {
release_button(app);
} else if(app->pressed) {
release_button(app);
} else {
app->click_counter++;
press_button(app);
}
}
}
static void genie_enter_callback(void* context) {
GenieApp* app = (GenieApp*)context;
genie_file_init();
start_listening(app->genie_subghz, app->frequency, genie_packet, context);
furi_timer_start(app->timer, furi_ms_to_ticks(CLICK_SPEED));
}
static 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);
}
static 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->click_counter = 0;
app->rx_counter = last_count();
app->genie_save_counter = 0;
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, "Config", GenieSubmenuIndexConfig, genie_submenu_callback, app);
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->variable_item_list_config = variable_item_list_alloc();
VariableItem* item = variable_item_list_add(
app->variable_item_list_config,
"Frequency",
COUNT_OF(setting_frequency_names),
genie_setting_frequency_change,
app);
uint8_t default_freq_index = 0;
variable_item_set_current_value_index(item, default_freq_index);
variable_item_set_current_value_text(item, setting_frequency_names[default_freq_index]);
app->frequency = setting_frequency_values[default_freq_index];
view_set_previous_callback(
variable_item_list_get_view(app->variable_item_list_config),
genie_navigation_submenu_callback);
view_dispatcher_add_view(
app->view_dispatcher,
GenieViewConfig,
variable_item_list_get_view(app->variable_item_list_config));
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_ABOUT_TEXT);
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;
}
static 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, GenieViewConfig);
variable_item_list_free(app->variable_item_list_config);
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);
view_dispatcher_run(genie_app_get_view_dispatcher(app));
genie_app_free(app);
return 0;
}

View File

@ -2,21 +2,17 @@
#define GENIE_ABOUT_TEXT \
"Genie garage door recorder.\n" \
"Version 2.5\n---\n" \
"Custom genie firmware is\n" \
"required for this app.\n" \
"---\n" \
"Version 3.0\n---\n" \
"This app clicks your remote\n" \
"and records the keys, so you\n" \
"can replay them later when\n" \
"you load a Sub-GHz file!\n" \
"can replay them later with\n" \
"the application!\n" \
"---\n" \
"Connect Genie remote to pin\n" \
"A7 and GND. Wiring diagram\n" \
"at https://tinyurl.com/genierecorder or github.\n" \
"Once remote connected and\n" \
"firmware is updated,\n" \
"click Start to capture!\n---\n" \
"Once remote connected," \
"click Learn to capture!\n---\n" \
"author: @codeallnight\n" \
"https://discord.com/invite/NsjCvqwPAd\n" \
"https://youtube.com/@MrDerekJamison\n" \

View File

@ -0,0 +1,244 @@
#include "genie_app.h"
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include "genie_subghz_receive.h"
#include "genie_file.h"
#include "genie_submenu.h"
#include "genie_send.h"
#include "genie_config.h"
#include "genie_learn.h"
#include "genie_about.h"
#include "genie_ini.h"
typedef enum {
GenieViewSubmenu,
GenieViewSend,
GenieViewConfig,
GenieViewLearn,
GenieViewAbout,
} GenieView;
struct GenieApp {
ViewDispatcher* view_dispatcher;
GenieSubmenu* submenu;
GenieSend* send_view;
View* learn_view;
Widget* widget_about;
GenieConfig* genie_config;
GenieSubGhz* genie_subghz;
uint32_t frequency;
FuriTimer* timer;
bool processing;
bool pressed;
uint32_t click_counter;
uint32_t try_counter;
uint32_t rx_counter;
uint32_t genie_save_counter;
FuriString* key;
FuriString* genie_saved_file_path;
};
extern const GpioPin* const pin_remote;
ViewDispatcher* genie_app_get_view_dispatcher(GenieApp* app) {
return app->view_dispatcher;
}
GenieConfig* genie_app_get_genie_config(GenieApp* app) {
return app->genie_config;
}
GenieSubGhz* genie_app_get_subghz(GenieApp* app) {
return app->genie_subghz;
}
void genie_app_start_timer(GenieApp* app, uint32_t ms) {
furi_timer_start(app->timer, furi_ms_to_ticks(ms));
app->try_counter = 0;
app->click_counter = 0;
}
void genie_app_stop_timer(GenieApp* app) {
furi_timer_stop(app->timer);
}
void genie_app_set_frequency(GenieApp* app, uint32_t frequency) {
app->frequency = frequency;
}
uint32_t genie_app_get_frequency(GenieApp* app) {
return app->frequency;
}
void genie_app_gpio_send(GenieApp* app, bool sending_signal) {
app->pressed = sending_signal;
}
bool genie_app_is_sending_signal(GenieApp* app) {
return app->pressed;
}
uint32_t genie_app_get_click_counter(GenieApp* app) {
return app->click_counter;
}
void genie_app_increment_click_counter(GenieApp* app) {
app->click_counter++;
app->try_counter++;
}
bool genie_app_has_no_signal(GenieApp* app) {
UNUSED(app);
return app->try_counter > 3;
}
FuriString* genie_app_get_key(GenieApp* app) {
return app->key;
}
const char* genie_app_get_file_path(GenieApp* app) {
return furi_string_get_cstr(app->genie_saved_file_path);
}
void genie_app_update_file_path(GenieApp* app, const char* file_path) {
furi_string_set(app->genie_saved_file_path, file_path);
}
void genie_app_update_save_counter(GenieApp* app, uint32_t num_saved) {
app->genie_save_counter = num_saved;
}
uint32_t genie_app_get_save_counter(GenieApp* app) {
return app->genie_save_counter;
}
uint32_t genie_app_get_rx_counter(GenieApp* app) {
return app->rx_counter;
}
void genie_app_set_processing_packet(GenieApp* app, bool processing_packet) {
app->processing = processing_packet;
}
bool genie_app_is_processing_packet(GenieApp* app) {
return app->processing;
}
void genie_app_received_key(GenieApp* app, FuriString* buffer) {
size_t key_index = furi_string_search_str(buffer, "Key:");
furi_string_set_n(app->key, buffer, key_index + 4, 16);
app->rx_counter++;
app->try_counter = 0;
}
static 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);
genie_app_gpio_send(app, true);
__gui_redraw();
}
void release_button(GenieApp* app) {
furi_hal_gpio_write(pin_remote, true);
genie_app_gpio_send(app, false);
__gui_redraw();
}
static void genie_tick(void* context) {
GenieApp* app = (GenieApp*)context;
if(!genie_app_is_processing_packet(app)) {
if(genie_app_get_save_counter(app) > 0xFFFF) {
release_button(app);
} else if(genie_app_is_sending_signal(app)) {
release_button(app);
} else if(!genie_app_has_no_signal(app)) {
genie_app_increment_click_counter(app);
press_button(app);
}
}
}
GenieApp* genie_app_alloc() {
GenieApp* app = (GenieApp*)malloc(sizeof(GenieApp));
app->genie_subghz = genie_subghz_alloc();
app->rx_counter = genie_load();
app->try_counter = 0;
app->click_counter = 0;
app->genie_save_counter = 0;
app->key = furi_string_alloc();
app->genie_saved_file_path = furi_string_alloc();
app->timer = furi_timer_alloc(genie_tick, FuriTimerTypePeriodic, app);
furi_hal_gpio_init_simple(pin_remote, GpioModeOutputOpenDrain);
release_button(app);
genie_ini_load(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 = genie_submenu_alloc(app);
View* submenu_view = genie_submenu_get_view(app->submenu);
view_dispatcher_add_view(app->view_dispatcher, GenieViewSubmenu, submenu_view);
view_dispatcher_switch_to_view(app->view_dispatcher, GenieViewSubmenu);
app->send_view = genie_send_alloc(app);
View* send_view = genie_send_get_view(app->send_view);
view_dispatcher_add_view(app->view_dispatcher, GenieViewSend, send_view);
app->genie_config = genie_config_alloc(app);
View* config_view = genie_config_get_view(app->genie_config);
view_dispatcher_add_view(app->view_dispatcher, GenieViewConfig, config_view);
app->learn_view = genie_learn_alloc(app);
view_dispatcher_add_view(app->view_dispatcher, GenieViewLearn, app->learn_view);
app->widget_about = widget_alloc();
widget_add_text_scroll_element(app->widget_about, 0, 0, 128, 64, GENIE_ABOUT_TEXT);
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_ini_save(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);
furi_string_free(app->genie_saved_file_path);
view_dispatcher_remove_view(app->view_dispatcher, GenieViewSend);
genie_send_free(app->send_view);
view_dispatcher_remove_view(app->view_dispatcher, GenieViewConfig);
genie_config_free(app->genie_config);
view_dispatcher_remove_view(app->view_dispatcher, GenieViewAbout);
widget_free(app->widget_about);
view_dispatcher_remove_view(app->view_dispatcher, GenieViewLearn);
genie_learn_free(app->learn_view);
view_dispatcher_remove_view(app->view_dispatcher, GenieViewSubmenu);
genie_submenu_free(app->submenu);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
free(app);
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <furi.h>
#include <gui/view_dispatcher.h>
typedef struct GenieApp GenieApp;
typedef struct GenieConfig GenieConfig;
typedef struct GenieSubGhz GenieSubGhz;
ViewDispatcher* genie_app_get_view_dispatcher(GenieApp* app);
GenieConfig* genie_app_get_genie_config(GenieApp* app);
GenieSubGhz* genie_app_get_subghz(GenieApp* app);
void genie_app_start_timer(GenieApp* app, uint32_t ms);
void genie_app_stop_timer(GenieApp* app);
void genie_app_set_frequency(GenieApp* app, uint32_t frequency);
uint32_t genie_app_get_frequency(GenieApp* app);
void genie_app_gpio_send(GenieApp* app, bool sending_signal);
bool genie_app_is_sending_signal(GenieApp* app);
uint32_t genie_app_get_click_counter(GenieApp* app);
void genie_app_increment_click_counter(GenieApp* app);
bool genie_app_has_no_signal(GenieApp* app);
FuriString* genie_app_get_key(GenieApp* app);
const char* genie_app_get_file_path(GenieApp* app);
void genie_app_update_file_path(GenieApp* app, const char* file_path);
void genie_app_update_save_counter(GenieApp* app, uint32_t num_saved);
uint32_t genie_app_get_save_counter(GenieApp* app);
uint32_t genie_app_get_rx_counter(GenieApp* app);
void genie_app_set_processing_packet(GenieApp* app, bool processing_packet);
bool genie_app_is_processing_packet(GenieApp* app);
void genie_app_received_key(GenieApp* app, FuriString* buffer);
void press_button(GenieApp* app);
void release_button(GenieApp* app);
GenieApp* genie_app_alloc();
void genie_app_free(GenieApp* app);

View File

@ -0,0 +1,166 @@
#include "genie_config.h"
#include <furi.h>
#include <furi_hal.h>
#include "genie_app.h"
#include "genie_submenu.h"
#include <dialogs/dialogs.h>
#include <storage/storage.h>
#include "toolbox/path.h"
#include "genie_record_v3_icons.h"
#define TAG "GenieConfig"
struct GenieConfig {
VariableItemList* variable_item_list;
GenieApp* app;
VariableItem* file_item;
FuriThread* thread;
DialogsApp* dialogs;
FuriString* tmp;
};
#define GENIE_SAVE_FOLDER \
EXT_PATH("apps_data") \
"/" \
"genie"
bool select_genie_file(GenieConfig* config) {
GenieApp* app = config->app;
const char* previous_path = genie_app_get_file_path(app);
if(previous_path && previous_path[0] != '\0') {
furi_string_set(config->tmp, previous_path);
} else {
furi_string_set(config->tmp, GENIE_SAVE_FOLDER);
}
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, ".gne", &I_genie_10x10);
browser_options.base_path = GENIE_SAVE_FOLDER;
browser_options.skip_assets = false;
browser_options.item_loader_callback = NULL;
browser_options.item_loader_context = NULL;
bool file_selected =
dialog_file_browser_show(config->dialogs, config->tmp, config->tmp, &browser_options);
if(file_selected) {
FURI_LOG_D("TAG", "Selected file: %s", furi_string_get_cstr(config->tmp));
genie_app_update_file_path(app, furi_string_get_cstr(config->tmp));
} else {
FURI_LOG_D("TAG", "No file selected.");
}
return file_selected;
}
static uint32_t setting_frequency_values[] = {315000000, 390000000};
static char* setting_frequency_names[] = {"315 MHz", "390 MHz"};
static void genie_setting_frequency_change(VariableItem* item) {
GenieApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, setting_frequency_names[index]);
genie_app_set_frequency(app, setting_frequency_values[index]);
}
static void genie_config_update_file_item(GenieConfig* genie_config) {
FuriString* current_name = furi_string_alloc();
const char* current_path = genie_app_get_file_path(genie_config->app);
if(current_path && current_path[0] != '\0') {
path_extract_filename_no_ext(current_path, current_name);
} else {
furi_string_set(current_name, "Select");
}
variable_item_set_current_value_text(
genie_config->file_item, furi_string_get_cstr(current_name));
furi_string_free(current_name);
}
static void genie_config_ok_button(void* context, uint32_t index) {
FURI_LOG_D(TAG, "OK button clicked. index = %ld", index);
if(index != 1) {
return;
}
GenieConfig* genie_config = (GenieConfig*)context;
furi_thread_flags_set(furi_thread_get_id(genie_config->thread), 1);
}
static int32_t genie_config_thread(void* context) {
GenieConfig* genie_config = (GenieConfig*)context;
furi_thread_flags_clear(3);
uint32_t flags = 0;
do {
flags = furi_thread_flags_wait(3, FuriFlagWaitAny, FuriWaitForever);
if((flags & 1) == 1) {
if(select_genie_file(genie_config)) {
genie_config_update_file_item(genie_config);
}
}
} while((flags & 2) != 2);
return 0;
}
GenieConfig* genie_config_alloc(GenieApp* app) {
GenieConfig* genie_config = (GenieConfig*)malloc(sizeof(GenieConfig));
genie_config->app = app;
genie_config->variable_item_list = variable_item_list_alloc();
genie_config->tmp = furi_string_alloc();
genie_config->dialogs = furi_record_open(RECORD_DIALOGS);
genie_config->thread = furi_thread_alloc_ex("config", 1024, genie_config_thread, genie_config);
furi_thread_start(genie_config->thread);
VariableItemList* variable_item_list = genie_config->variable_item_list;
variable_item_list_reset(variable_item_list);
variable_item_list_set_enter_callback(
variable_item_list, genie_config_ok_button, genie_config);
view_set_previous_callback(
variable_item_list_get_view(variable_item_list), genie_navigation_submenu_callback);
VariableItem* item = variable_item_list_add(
variable_item_list,
"Frequency",
COUNT_OF(setting_frequency_names),
genie_setting_frequency_change,
app);
uint32_t current_freq = genie_app_get_frequency(app);
uint8_t default_freq_index = 0;
for(size_t i = 0; i < COUNT_OF(setting_frequency_values); i++) {
if(setting_frequency_values[i] == current_freq) {
default_freq_index = i;
break;
}
}
variable_item_set_current_value_index(item, default_freq_index);
variable_item_set_current_value_text(item, setting_frequency_names[default_freq_index]);
genie_app_set_frequency(app, setting_frequency_values[default_freq_index]);
genie_config->file_item =
variable_item_list_add(variable_item_list, "Genie File", 1, NULL, app);
genie_config_update_file_item(genie_config);
return genie_config;
}
void genie_config_free(GenieConfig* genie_config) {
furi_assert(genie_config);
furi_assert(genie_config->variable_item_list);
furi_thread_flags_set(furi_thread_get_id(genie_config->thread), 2);
furi_thread_join(genie_config->thread);
furi_thread_free(genie_config->thread);
variable_item_list_free(genie_config->variable_item_list);
furi_string_free(genie_config->tmp);
genie_config->tmp = NULL;
genie_config->variable_item_list = NULL;
genie_config->app = NULL;
free(genie_config);
furi_record_close(RECORD_DIALOGS);
}
View* genie_config_get_view(GenieConfig* genie_config) {
furi_assert(genie_config);
furi_assert(genie_config->variable_item_list);
return variable_item_list_get_view(genie_config->variable_item_list);
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <gui/modules/variable_item_list.h>
#include <gui/view.h>
typedef struct GenieConfig GenieConfig;
typedef struct GenieApp GenieApp;
GenieConfig* genie_config_alloc(GenieApp* app);
void genie_config_free(GenieConfig* genie_config);
View* genie_config_get_view(GenieConfig* genie_config);
bool select_genie_file(GenieConfig* config);

View File

@ -1,3 +1,5 @@
#include "genie_file.h"
#include <storage/storage.h>
#include <flipper_format.h>
@ -19,6 +21,13 @@
#endif
#define TAG "GenieFile"
struct GenieFile {
uint32_t key_hi;
uint32_t key_lo; // sn
uint16_t last_sent;
uint16_t rec_count;
};
static void ensure_dir_exists(Storage* storage, char* dir) {
if(!storage_dir_exists(storage, dir)) {
FURI_LOG_I(TAG, "Creating directory: %s", dir);
@ -83,7 +92,7 @@ enum {
GENIE_REC_COUNT = 10, // 2 bytes
GENIE_RESERVED = 12, // 4 bytes
GENIE_DATA = 16, // 64K bytes
} genie_file;
} genie_file_layout;
static void genie_create_file(Storage* storage, char* name, uint32_t low) {
File* file = storage_file_alloc(storage);
@ -120,6 +129,181 @@ static uint32_t hex_to_i32(const char* data) {
return value;
}
GenieFile* genie_file_load(const char* path) {
GenieFile* genie_file = malloc(sizeof(GenieFile));
memset(genie_file, 0, sizeof(GenieFile));
bool load_success = false;
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = NULL;
do {
if(!storage) {
FURI_LOG_E(TAG, "Failed to access storage.");
break;
}
file = storage_file_alloc(storage);
if(!file) {
FURI_LOG_E(TAG, "Failed to allocate file.");
break;
}
if(!storage_file_exists(storage, path)) {
FURI_LOG_D(TAG, "File does not exist: %s", path);
break;
}
if(storage_file_open(file, path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
if(!storage_file_seek(file, GENIE_VERSION, true)) {
FURI_LOG_E(TAG, "Failed to seek to GENIE_VERSION @ %d", GENIE_VERSION);
break;
}
uint16_t version = storage_file_read16(file);
if((version >> 8) > GENIE_MAJOR_VERSION) {
FURI_LOG_E(TAG, "Unsupported version: %04X", version);
break;
}
if(!storage_file_seek(file, GENIE_SN, true)) {
FURI_LOG_E(TAG, "Failed to seek to GENIE_SN @ %d", GENIE_SN);
break;
}
genie_file->key_lo = storage_file_read32(file);
if(!storage_file_seek(file, GENIE_LAST_SENT, true)) {
FURI_LOG_E(TAG, "Failed to seek to GENIE_LAST_SENT @ %d", GENIE_LAST_SENT);
break;
}
genie_file->last_sent = storage_file_read16(file);
if(!storage_file_seek(file, GENIE_REC_COUNT, true)) {
FURI_LOG_E(TAG, "Failed to seek to GENIE_REC_COUNT @ %d", GENIE_REC_COUNT);
break;
}
genie_file->rec_count = storage_file_read16(file);
if(!storage_file_seek(file, GENIE_DATA + (genie_file->last_sent * 4), true)) {
FURI_LOG_E(
TAG,
"Failed to seek to GENIE_DATA+last_sent*4 @ %d",
GENIE_DATA + (genie_file->last_sent * 4));
break;
}
genie_file->key_hi = storage_file_read32(file);
load_success = true;
}
} while(false);
if(file) {
storage_file_close(file);
storage_file_free(file);
}
furi_record_close(RECORD_STORAGE);
if(!load_success) {
free(genie_file);
genie_file = NULL;
}
return genie_file;
}
void genie_file_free(GenieFile* file) {
if(file) {
free(file);
}
}
uint32_t genie_file_get_key_hi(GenieFile* file) {
return file ? file->key_hi : 0;
}
uint32_t genie_file_get_key_lo(GenieFile* file) {
return file ? file->key_lo : 0;
}
uint16_t genie_file_get_last_sent(GenieFile* file) {
return file ? file->last_sent : 0;
}
void genie_file_set_last_sent(const char* genie_path, uint16_t last_sent) {
if(genie_path) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = NULL;
do {
if(!storage) {
FURI_LOG_E(TAG, "Failed to access storage.");
break;
}
file = storage_file_alloc(storage);
if(!file) {
FURI_LOG_E(TAG, "Failed to allocate file.");
break;
}
if(!storage_file_exists(storage, genie_path)) {
FURI_LOG_D(TAG, "File not found: %s", genie_path);
break;
}
if(storage_file_open(file, genie_path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
if(!storage_file_seek(file, GENIE_VERSION, true)) {
FURI_LOG_E(TAG, "Failed to seek to GENIE_VERSION @ %d", GENIE_VERSION);
break;
}
uint16_t version = storage_file_read16(file);
if((version >> 8) > GENIE_MAJOR_VERSION) {
FURI_LOG_E(TAG, "Unsupported version: %04X", version);
break;
}
if(!storage_file_seek(file, GENIE_LAST_SENT, true)) {
FURI_LOG_E(TAG, "Failed to seek to GENIE_LAST_SENT @ %d", GENIE_LAST_SENT);
break;
}
if(!storage_file_write16(file, last_sent)) {
FURI_LOG_E(TAG, "Failed to set last sent count to %d.", last_sent);
break;
}
} else {
FURI_LOG_E(TAG, "Failed to open file");
break;
}
} while(false);
if(file) {
storage_file_close(file);
storage_file_free(file);
}
furi_record_close(RECORD_STORAGE);
}
}
uint16_t genie_file_get_rec_count(GenieFile* file) {
return file ? file->rec_count : 0;
}
uint16_t genie_rec_count_bin(uint32_t key_low) {
uint16_t count = 0;
char buffer[128] = {0};
snprintf(buffer, 128, "%s/%08lX.gne", GENIE_SAVE_FOLDER, key_low);
GenieFile* file = genie_file_load(buffer);
if(file) {
count = file->rec_count;
genie_file_free(file);
}
return count;
}
uint32_t genie_save_bin(const char* key) {
uint32_t result = 0;
uint32_t key_high = 0;
@ -288,9 +472,10 @@ bool genie_save(uint32_t count, FuriString* key) {
uint32_t genie_load() {
uint32_t count = 0;
uint32_t key_hi = 0;
uint32_t key_lo = 0;
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* buffer = furi_string_alloc();
File* file = NULL;
do {
if(!storage) {
@ -310,9 +495,18 @@ uint32_t genie_load() {
char data[8 + 1 + 16 + 2 + 1] = {0};
int64_t offset = ((int64_t)storage_file_size(file)) - 120;
if(offset > 0) {
storage_file_seek(file, offset, true);
do {
storage_file_read(file, data, 1);
} while(data[0] != '\n');
}
while(storage_file_read(file, data, 8 + 1 + 16 + 2)) {
sscanf(data, "%08lX", &count);
FURI_LOG_D(TAG, "Read: %s, count: %ld", data, count);
sscanf(data, "%08lX,%08lX%08lX", &count, &key_hi, &key_lo);
FURI_LOG_D(
TAG, "Read: %s, count: %ld, hi: %08lx, lo: %08lx", data, count, key_hi, key_lo);
}
}
} while(false);
@ -324,5 +518,13 @@ uint32_t genie_load() {
furi_string_free(buffer);
furi_record_close(RECORD_STORAGE);
if(key_lo) {
uint16_t count_bin = genie_rec_count_bin(key_lo);
if(count_bin > 0) {
count = count_bin;
}
}
return count;
}

View File

@ -2,6 +2,16 @@
#include <furi.h>
typedef struct GenieFile GenieFile;
GenieFile* genie_file_load(const char* path);
void genie_file_free(GenieFile* file);
uint32_t genie_file_get_key_hi(GenieFile* file);
uint32_t genie_file_get_key_lo(GenieFile* file);
uint16_t genie_file_get_last_sent(GenieFile* file);
uint16_t genie_file_get_rec_count(GenieFile* file);
void genie_file_set_last_sent(const char* genie_path, uint16_t last_sent);
bool genie_save(uint32_t count, FuriString* key);
uint32_t genie_save_bin(const char* key);
uint32_t genie_load();

View File

@ -0,0 +1,79 @@
#include "genie_ini.h"
#include "genie_app.h"
#include <furi.h>
#include <flipper_format.h>
#define GENIE_SETTINGS_FILE EXT_PATH("apps_data") "/genie/genie.txt"
#define GENIE_SETTINGS_NAME "Genie settings file"
#define GENIE_SETTINGS_VERSION 1
#define TAG "GenieIni"
FlipperFormat* ff;
void genie_ini_load(GenieApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* buf = furi_string_alloc();
ff = flipper_format_buffered_file_alloc(storage);
do {
uint32_t format_version;
if(!flipper_format_buffered_file_open_existing(ff, GENIE_SETTINGS_FILE)) {
FURI_LOG_E(TAG, "Failed to open settings file: %s", GENIE_SETTINGS_FILE);
break;
}
if(!flipper_format_read_header(ff, buf, &format_version)) {
FURI_LOG_E(TAG, "Failed to read settings header.");
break;
}
if(furi_string_cmp_str(buf, GENIE_SETTINGS_NAME) ||
format_version != GENIE_SETTINGS_VERSION) {
FURI_LOG_E(
TAG, "Unsupported file. `%s` v`%ld`", furi_string_get_cstr(buf), format_version);
break;
}
uint32_t frequency;
flipper_format_read_uint32(ff, "frequency", &frequency, 1);
genie_app_set_frequency(app, frequency);
FuriString* buf = furi_string_alloc();
if(flipper_format_read_string(ff, "path", buf)) {
genie_app_update_file_path(app, furi_string_get_cstr(buf));
}
} while(false);
flipper_format_buffered_file_close(ff);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
furi_string_free(buf);
}
void genie_ini_save(GenieApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* buf = furi_string_alloc();
ff = flipper_format_buffered_file_alloc(storage);
do {
if(!flipper_format_buffered_file_open_always(ff, GENIE_SETTINGS_FILE)) {
FURI_LOG_E(TAG, "Failed to open settings file: %s", GENIE_SETTINGS_FILE);
break;
}
if(!flipper_format_write_header_cstr(ff, GENIE_SETTINGS_NAME, GENIE_SETTINGS_VERSION)) {
FURI_LOG_E(TAG, "Failed to write settings header.");
break;
}
uint32_t frequency = genie_app_get_frequency(app);
flipper_format_write_uint32(ff, "frequency", &frequency, 1);
const char* path = genie_app_get_file_path(app);
flipper_format_write_string_cstr(ff, "path", path);
} while(false);
flipper_format_buffered_file_close(ff);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
furi_string_free(buf);
}

View File

@ -0,0 +1,6 @@
#pragma once
typedef struct GenieApp GenieApp;
void genie_ini_load(GenieApp* app);
void genie_ini_save(GenieApp* app);

View File

@ -0,0 +1,129 @@
#include "genie_learn.h"
#include "genie_app.h"
#include "genie_submenu.h"
#include "genie_subghz_receive.h"
#include "genie_file.h"
#ifdef TAG
#undef TAG
#endif
#define TAG "GenieLearn"
typedef struct GenieApp GenieApp;
typedef struct {
GenieApp* ref;
} GenieAppRef;
#define CLICK_SPEED 2000
static void genie_learn_draw_callback(Canvas* canvas, void* model) {
GenieApp* app = ((GenieAppRef*)model)->ref;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 5, 10, "Genie Sub-Ghz Recorder!!!");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 5, 20, "A7/GND to Genie remote");
char buffer[30] = {0};
snprintf(buffer, COUNT_OF(buffer), "Click %ld", genie_app_get_click_counter(app));
canvas_draw_str(canvas, 1, 40, buffer);
uint32_t count = genie_app_get_save_counter(app);
if(count == 0) {
snprintf(buffer, COUNT_OF(buffer), "Prev %ld", genie_app_get_rx_counter(app));
} else {
snprintf(buffer, COUNT_OF(buffer), "Got %ld", count);
}
canvas_draw_str(canvas, 75, 40, buffer);
if(count < 0x10000) {
uint32_t remaining;
if(count == 0) {
remaining = 65536 - genie_app_get_rx_counter(app);
} else {
remaining = 65536 - count;
}
snprintf(buffer, COUNT_OF(buffer), "Remaining codes %ld", remaining);
canvas_draw_str(canvas, 1, 30, buffer);
} else {
canvas_draw_str(canvas, 1, 30, "Found all codes!");
}
canvas_draw_str(canvas, 5, 50, furi_string_get_cstr(genie_app_get_key(app)));
if(genie_app_has_no_signal(app)) {
canvas_draw_str(canvas, 1, 60, "NO SIGNAL FROM REMOTE?");
} else if(genie_app_is_sending_signal(app)) {
canvas_draw_str(canvas, 100, 60, "SEND");
}
}
static bool genie_learn_input_callback(InputEvent* event, void* context) {
UNUSED(context);
UNUSED(event);
return false;
}
static uint32_t save_count(uint32_t count, FuriString* key, bool is_genie) {
FURI_LOG_D(TAG, "%ld,%s", count, furi_string_get_cstr(key));
genie_save(count, key);
if(is_genie) {
return genie_save_bin(furi_string_get_cstr(key));
}
return 0;
}
static void genie_packet(FuriString* buffer, void* context) {
GenieApp* app = (GenieApp*)context;
genie_app_set_processing_packet(app, true);
if(furi_string_search_str(buffer, "Genie 64bit") < furi_string_size(buffer)) {
release_button(app);
FURI_LOG_D(TAG, "Genie 64bit packet");
size_t key_index = furi_string_search_str(buffer, "Key:");
if(key_index < furi_string_size(buffer)) {
genie_app_received_key(app, buffer);
uint32_t click_counter = genie_app_get_click_counter(app);
FuriString* key = genie_app_get_key(app);
uint32_t num_saved = save_count(click_counter, key, true);
genie_app_update_save_counter(app, num_saved);
}
}
genie_app_set_processing_packet(app, false);
}
static void genie_learn_enter_callback(void* context) {
GenieApp* app = (GenieApp*)context;
genie_file_init();
uint32_t frequency = genie_app_get_frequency(app);
GenieSubGhz* subghz = genie_app_get_subghz(app);
start_listening(subghz, frequency, genie_packet, context);
genie_app_start_timer(app, CLICK_SPEED);
}
static void genie_learn_exit_callback(void* context) {
GenieApp* app = (GenieApp*)context;
genie_app_set_processing_packet(app, false);
release_button(app);
GenieSubGhz* subghz = genie_app_get_subghz(app);
stop_listening(subghz);
genie_app_stop_timer(app);
}
View* genie_learn_alloc(void* app) {
View* view = view_alloc();
view_set_draw_callback(view, genie_learn_draw_callback);
view_set_input_callback(view, genie_learn_input_callback);
view_set_previous_callback(view, genie_navigation_submenu_callback);
view_set_context(view, app);
view_set_enter_callback(view, genie_learn_enter_callback);
view_set_exit_callback(view, genie_learn_exit_callback);
view_allocate_model(view, ViewModelTypeLockFree, sizeof(GenieAppRef));
GenieAppRef* r = (GenieAppRef*)view_get_model(view);
r->ref = app;
return view;
}
void genie_learn_free(View* view) {
view_free(view);
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view.h>
View* genie_learn_alloc(void* app);
void genie_learn_free(View* view);

View File

@ -0,0 +1,259 @@
#include "genie_send.h"
#include "genie_app.h"
#include "genie_submenu.h"
#include "genie_subghz_receive.h"
#include "genie_file.h"
#include <flipper_format/flipper_format_i.h>
#include <lib/subghz/transmitter.h>
#ifdef TAG
#undef TAG
#endif
#define TAG "GenieSend"
typedef struct GenieSend GenieSend;
struct GenieSend {
View* view;
};
typedef struct {
GenieApp* app;
GenieFile* file;
bool loaded;
} GenieRefs;
static void
set_genie(FlipperFormat* flipper_format, uint32_t key_hi, uint32_t key_lo, uint32_t repeat) {
FuriString* genie_settings = furi_string_alloc_printf(
"Protocol: Genie\n"
"Bit: 64\n"
"Key: %02X %02X %02X %02X %02X %02X %02X %02X\n"
"Increment: 0\n"
"Repeat: %lu\n",
(uint8_t)((key_hi >> 24) & 0xFFU),
(uint8_t)((key_hi >> 16) & 0xFFU),
(uint8_t)((key_hi >> 8) & 0xFFU),
(uint8_t)((key_hi) & 0xFFU),
(uint8_t)((key_lo >> 24) & 0xFFU),
(uint8_t)((key_lo >> 16) & 0xFFU),
(uint8_t)((key_lo >> 8) & 0xFFU),
(uint8_t)((key_lo) & 0xFFU),
repeat);
Stream* stream = flipper_format_get_raw_stream(flipper_format);
stream_clean(stream);
stream_write_cstring(stream, furi_string_get_cstr(genie_settings));
stream_seek(stream, 0, StreamOffsetFromStart);
furi_string_free(genie_settings);
}
const SubGhzProtocol* send_subghz_protocol_registry_items[] = {
&subghz_protocol_genie,
};
const SubGhzProtocolRegistry send_subghz_protocol_registry = {
.items = send_subghz_protocol_registry_items,
.size = COUNT_OF(send_subghz_protocol_registry_items)};
static void send_genie(uint32_t key_hi, uint32_t key_lo, uint32_t frequency) {
uint32_t repeat = 5;
const SubGhzDevice* device;
subghz_devices_init();
device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
SubGhzEnvironment* environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry(environment, (void*)&send_subghz_protocol_registry);
SubGhzTransmitter* transmitter = subghz_transmitter_alloc_init(environment, "Genie");
FlipperFormat* flipper_format = flipper_format_string_alloc();
set_genie(flipper_format, key_hi, key_lo, repeat);
subghz_transmitter_deserialize(transmitter, flipper_format);
subghz_devices_begin(device);
subghz_devices_reset(device);
subghz_devices_load_preset(device, FuriHalSubGhzPresetOok650Async, NULL);
frequency = subghz_devices_set_frequency(device, frequency);
// Send
furi_hal_power_suppress_charge_enter();
if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) {
while(!(subghz_devices_is_async_complete_tx(device))) {
furi_delay_ms(100);
}
subghz_devices_stop_async_tx(device);
}
// Cleanup
subghz_devices_sleep(device);
subghz_devices_end(device);
subghz_devices_deinit();
furi_hal_power_suppress_charge_exit();
flipper_format_free(flipper_format);
subghz_transmitter_free(transmitter);
subghz_environment_free(environment);
}
static void genie_send_draw_callback(Canvas* canvas, void* model) {
GenieRefs* refs = (GenieRefs*)model;
GenieFile* file = refs->file;
char buffer[36] = {0};
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 5, 10, "Genie Sub-Ghz Sender!!!");
canvas_set_font(canvas, FontSecondary);
if(refs->loaded) {
if(!file) {
return;
}
snprintf(
buffer,
COUNT_OF(buffer),
"KEY: %08lx %08lx",
genie_file_get_key_hi(file),
genie_file_get_key_lo(file));
canvas_draw_str(canvas, 1, 23, buffer);
if(genie_file_get_rec_count(file) < 0xFFFD) {
snprintf(
buffer,
COUNT_OF(buffer),
"Last sent: %d of %d",
genie_file_get_last_sent(file),
genie_file_get_rec_count(file));
canvas_draw_str(canvas, 1, 33, buffer);
snprintf(buffer, COUNT_OF(buffer), "Remote not fully captured!");
canvas_draw_str(canvas, 1, 49, buffer);
} else {
snprintf(buffer, COUNT_OF(buffer), "Last sent: %d", genie_file_get_last_sent(file));
canvas_draw_str(canvas, 1, 33, buffer);
}
// TODO: Is this off by 1?
if(genie_file_get_last_sent(file) < genie_file_get_rec_count(file)) {
canvas_draw_str(canvas, 1, 63, "Press OK to send next code.");
} else {
canvas_draw_str(canvas, 1, 61, "Long press OK to reset code.");
}
} else {
canvas_draw_str(canvas, 20, 36, "NO .GNE FILE LOADED");
}
}
static void genie_send_refresh_file(View* view) {
with_view_model(
view,
GenieRefs * refs,
{
const char* path = genie_app_get_file_path(refs->app);
if(refs->file) {
genie_file_free(refs->file);
refs->file = NULL;
}
refs->file = genie_file_load(path);
},
true);
}
static bool genie_send_input_callback(InputEvent* event, void* context) {
View* view = (View*)context;
genie_send_refresh_file(view);
if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
with_view_model(
view,
GenieRefs * refs,
{
if(refs->file) {
if(genie_file_get_last_sent(refs->file) <
genie_file_get_rec_count(refs->file)) {
send_genie(
genie_file_get_key_hi(refs->file),
genie_file_get_key_lo(refs->file),
genie_app_get_frequency(refs->app));
genie_file_set_last_sent(
genie_app_get_file_path(refs->app),
genie_file_get_last_sent(refs->file) + 1);
genie_send_refresh_file(view);
}
}
},
false);
return true;
}
} else if(event->type == InputTypeLong) {
with_view_model(
view,
GenieRefs * refs,
{
if(refs->file) {
if(genie_file_get_last_sent(refs->file) >=
genie_file_get_rec_count(refs->file)) {
genie_file_set_last_sent(genie_app_get_file_path(refs->app), 0);
genie_send_refresh_file(view);
}
}
},
true);
return true;
}
return false;
}
static void genie_send_enter_callback(void* context) {
View* view = (View*)context;
with_view_model(
view,
GenieRefs * refs,
{
const char* path = genie_app_get_file_path(refs->app);
refs->file = genie_file_load(path);
refs->loaded = refs->file != NULL;
},
false);
}
GenieSend* genie_send_alloc(GenieApp* app) {
UNUSED(app);
GenieSend* genie_send = malloc(sizeof(GenieSend));
View* view = view_alloc();
genie_send->view = view;
view_set_draw_callback(view, genie_send_draw_callback);
view_set_input_callback(view, genie_send_input_callback);
view_set_previous_callback(view, genie_navigation_submenu_callback);
view_set_enter_callback(view, genie_send_enter_callback);
view_set_context(view, view);
view_allocate_model(view, ViewModelTypeLockFree, sizeof(GenieRefs));
GenieRefs* refs = (GenieRefs*)view_get_model(view);
refs->app = app;
refs->file = NULL;
refs->loaded = false;
return genie_send;
}
void genie_send_free(GenieSend* genie_send) {
furi_assert(genie_send);
furi_assert(genie_send->view);
with_view_model(
genie_send->view,
GenieRefs * refs,
{
genie_file_free(refs->file);
refs->file = NULL;
},
false);
view_free(genie_send->view);
genie_send->view = NULL;
free(genie_send);
}
View* genie_send_get_view(GenieSend* genie_send) {
furi_assert(genie_send);
furi_assert(genie_send->view);
return genie_send->view;
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <gui/view.h>
typedef struct GenieApp GenieApp;
typedef struct GenieSend GenieSend;
GenieSend* genie_send_alloc(GenieApp* app);
void genie_send_free(GenieSend* genie_send);
View* genie_send_get_view(GenieSend* genie_send);

View File

@ -5,17 +5,17 @@
#endif
#define TAG "GenieSubGHzReceive"
const SubGhzProtocol* subghz_protocol_registry_item_genie[] = {
&subghz_protocol_genie,
};
const SubGhzProtocolRegistry subghz_protocol_registry_genie = {
.items = subghz_protocol_registry_item_genie,
.size = COUNT_OF(subghz_protocol_registry_item_genie)};
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);
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry_genie);
return environment;
}

View File

@ -4,9 +4,9 @@
#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>
#include "protocols/genie.h"
typedef void (*SubghzPacketCallback)(FuriString* buffer, void* context);
@ -18,7 +18,9 @@ typedef enum {
SUBGHZ_RECEIVER_UNINITIALIZED,
} SubghzReceiverState;
typedef struct {
typedef struct GenieSubGhz GenieSubGhz;
struct GenieSubGhz {
SubGhzEnvironment* environment;
FuriStreamBuffer* stream;
FuriThread* thread;
@ -27,7 +29,7 @@ typedef struct {
SubghzReceiverState status;
SubghzPacketCallback callback;
void* callback_context;
} GenieSubGhz;
};
GenieSubGhz* genie_subghz_alloc();
void genie_subghz_free(GenieSubGhz* subghz);

View File

@ -0,0 +1,97 @@
#include "genie_submenu.h"
#include <gui/view_dispatcher.h>
#include "genie_app.h"
#include "genie_config.h"
typedef struct GenieApp GenieApp;
struct GenieSubmenu {
Submenu* submenu;
};
typedef enum {
GenieSubmenuIndexSend,
GenieSubmenuIndexConfig,
GenieSubmenuIndexLearn,
GenieSubmenuIndexAbout,
} GenieSubmenuIndex;
typedef enum {
GenieViewSubmenu,
GenieViewSend,
GenieViewConfig,
GenieViewLearn,
GenieViewAbout,
} GenieView;
/**
* @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;
}
/**
* @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
*/
static uint32_t genie_navigation_exit_callback(void* context) {
UNUSED(context);
return VIEW_NONE;
}
static void genie_submenu_callback(void* context, uint32_t index) {
GenieApp* app = (GenieApp*)context;
ViewDispatcher* view_dispatcher = genie_app_get_view_dispatcher(app);
switch(index) {
case GenieSubmenuIndexSend:
view_dispatcher_switch_to_view(view_dispatcher, GenieViewSend);
break;
case GenieSubmenuIndexConfig: {
view_dispatcher_switch_to_view(view_dispatcher, GenieViewConfig);
break;
}
case GenieSubmenuIndexLearn:
view_dispatcher_switch_to_view(view_dispatcher, GenieViewLearn);
break;
case GenieSubmenuIndexAbout:
view_dispatcher_switch_to_view(view_dispatcher, GenieViewAbout);
break;
default:
break;
}
}
GenieSubmenu* genie_submenu_alloc(GenieApp* app) {
GenieSubmenu* genie_submenu = malloc(sizeof(GenieSubmenu));
Submenu* submenu = submenu_alloc();
genie_submenu->submenu = submenu;
submenu_add_item(submenu, "Send", GenieSubmenuIndexSend, genie_submenu_callback, app);
submenu_add_item(submenu, "Config", GenieSubmenuIndexConfig, genie_submenu_callback, app);
submenu_add_item(submenu, "Learn", GenieSubmenuIndexLearn, genie_submenu_callback, app);
submenu_add_item(submenu, "About", GenieSubmenuIndexAbout, genie_submenu_callback, app);
view_set_previous_callback(submenu_get_view(submenu), genie_navigation_exit_callback);
return genie_submenu;
}
View* genie_submenu_get_view(GenieSubmenu* genie_submenu) {
furi_assert(genie_submenu);
furi_assert(genie_submenu->submenu);
return submenu_get_view(genie_submenu->submenu);
}
void genie_submenu_free(GenieSubmenu* genie_submenu) {
submenu_free(genie_submenu->submenu);
genie_submenu->submenu = NULL;
free(genie_submenu);
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <furi.h>
#include <gui/modules/submenu.h>
#include <gui/view.h>
typedef struct GenieSubmenu GenieSubmenu;
typedef struct GenieApp GenieApp;
uint32_t genie_navigation_submenu_callback(void* context);
GenieSubmenu* genie_submenu_alloc(GenieApp* app);
View* genie_submenu_get_view(GenieSubmenu* submenu);
void genie_submenu_free(GenieSubmenu* submenu);

View File

@ -0,0 +1,151 @@
#include "generic.h"
#include <lib/toolbox/stream/stream.h>
#include <lib/flipper_format/flipper_format_i.h>
#define TAG "SubGhzBlockGeneric"
void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) {
const char* preset_name_temp;
if(!strcmp(preset_name, "AM270")) {
preset_name_temp = "FuriHalSubGhzPresetOok270Async";
} else if(!strcmp(preset_name, "AM650")) {
preset_name_temp = "FuriHalSubGhzPresetOok650Async";
} else if(!strcmp(preset_name, "FM238")) {
preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async";
} else if(!strcmp(preset_name, "FM476")) {
preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async";
} else {
preset_name_temp = "FuriHalSubGhzPresetCustom";
}
furi_string_set(preset_str, preset_name_temp);
}
SubGhzProtocolStatus subghz_block_generic_serialize(
SubGhzBlockGeneric* instance,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(instance);
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
FuriString* temp_str;
temp_str = furi_string_alloc();
do {
stream_clean(flipper_format_get_raw_stream(flipper_format));
if(!flipper_format_write_header_cstr(
flipper_format, SUBGHZ_KEY_FILE_TYPE, SUBGHZ_KEY_FILE_VERSION)) {
FURI_LOG_E(TAG, "Unable to add header");
res = SubGhzProtocolStatusErrorParserHeader;
break;
}
if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) {
FURI_LOG_E(TAG, "Unable to add Frequency");
res = SubGhzProtocolStatusErrorParserFrequency;
break;
}
subghz_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str);
if(!flipper_format_write_string_cstr(
flipper_format, "Preset", furi_string_get_cstr(temp_str))) {
FURI_LOG_E(TAG, "Unable to add Preset");
res = SubGhzProtocolStatusErrorParserPreset;
break;
}
if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) {
if(!flipper_format_write_string_cstr(
flipper_format, "Custom_preset_module", "CC1101")) {
FURI_LOG_E(TAG, "Unable to add Custom_preset_module");
res = SubGhzProtocolStatusErrorParserCustomPreset;
break;
}
if(!flipper_format_write_hex(
flipper_format, "Custom_preset_data", preset->data, preset->data_size)) {
FURI_LOG_E(TAG, "Unable to add Custom_preset_data");
res = SubGhzProtocolStatusErrorParserCustomPreset;
break;
}
}
if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) {
FURI_LOG_E(TAG, "Unable to add Protocol");
res = SubGhzProtocolStatusErrorParserProtocolName;
break;
}
uint32_t temp = instance->data_count_bit;
if(!flipper_format_write_uint32(flipper_format, "Bit", &temp, 1)) {
FURI_LOG_E(TAG, "Unable to add Bit");
res = SubGhzProtocolStatusErrorParserBitCount;
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->data >> (i * 8)) & 0xFF;
}
if(!flipper_format_write_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
res = SubGhzProtocolStatusErrorParserKey;
break;
}
res = SubGhzProtocolStatusOk;
} while(false);
furi_string_free(temp_str);
return res;
}
SubGhzProtocolStatus
subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format) {
furi_assert(instance);
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
FuriString* temp_str;
temp_str = furi_string_alloc();
uint32_t temp_data = 0;
do {
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
res = SubGhzProtocolStatusErrorParserOthers;
break;
}
if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) {
FURI_LOG_E(TAG, "Missing Bit");
res = SubGhzProtocolStatusErrorParserBitCount;
break;
}
instance->data_count_bit = (uint16_t)temp_data;
uint8_t key_data[sizeof(uint64_t)] = {0};
if(!flipper_format_read_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Missing Key");
res = SubGhzProtocolStatusErrorParserKey;
break;
}
for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
instance->data = instance->data << 8 | key_data[i];
}
res = SubGhzProtocolStatusOk;
} while(0);
furi_string_free(temp_str);
return res;
}
SubGhzProtocolStatus subghz_block_generic_deserialize_check_count_bit(
SubGhzBlockGeneric* instance,
FlipperFormat* flipper_format,
uint16_t count_bit) {
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(instance, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->data_count_bit != count_bit) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
} while(false);
return ret;
}

View File

@ -0,0 +1,69 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <lib/flipper_format/flipper_format.h>
#include <furi.h>
#include <furi_hal.h>
#include <lib/subghz/types.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct SubGhzBlockGeneric SubGhzBlockGeneric;
struct SubGhzBlockGeneric {
const char* protocol_name;
uint64_t data;
uint32_t serial;
uint16_t data_count_bit;
uint8_t btn;
uint32_t cnt;
};
/**
* Get name preset.
* @param preset_name name preset
* @param preset_str Output name preset
*/
void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str);
/**
* Serialize data SubGhzBlockGeneric.
* @param instance Pointer to a SubGhzBlockGeneric instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return Status Error
*/
SubGhzProtocolStatus subghz_block_generic_serialize(
SubGhzBlockGeneric* instance,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzBlockGeneric.
* @param instance Pointer to a SubGhzBlockGeneric instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return Status Error
*/
SubGhzProtocolStatus
subghz_block_generic_deserialize(SubGhzBlockGeneric* instance, FlipperFormat* flipper_format);
/**
* Deserialize data SubGhzBlockGeneric.
* @param instance Pointer to a SubGhzBlockGeneric instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param count_bit Count bit protocol
* @return Status Error
*/
SubGhzProtocolStatus subghz_block_generic_deserialize_check_count_bit(
SubGhzBlockGeneric* instance,
FlipperFormat* flipper_format,
uint16_t count_bit);
#ifdef __cplusplus
}
#endif

View File

@ -15,12 +15,6 @@
#include "genie.h"
#include "keeloq_common.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#define TAG "SubGhzProtocolGenie"
// Should be major version of supported Genie Recorder (.gne) files
@ -379,12 +373,14 @@ static bool subghz_protocol_genie_gen_data(
* @param instance Pointer to a SubGhzProtocolEncoderGenie instance
* @return true On success
*/
static bool
subghz_protocol_encoder_genie_get_upload(SubGhzProtocolEncoderGenie* instance, uint8_t btn) {
static bool subghz_protocol_encoder_genie_get_upload(
SubGhzProtocolEncoderGenie* instance,
uint8_t btn,
bool counter_up) {
furi_assert(instance);
// Generate next key
if(!subghz_protocol_genie_gen_data(instance, btn, true)) {
if(!subghz_protocol_genie_gen_data(instance, btn, counter_up)) {
return false;
}
@ -465,12 +461,21 @@ SubGhzProtocolStatus
subghz_protocol_genie_set_sn_and_btn(&instance->generic);
uint32_t increment = 1;
flipper_format_read_uint32(flipper_format, "Increment", (uint32_t*)&increment, 1);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
//optional parameter parameter
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
// Get_upload will generate the next key.
if(!subghz_protocol_encoder_genie_get_upload(instance, instance->generic.btn)) {
if(!subghz_protocol_encoder_genie_get_upload(
instance, instance->generic.btn, increment != 0)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
@ -582,10 +587,14 @@ void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t dura
subghz_protocol_genie_const.te_delta)) {
// Found end TX
instance->decoder.parser_step = GenieDecoderStepReset;
FURI_LOG_D(TAG, "Found end TX. Count bit: %d", instance->decoder.decode_count_bit);
// We expect 64-bits of data, but some Intellisense compatible remote send
// 66-bits or 69-bits of data (+5 extra bits).
if((instance->decoder.decode_count_bit >=
subghz_protocol_genie_const.min_count_bit_for_found) &&
(instance->decoder.decode_count_bit <=
subghz_protocol_genie_const.min_count_bit_for_found + 2)) {
subghz_protocol_genie_const.min_count_bit_for_found + 5)) {
if(instance->generic.data != instance->decoder.decode_data) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit =

View File

@ -1,6 +1,12 @@
#pragma once
#include "base.h"
#include <lib/subghz/types.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/math.h>
#include "generic.h"
#define SUBGHZ_PROTOCOL_GENIE_NAME "Genie"

View File

@ -0,0 +1,126 @@
#include "keeloq_common.h"
#include <furi.h>
#include <m-array.h>
#define bit(x, n) (((x) >> (n)) & 1)
#define g5(x, a, b, c, d, e) \
(bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16)
/** Simple Learning Encrypt
* @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter
* @param key - manufacture (64bit)
* @return keeloq encrypt data
*/
inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) {
uint32_t x = data, r;
for(r = 0; r < 528; r++)
x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^
bit(KEELOQ_NLF, g5(x, 1, 9, 20, 26, 31)))
<< 31);
return x;
}
/** Simple Learning Decrypt
* @param data - keeloq encrypt data
* @param key - manufacture (64bit)
* @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter
*/
inline uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) {
uint32_t x = data, r;
for(r = 0; r < 528; r++)
x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^
bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30));
return x;
}
/** Normal Learning
* @param data - serial number (28bit)
* @param key - manufacture (64bit)
* @return manufacture for this serial number (64bit)
*/
inline uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) {
uint32_t k1, k2;
data &= 0x0FFFFFFF;
data |= 0x20000000;
k1 = subghz_protocol_keeloq_common_decrypt(data, key);
data &= 0x0FFFFFFF;
data |= 0x60000000;
k2 = subghz_protocol_keeloq_common_decrypt(data, key);
return ((uint64_t)k2 << 32) | k1; // key - shifrovanoya
}
/** Secure Learning
* @param data - serial number (28bit)
* @param seed - seed number (32bit)
* @param key - manufacture (64bit)
* @return manufacture for this serial number (64bit)
*/
inline uint64_t subghz_protocol_keeloq_common_secure_learning(
uint32_t data,
uint32_t seed,
const uint64_t key) {
uint32_t k1, k2;
data &= 0x0FFFFFFF;
k1 = subghz_protocol_keeloq_common_decrypt(data, key);
k2 = subghz_protocol_keeloq_common_decrypt(seed, key);
return ((uint64_t)k1 << 32) | k2;
}
/** Magic_xor_type1 Learning
* @param data - serial number (28bit)
* @param xor - magic xor (64bit)
* @return manufacture for this serial number (64bit)
*/
inline uint64_t
subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) {
data &= 0x0FFFFFFF;
return (((uint64_t)data << 32) | data) ^ xor;
}
/** Magic_serial_type1 Learning
* @param data - serial number (28bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
inline uint64_t
subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) {
return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) |
((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32);
}
/** Magic_serial_type2 Learning
* @param data - btn+serial number (32bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
inline uint64_t
subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) {
uint8_t* p = (uint8_t*)&data;
uint8_t* m = (uint8_t*)&man;
m[7] = p[0];
m[6] = p[1];
m[5] = p[2];
m[4] = p[3];
return man;
}
/** Magic_serial_type3 Learning
* @param data - serial number (24bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
inline uint64_t
subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) {
return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF);
}

View File

@ -0,0 +1,91 @@
#pragma once
#include <lib/subghz/types.h>
#include <furi.h>
/*
* Keeloq
* https://ru.wikipedia.org/wiki/KeeLoq
* https://phreakerclub.com/forum/showthread.php?t=1094
*
*/
#define KEELOQ_NLF 0x3A5C742E
/*
* KeeLoq learning types
* https://phreakerclub.com/forum/showthread.php?t=67
*/
#define KEELOQ_LEARNING_UNKNOWN 0u
#define KEELOQ_LEARNING_SIMPLE 1u
#define KEELOQ_LEARNING_NORMAL 2u
#define KEELOQ_LEARNING_SECURE 3u
#define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u
#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 5u
#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2 6u
#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3 7u
/**
* Simple Learning Encrypt
* @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter
* @param key - manufacture (64bit)
* @return keeloq encrypt data
*/
uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key);
/**
* Simple Learning Decrypt
* @param data - keeloq encrypt data
* @param key - manufacture (64bit)
* @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter
*/
uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key);
/**
* Normal Learning
* @param data - serial number (28bit)
* @param key - manufacture (64bit)
* @return manufacture for this serial number (64bit)
*/
uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key);
/**
* Secure Learning
* @param data - serial number (28bit)
* @param seed - seed number (32bit)
* @param key - manufacture (64bit)
* @return manufacture for this serial number (64bit)
*/
uint64_t
subghz_protocol_keeloq_common_secure_learning(uint32_t data, uint32_t seed, const uint64_t key);
/**
* Magic_xor_type1 Learning
* @param data - serial number (28bit)
* @param xor - magic xor (64bit)
* @return manufacture for this serial number (64bit)
*/
uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor);
/** Magic_serial_type1 Learning
* @param data - serial number (28bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man);
/** Magic_serial_type2 Learning
* @param data - btn+serial number (32bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man);
/** Magic_serial_type3 Learning
* @param data - btn+serial number (32bit)
* @param man - magic man (64bit)
* @return manufacture for this serial number (64bit)
*/
uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB