Genie v2 - supports custom protocol handler

This commit is contained in:
Derek Jamison 2023-10-18 19:39:57 -05:00
parent 0f7ab59050
commit 88992639c6
9 changed files with 1271 additions and 109 deletions

View File

@ -1,51 +1,89 @@
# Genie garage door recorder
Watch this [YouTube video](https://youtu.be/C-TnlVM4Ahs) for a demo of installing and running this application.
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.
* You MUST need to edit ``lib\subghz\protocols\keeloq.c`` to change the timing values; or else the application will not work!
* NOTE: You no longer have to edit ``api_symbols.csv`` file.
Document sections:
- [Description](#description)
- [Installing](#installing)
- [Determine Genie Frequency](#determine-genie-frequency)
- [Connecting to remote](#connecting-to-remote)
- [Capture codes](#capture-codes)
- [Sub-GHz Read](#sub-ghz-read)
- [Sub-GHz Saved](#sub-ghz-saved)
- [Troubleshooting](#troubleshooting)
## 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?
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.
Once completed, the built-in Sub-GHz Read application will know how to playback your Genie garage door remote.
## 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)".
## 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.
## Connecting to remote
<img src="wiring.png">
You only need to connect two wires between the Flipper and the remote. One wire is GND on the Flipper. It should connect to the pin that is the same as the battery negative pad on the remote. The second wire is the signal wire, it should connect to A7 on the Flipper Zero. On the remote side, it should connect to a pin that transmits the signal. If you remove the wires from the Flipper Zero and touch the two wires together, the remote should send a signal.
<p/><p/>
In my testing, a new CR2032 battery was able to send all 65,536 codes & still had power left over! IF YOU WANT TO USE THE FLIPPER 3V3 FOR A POWER SOURCE, THEN YOU MUST MAKE SURE THAT GND ON THE FLIPPER IS GOING TO GND ON THE REMOTE (and not the signal pin). IN YOU ARE 100% SURE, THEN YOU CAN REMOVE THE BATTERY FROM YOUR REMOTE AND CONNECT 3V3 PIN ON THE FLIPPER TO THE BATTERY POSITIVE BAR ON THE REMOTE.
<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 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.
<p/><p/>
WARNING -- Don't run this near your garage. There is no reason to open the physical garage door & you will likely burn out the motor.
<img src="wiring.png"><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.
- Step 1. Open the case off of your garage door remote.
- Step 2. Connect GND from Flipper to GND pin on the remote.
- Step 3. Connect A7 from Flipper to the signal pin on the 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.
## Running
- Step 1. 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 2. Build your firmware (``./fbt vscode_dist`` & ``./fbt`` -- "[Debug] Build Firmware"). You may get a build error.
- Step 3. Edit ``lib\subghz\protocols\keeloq.c`` so it have te_short=200, te_long=400, te_delta=70. NOTE: This will no longer be able to receive signals from other KeeLoq devices.
- Step 4. Build your firmware & deploy onto Flipper ``./fbt FORCE=1 flash_usb_full`` -- "[Debug]Flash (USB, with Resources)".
- Step 5. On your Flipper Zero, load ``Sub-GHz`` app.
- Step 6. Choose ``Read`` to start scanning.
- Step 7. Press the LEFT button to edit the Config.
- Step 8. Change the ``Frequency`` to 315000000.
- Step 9. Press the BACK button to continue scanning on the new frequency.
- Step 10. Press a button on your remote to see if it is detected.
- Step 11. If it is NOT detected, try 390000000.
- Step 12. Open the case off of your garage door remote.
- Step 13. Connect the remote to pins A7 and GND.
- Step 14. Edit FREQUENCY if 315MHz is not correct (in genie.c).
- Step 15. Run the Genie Recorder application
- Step 16. Choose "Start" to start recording signals.
## 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 the most recent Keeloq code received
- Step 17. Every time a signal is recevied, it is written to the log file. If the application exits and restarts, it will resume the counters where it left off.
- Step 18. Let it run for three days (the goal is to capture at least 65,536+ signals)
- Step 19. Press the BACK button twice to exit the application.
- Step 20. Copy the file "\apps_data\genie\keys.txt" from the SD card to your computer.
- Step 21. Edit ``lib\subghz\protocols\keeloq.c`` so it has original values of te_short=400, te_long=800, te_delta=140.
- Step 22. Build your firmware & deploy onto Flipper.
- 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.
Now that you have the keys.txt file, you can use the [Genie.py script](https://github.com/jamisonderek/flipper-zero-tutorials/tree/main/subghz/samples/genie-girud-1t/README.md) to generate a genie.sub file that will transmit the key.
## 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.
## 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.
## Troubleshooting

View File

@ -1,13 +1,13 @@
App(
appid="genie_record",
name="Genie Door Recorder",
appid="genie_record_v2",
name="Genie Door Recorder v2",
apptype=FlipperAppType.EXTERNAL,
entry_point="genie_record_app",
requires=["gui", "subghz"],
stack_size=2 * 1024,
fap_version=(1, 3),
stack_size=4 * 1024,
fap_version=(2, 4),
fap_icon="genie.png",
fap_category="Sub-GHz",
fap_icon_assets="assets",
fap_description="Genie Door Signal w/Recorder",
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.",
)

View File

@ -4,25 +4,27 @@
#include <gui/view.h>
#include <gui/view_dispatcher.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_about.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 {
GenieSubmenuIndexConfig,
GenieSubmenuIndexStart,
GenieSubmenuIndexAbout,
} GenieSubmenuIndex;
typedef enum {
GenieViewSubmenu,
GenieViewConfig,
GenieViewStart,
GenieViewAbout,
} GenieView;
@ -32,12 +34,15 @@ typedef struct {
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 counter;
uint32_t save_counter;
uint32_t click_counter;
uint32_t rx_counter;
uint16_t genie_save_counter;
FuriString* key;
} GenieApp;
@ -52,7 +57,7 @@ typedef struct {
* @param context The context
* @return next view id
*/
uint32_t genie_navigation_exit_callback(void* context) {
static uint32_t genie_navigation_exit_callback(void* context) {
UNUSED(context);
return VIEW_NONE;
}
@ -64,14 +69,17 @@ uint32_t genie_navigation_exit_callback(void* context) {
* @param context The context
* @return next view id
*/
uint32_t genie_navigation_submenu_callback(void* context) {
static uint32_t genie_navigation_submenu_callback(void* context) {
UNUSED(context);
return GenieViewSubmenu;
}
void genie_submenu_callback(void* context, uint32_t index) {
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;
@ -83,94 +91,130 @@ void genie_submenu_callback(void* context, uint32_t index) {
}
}
void genie_view_draw_callback(Canvas* canvas, void* model) {
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_draw_str(canvas, 5, 15, "Genie Sub-Ghz Recorder!!!");
canvas_draw_str(canvas, 5, 30, "Make sure A7 is connected");
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[20] = {0};
snprintf(buffer, COUNT_OF(buffer), "%ld", app->counter);
canvas_draw_str(canvas, 50, 45, buffer);
snprintf(buffer, COUNT_OF(buffer), "%ld", app->save_counter);
canvas_draw_str(canvas, 96, 45, buffer);
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 != 0xFFFF) {
snprintf(buffer, COUNT_OF(buffer), "Remaining codes %d", 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));
}
bool genie_view_input_callback(InputEvent* event, void* context) {
static bool genie_view_input_callback(InputEvent* event, void* context) {
UNUSED(context);
UNUSED(event);
return false;
}
uint32_t last_count() {
static 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));
static uint16_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;
}
void __gui_redraw() {
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) {
static void press_button(GenieApp* app) {
furi_hal_gpio_write(pin_remote, false);
app->pressed = true;
__gui_redraw();
}
void release_button(GenieApp* app) {
static void release_button(GenieApp* app) {
furi_hal_gpio_write(pin_remote, true);
app->pressed = false;
__gui_redraw();
}
void genie_packet(FuriString* buffer, void* context) {
static 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");
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_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->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;
}
void genie_tick(void* context) {
static void genie_tick(void* context) {
GenieApp* app = (GenieApp*)context;
if(!app->processing) {
if(app->pressed) {
release_button(app);
} else {
app->counter++;
app->click_counter++;
press_button(app);
}
}
}
void genie_enter_callback(void* context) {
static void genie_enter_callback(void* context) {
GenieApp* app = (GenieApp*)context;
genie_file_init();
start_listening(app->genie_subghz, FREQUENCY, genie_packet, context);
start_listening(app->genie_subghz, app->frequency, genie_packet, context);
furi_timer_start(app->timer, furi_ms_to_ticks(CLICK_SPEED));
}
void genie_exit_callback(void* context) {
static void genie_exit_callback(void* context) {
GenieApp* app = (GenieApp*)context;
app->processing = false;
release_button(app);
@ -178,12 +222,13 @@ void genie_exit_callback(void* context) {
furi_timer_stop(app->timer);
}
GenieApp* genie_app_alloc() {
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->counter = last_count();
app->save_counter = app->counter;
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);
@ -196,6 +241,7 @@ GenieApp* genie_app_alloc() {
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);
@ -203,6 +249,25 @@ GenieApp* genie_app_alloc() {
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);
@ -216,30 +281,7 @@ GenieApp* genie_app_alloc() {
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"
"Version 1.3\n---\n"
"Custom keeloq.c firmware\n"
"is required for this app.\n"
"---\n"
"This app clicks your remote\n"
"and records the keys, so you\n"
"can replay them later.\n"
"Connect your remote to pin\n"
"A7 and GND. Wiring diagram\n"
"at https://tinyurl.com/genierecorder or github.\n"
"Once connected and\n"
"firmware is updated,\n"
"run Start!\n---\n"
"author: @codeallnight\n"
"https://discord.com/invite/NsjCvqwPAd\n"
"https://youtube.com/@MrDerekJamison\n"
"https://github.com/jamisonderek/flipper-zero-tutorials");
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(
@ -248,12 +290,14 @@ GenieApp* genie_app_alloc() {
return app;
}
void genie_app_free(GenieApp* 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 File

@ -0,0 +1,24 @@
#pragma once
#define GENIE_ABOUT_TEXT \
"Genie garage door recorder.\n" \
"Version 2.4\n---\n" \
"Custom genie firmware is\n" \
"required for this app.\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" \
"---\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" \
"author: @codeallnight\n" \
"https://discord.com/invite/NsjCvqwPAd\n" \
"https://youtube.com/@MrDerekJamison\n" \
"https://github.com/jamisonderek/flipper-zero-tutorials" \
""

View File

@ -8,6 +8,12 @@
#define GENIE_SAVE_NAME "keys"
#define GENIE_SAVE_EXTENSION ".txt"
// Should match application.fam, fap_version.
#define GENIE_MAJOR_VERSION 2
#define GENIE_MINOR_VERSION 4
#define GENIE_MAGIC_CODE 0x472A
#ifdef TAG
#undef TAG
#endif
@ -33,6 +39,202 @@ void genie_file_init() {
furi_record_close(RECORD_STORAGE);
}
static uint16_t storage_file_read16(File* file) {
uint16_t read = 0;
char buffer[2] = {0};
storage_file_read(file, buffer, 2);
read |= (buffer[0] << 8);
read |= buffer[1];
return read;
}
static bool storage_file_write16(File* file, uint16_t data) {
char buffer[2] = {0};
buffer[0] = (data >> 8) & 0xFF;
buffer[1] = data & 0xFF;
return storage_file_write(file, buffer, 2) == 2;
}
static uint32_t storage_file_read32(File* file) {
uint32_t read = 0;
char buffer[4] = {0};
storage_file_read(file, buffer, 4);
read = (buffer[0] << 24);
read |= (buffer[1] << 16);
read |= (buffer[2] << 8);
read |= buffer[3];
return read;
}
static bool storage_file_write32(File* file, uint32_t data) {
char buffer[4] = {0};
buffer[0] = (data >> 24) & 0xFF;
buffer[1] = (data >> 16) & 0xFF;
buffer[2] = (data >> 8) & 0xFF;
buffer[3] = data & 0xFF;
return storage_file_write(file, buffer, 4) == 4;
}
enum {
GENIE_MAGIC = 0, // 2 bytes
GENIE_VERSION = 2, // 2 bytes
GENIE_SN = 4, // 4 bytes
GENIE_LAST_SENT = 8, // 2 bytes
GENIE_REC_COUNT = 10, // 2 bytes
GENIE_RESERVED = 12, // 4 bytes
GENIE_DATA = 16, // 64K bytes
} genie_file;
static void genie_create_file(Storage* storage, char* name, uint32_t low) {
File* file = storage_file_alloc(storage);
if(!file) {
FURI_LOG_E(TAG, "Failed to alloc file");
return;
}
if(storage_file_open(file, name, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) {
storage_file_write16(file, GENIE_MAGIC_CODE); // Magic value for Genie files.
storage_file_write16(file,
GENIE_MAJOR_VERSION << 8 | GENIE_MINOR_VERSION); // Version info
storage_file_write32(file, low); // Btn+SN
storage_file_write16(file, 0); // Last send (where to start looking for keys)
storage_file_write16(file, 0x0000); // Last recorded value (how many values we recorded)
storage_file_write32(file, 0x00000000); // Reserved data
char buffer[256] = {0}; // blocks of 0s.
for(int i = 0; i < 1024; i++) {
storage_file_write(file, buffer, 256);
}
}
storage_file_close(file);
storage_file_free(file);
}
static uint32_t hex_to_i32(const char* data) {
uint32_t value = 0;
for(int i = 0; i < 8; i++) {
value = value << 4;
value |= (data[i] >= 'a') ? 10 + data[i] - 'a' :
(data[i] >= 'A') ? 10 + data[i] - 'A' :
data[i] - '0';
}
return value;
}
uint16_t genie_save_bin(const char* key) {
uint16_t result = 0;
uint32_t key_high = 0;
uint32_t key_low = 0;
key_high = hex_to_i32(key);
key_low = hex_to_i32(key + 8);
FURI_LOG_D(TAG, "Saving binary key. key: %s high: %08lX low: %08lX", key, key_high, key_low);
Storage* storage = furi_record_open(RECORD_STORAGE);
char buffer[128] = {0};
snprintf(buffer, 128, "%s/%08lX.gne", GENIE_SAVE_FOLDER, key_low);
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, buffer)) {
FURI_LOG_D(TAG, "Creating file: %s", buffer);
genie_create_file(storage, buffer, key_low);
}
if(storage_file_open(file, buffer, 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;
}
uint32_t low = storage_file_read32(file);
if(low != key_low) {
FURI_LOG_E(TAG, "Key mismatch. Expected: %08lX, got: %08lX", key_low, low);
break;
}
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;
}
uint16_t count = storage_file_read16(file);
if(!storage_file_seek(file, GENIE_DATA + (count * 4), true)) {
FURI_LOG_E(
TAG, "Failed to seek to GENIE_DATA+count*4 @ %d", GENIE_DATA + (count * 4));
break;
}
uint32_t existing = storage_file_read32(file);
if(existing != 0 && existing != key_high) {
FURI_LOG_E(
TAG, "Key mismatch at %04X. Old: %08lX, New: %08lX", count, existing, key_high);
result = count;
break;
}
if(!storage_file_seek(file, GENIE_DATA + (count * 4), true)) {
FURI_LOG_E(
TAG, "Failed to seek to GENIE_DATA+count*4 @ %d", GENIE_DATA + (count * 4));
break;
}
if(!storage_file_write32(file, key_high)) {
FURI_LOG_E(TAG, "Failed to write to key_high to file: %08lX", key_high);
break;
}
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;
}
if(count != 0xFFFF) {
count++;
if(!storage_file_write16(file, count)) {
FURI_LOG_E(TAG, "Failed to write count to file: %04X", count);
break;
}
}
result = count;
} 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);
return result;
}
bool genie_save(uint32_t count, FuriString* key) {
bool success = false;
Storage* storage = furi_record_open(RECORD_STORAGE);

View File

@ -3,5 +3,6 @@
#include <furi.h>
bool genie_save(uint32_t count, FuriString* key);
uint16_t genie_save_bin(const char* key);
uint32_t genie_load();
void genie_file_init();

View File

@ -41,7 +41,6 @@ static void
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);
}
@ -119,7 +118,9 @@ void start_listening(
FURI_LOG_I(TAG, "Listening at frequency: %lu\r\n", frequency);
context->thread = furi_thread_alloc_ex("RX", 1024, listen_rx, context);
// This thread name is used by the genie protocol decoder to determine if it is running in the
// context of this application. If it is, it will not attempt to find the next code.
context->thread = furi_thread_alloc_ex("genie-rx", 1024, listen_rx, context);
furi_thread_start(context->thread);
}

View File

@ -0,0 +1,743 @@
/**
* The majority of this code is from the keeloq.c file. The Genie garage door openers
* use a modified version of the Keeloq algorithm. The speed of the transmission is
* different (twice as fast). We don't have the manufacturer's code, so instead we
* use the button+serial number to look through a file of previously captured codes.
*
* You can use the Genie Recorder app to capture codes and save them to a .GNE file.
* It uses the GPIO pin on the Flipper Zero to click the remote automatically and
* extract the codes. A full capture is 65536 codes, so it takes about 2 days. Your
* receiver may become out-of-sync with your remote & you may need to pair it again.
*
* @CodeAllNight
*/
#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
#define GENIE_MAJOR_VERSION 2
#define GENIE_APPS_DATA_FOLDER EXT_PATH("apps_data")
#define GENIE_SAVE_FOLDER \
GENIE_APPS_DATA_FOLDER "/" \
"genie"
#define GENIE_FILE_EXT ".gne"
enum {
GENIE_MAGIC = 0, // 2 bytes
GENIE_VERSION = 2, // 2 bytes
GENIE_SN = 4, // 4 bytes
GENIE_LAST_SENT = 8, // 2 bytes
GENIE_REC_COUNT = 10, // 2 bytes
GENIE_RESERVED = 12, // 4 bytes
GENIE_DATA = 16, // 64K bytes
} genie_file;
static const SubGhzBlockConst subghz_protocol_genie_const = {
.te_short = 200,
.te_long = 400,
.te_delta = 70,
.min_count_bit_for_found = 64,
};
struct SubGhzProtocolDecoderGenie {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t header_count;
};
struct SubGhzProtocolEncoderGenie {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
GenieDecoderStepReset = 0,
GenieDecoderStepCheckPreambula,
GenieDecoderStepSaveDuration,
GenieDecoderStepCheckDuration,
} GenieDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_genie_decoder = {
.alloc = subghz_protocol_decoder_genie_alloc,
.free = subghz_protocol_decoder_genie_free,
.feed = subghz_protocol_decoder_genie_feed,
.reset = subghz_protocol_decoder_genie_reset,
.get_hash_data = subghz_protocol_decoder_genie_get_hash_data,
.serialize = subghz_protocol_decoder_genie_serialize,
.deserialize = subghz_protocol_decoder_genie_deserialize,
.get_string = subghz_protocol_decoder_genie_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_genie_encoder = {
.alloc = subghz_protocol_encoder_genie_alloc,
.free = subghz_protocol_encoder_genie_free,
.deserialize = subghz_protocol_encoder_genie_deserialize,
.stop = subghz_protocol_encoder_genie_stop,
.yield = subghz_protocol_encoder_genie_yield,
};
const SubGhzProtocol subghz_protocol_genie = {
.name = SUBGHZ_PROTOCOL_GENIE_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 |
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_genie_decoder,
.encoder = &subghz_protocol_genie_encoder,
};
void* subghz_protocol_encoder_genie_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderGenie* instance = malloc(sizeof(SubGhzProtocolEncoderGenie));
instance->base.protocol = &subghz_protocol_genie;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 20;
instance->encoder.size_upload = 256;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_genie_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderGenie* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Set serial number and button number from data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_genie_set_sn_and_btn(SubGhzBlockGeneric* instance) {
uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit);
uint32_t key_fix = key >> 32;
instance->serial = key_fix & 0x0FFFFFFF;
instance->btn = key_fix >> 28;
}
/**
* Read 16-bits from file
* @param file Pointer to a File instance
* @return 16-bit unsigned integer
*/
static uint16_t subghz_protocol_genie_storage_file_read16(File* file) {
uint16_t read = 0;
char buffer[2] = {0};
storage_file_read(file, buffer, 2);
read |= (buffer[0] << 8);
read |= buffer[1];
return read;
}
/**
* Read 32-bits from file
* @param file Pointer to a File instance
* @return 32-bit unsigned integer
*/
static uint32_t subghz_protocol_genie_storage_file_read32(File* file) {
uint32_t read = 0;
char buffer[4] = {0};
storage_file_read(file, buffer, 4);
read = (buffer[0] << 24);
read |= (buffer[1] << 16);
read |= (buffer[2] << 8);
read |= buffer[3];
return read;
}
/**
* Write 16-bits to file
* @param file Pointer to a File instance
* @param data 16-bit unsigned integer
* @return true On success
*/
static bool subghz_protocol_genie_storage_file_write16(File* file, uint16_t data) {
char buffer[2] = {0};
buffer[0] = (data >> 8) & 0xFF;
buffer[1] = data & 0xFF;
return storage_file_write(file, buffer, 2) == 2;
}
/**
* Finds next code from the .gne file associated with the given code_low.
* @param code_low 32-bit unsigned integer (static part of code)
* @param code_high 32-bit unsigned integer (dynamic part of code)
* @param update_index If true, the index of the last sent code will be updated.
* @return 64-bit unsigned integer (next code) or 0xFFFFFFFFFFFFFFFF if not found
*/
static uint64_t subghz_protocol_genie_next_code_from_file(
uint32_t code_low,
uint32_t code_high,
bool update_index) {
Storage* storage = furi_record_open(RECORD_STORAGE);
char buffer[256] = {0};
snprintf(buffer, 128, "%s/%08lX%s", GENIE_SAVE_FOLDER, code_low, GENIE_FILE_EXT);
uint64_t result = 0xFFFFFFFFFFFFFFFF;
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 alloc file");
break;
}
if(!storage_file_exists(storage, buffer)) {
FURI_LOG_E(TAG, "File %s does not exist, rerun Genie Recorder app!", buffer);
break;
}
if(storage_file_open(file, buffer, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
if(!storage_file_seek(file, GENIE_VERSION, true)) {
FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_VERSION);
break;
}
uint16_t version = subghz_protocol_genie_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 %d", GENIE_SN);
break;
}
uint32_t low = subghz_protocol_genie_storage_file_read32(file);
if(low != code_low) {
FURI_LOG_E(TAG, "Btn/SN mismatch. Expected: %08lX, got: %08lX", code_low, low);
break;
}
if(!storage_file_seek(file, GENIE_LAST_SENT, true)) {
FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_SN);
break;
}
uint16_t last_sent = subghz_protocol_genie_storage_file_read16(file);
last_sent -= last_sent % (COUNT_OF(buffer) / 4);
result = code_high;
bool found = false;
int j = 0;
for(int i = 0; i <= 65536; i++) {
if(last_sent % (COUNT_OF(buffer) / 4) == 0) {
if(!storage_file_seek(file, GENIE_DATA + (last_sent * 4), true)) {
FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_DATA + (last_sent * 4));
break;
}
if(!storage_file_read(file, buffer, COUNT_OF(buffer))) {
FURI_LOG_E(TAG, "Failed to read %d", COUNT_OF(buffer));
break;
}
j = 0;
}
uint32_t high = (buffer[j++] << 24);
high |= (buffer[j++] << 16);
high |= (buffer[j++] << 8);
high |= buffer[j++];
if(found && high != 0) {
result = high;
break;
}
found |= (high == code_high);
if(last_sent == 0xFFFF) {
last_sent = 0;
} else {
last_sent++;
}
}
if(found && update_index) {
if(!storage_file_seek(file, GENIE_LAST_SENT, true)) {
FURI_LOG_E(TAG, "Failed to seek to %d", GENIE_SN);
break;
}
if(!subghz_protocol_genie_storage_file_write16(file, last_sent)) {
FURI_LOG_E(TAG, "Failed to write last sent. %d", last_sent);
break;
}
} else if(!found) {
FURI_LOG_E(TAG, "Code not found: %08lX", code_high);
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);
return result;
}
/**
* Finds next code for this remote.
* @param instance Pointer to a SubGhzProtocolEncoderGenie* instance
* @param counter_up attempt to find next code if the value is true
*/
static void
subghz_protocol_genie_find_next_code(SubGhzProtocolEncoderGenie* instance, bool counter_up) {
instance->generic.data_count_bit = 64;
instance->generic.cnt = 0x0000;
if(counter_up) {
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
uint64_t next_code =
subghz_protocol_genie_next_code_from_file(code_found_lo, code_found_hi, true);
FURI_LOG_D(
TAG,
"Low: %08lX High: %08lX New-High: %08lX",
code_found_lo,
code_found_hi,
(uint32_t)next_code);
instance->generic.cnt = next_code &
0xffff; // We don't know counter value, just use bottom of code.
if((next_code & 0xFFFFFFFF) == 0xFFFFFFFF) {
instance->generic.data = ((uint64_t)code_found_hi) << 32 | code_found_lo;
} else {
instance->generic.data = ((uint64_t)next_code) << 32 | code_found_lo;
}
}
}
/**
* Key generation from simple data
* @param instance Pointer to a SubGhzProtocolEncoderGenie* instance
* @param btn Button number, 4 bit
* @param counter_up increasing the counter if the value is true
*/
static bool subghz_protocol_genie_gen_data(
SubGhzProtocolEncoderGenie* instance,
uint8_t btn,
bool counter_up) {
uint32_t fix = (uint32_t)btn << 28 | instance->generic.serial;
uint32_t hop = 0;
uint64_t code_found_reverse;
subghz_protocol_genie_find_next_code(instance, counter_up);
code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
hop = code_found_reverse & 0x00000000ffffffff;
if(hop) {
uint64_t yek = (uint64_t)fix << 32 | hop;
instance->generic.data =
subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit);
}
return true;
}
/**
* Generating an upload from 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) {
furi_assert(instance);
// Generate next key
if(!subghz_protocol_genie_gen_data(instance, btn, true)) {
return false;
}
size_t index = 0;
size_t size_upload = 11 * 2 + 2 + (instance->generic.data_count_bit * 2) + 4;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
for(uint8_t i = 11; i > 0; i--) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short);
}
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short * 10);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_long);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short);
}
}
// +send 2 status bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_long);
// send end
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_genie_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_genie_const.te_short * 40);
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_genie_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderGenie* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_genie_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit !=
subghz_protocol_genie_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
break;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
subghz_protocol_genie_set_sn_and_btn(&instance->generic);
//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)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
ret = SubGhzProtocolStatusErrorParserOthers;
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->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
ret = SubGhzProtocolStatusErrorParserKey;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_genie_stop(void* context) {
SubGhzProtocolEncoderGenie* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_genie_yield(void* context) {
SubGhzProtocolEncoderGenie* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_genie_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderGenie* instance = malloc(sizeof(SubGhzProtocolDecoderGenie));
instance->base.protocol = &subghz_protocol_genie;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_genie_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGenie* instance = context;
free(instance);
}
void subghz_protocol_decoder_genie_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGenie* instance = context;
instance->decoder.parser_step = GenieDecoderStepReset;
}
void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderGenie* instance = context;
switch(instance->decoder.parser_step) {
case GenieDecoderStepReset:
if((level) && DURATION_DIFF(duration, subghz_protocol_genie_const.te_short) <
subghz_protocol_genie_const.te_delta) {
instance->decoder.parser_step = GenieDecoderStepCheckPreambula;
instance->header_count++;
}
break;
case GenieDecoderStepCheckPreambula:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_genie_const.te_short) <
subghz_protocol_genie_const.te_delta)) {
instance->decoder.parser_step = GenieDecoderStepReset;
break;
}
if((instance->header_count > 2) &&
(DURATION_DIFF(duration, subghz_protocol_genie_const.te_short * 10) <
subghz_protocol_genie_const.te_delta * 10)) {
// Found header
instance->decoder.parser_step = GenieDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = GenieDecoderStepReset;
instance->header_count = 0;
}
break;
case GenieDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = GenieDecoderStepCheckDuration;
}
break;
case GenieDecoderStepCheckDuration:
if(!level) {
if(duration >= ((uint32_t)subghz_protocol_genie_const.te_short * 2 +
subghz_protocol_genie_const.te_delta)) {
// Found end TX
instance->decoder.parser_step = GenieDecoderStepReset;
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)) {
if(instance->generic.data != instance->decoder.decode_data) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit =
subghz_protocol_genie_const.min_count_bit_for_found;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->header_count = 0;
}
break;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_genie_const.te_short) <
subghz_protocol_genie_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_genie_const.te_long) <
subghz_protocol_genie_const.te_delta * 2)) {
if(instance->decoder.decode_count_bit <
subghz_protocol_genie_const.min_count_bit_for_found) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
} else {
instance->decoder.decode_count_bit++;
}
instance->decoder.parser_step = GenieDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_genie_const.te_long) <
subghz_protocol_genie_const.te_delta * 2) &&
(DURATION_DIFF(duration, subghz_protocol_genie_const.te_short) <
subghz_protocol_genie_const.te_delta)) {
if(instance->decoder.decode_count_bit <
subghz_protocol_genie_const.min_count_bit_for_found) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
} else {
instance->decoder.decode_count_bit++;
}
instance->decoder.parser_step = GenieDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = GenieDecoderStepReset;
instance->header_count = 0;
}
} else {
instance->decoder.parser_step = GenieDecoderStepReset;
instance->header_count = 0;
}
break;
}
}
uint8_t subghz_protocol_decoder_genie_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGenie* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_genie_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderGenie* instance = context;
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
subghz_protocol_genie_set_sn_and_btn(&instance->generic);
return res;
}
SubGhzProtocolStatus
subghz_protocol_decoder_genie_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderGenie* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
if(SubGhzProtocolStatusOk !=
subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
if(instance->generic.data_count_bit !=
subghz_protocol_genie_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
break;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
res = SubGhzProtocolStatusOk;
} while(false);
return res;
}
void subghz_protocol_decoder_genie_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderGenie* instance = context;
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
// If we are in the genie thread (running the Genie Recorder app),
// output the protocol & key, then exit without looking for the next code.
const char* name = furi_thread_get_name(furi_thread_get_current_id());
if(strcmp(name, "genie-rx") == 0) {
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo);
return;
}
uint64_t next_code =
subghz_protocol_genie_next_code_from_file(code_found_lo, code_found_hi, false);
subghz_protocol_genie_set_sn_and_btn(&instance->generic);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo);
if((next_code & 0xFFFFFFFF) == 0xFFFFFFFF) {
furi_string_cat_printf(
output,
"No keys found. Please run the\r\n"
"Genie Recorder app to\r\n"
"extract keys from the\r\n"
"remote.");
instance->generic.cnt = 0;
instance->generic.data = ((uint64_t)code_found_hi) << 32 | code_found_lo;
} else if((next_code & 0xFFFFFFFF) == code_found_hi) {
furi_string_cat_printf(
output,
"Key missing. Please run the\r\n"
"Genie Recorder app to\r\n"
"extract additional keys from\r\n"
"the remote.");
instance->generic.cnt = 0;
instance->generic.data = ((uint64_t)code_found_hi) << 32 | code_found_lo;
} else {
furi_string_cat_printf(output, "Next code: %08lX", (uint32_t)next_code);
}
}

View File

@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_GENIE_NAME "Genie"
typedef struct SubGhzProtocolDecoderGenie SubGhzProtocolDecoderGenie;
typedef struct SubGhzProtocolEncoderGenie SubGhzProtocolEncoderGenie;
extern const SubGhzProtocolDecoder subghz_protocol_genie_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_genie_encoder;
extern const SubGhzProtocol subghz_protocol_genie;
/**
* Allocate SubGhzProtocolEncoderGenie.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderGenie* pointer to a SubGhzProtocolEncoderGenie instance
*/
void* subghz_protocol_encoder_genie_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderGenie.
* @param context Pointer to a SubGhzProtocolEncoderGenie instance
*/
void subghz_protocol_encoder_genie_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderGenie instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_genie_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderGenie instance
*/
void subghz_protocol_encoder_genie_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderGenie instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_genie_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderGenie.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderGenie* pointer to a SubGhzProtocolDecoderGenie instance
*/
void* subghz_protocol_decoder_genie_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderGenie.
* @param context Pointer to a SubGhzProtocolDecoderGenie instance
*/
void subghz_protocol_decoder_genie_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderGenie.
* @param context Pointer to a SubGhzProtocolDecoderGenie instance
*/
void subghz_protocol_decoder_genie_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderGenie instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderGenie instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_genie_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderGenie.
* @param context Pointer to a SubGhzProtocolDecoderGenie instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return SubGhzProtocolStatus
*/
SubGhzProtocolStatus subghz_protocol_decoder_genie_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderGenie.
* @param context Pointer to a SubGhzProtocolDecoderGenie instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return SubGhzProtocolStatus
*/
SubGhzProtocolStatus
subghz_protocol_decoder_genie_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderGenie instance
* @param output Resulting text
*/
void subghz_protocol_decoder_genie_get_string(void* context, FuriString* output);