This commit is contained in:
Derek Jamison 2024-02-17 23:10:20 -06:00
commit fdb828016a
48 changed files with 2099 additions and 445 deletions

View File

@ -1,6 +1,8 @@
# IR-Blaster # IR-Blaster
## Overview ## Overview
Good news -- The latest Xtreme and RogueMaster firmware now contain the patch! Additional settings and modifications were made by Sil333033. Thanks to RogueMaster for crediting me with the original code.
I created a YouTube video to explain how to modify official firmware to use an external IR Blaster. Here is the [original video](https://www.youtube.com/watch?v=o_Tz68ju4Dg). In the comments **Jeff-ss6qt** posted a question about if we can detect the IR hardware and switch automatically. It turns out that we can! This tutorial will show you how! Here is the [updated video](https://youtu.be/gRizmfNbIsM) that discusses the patch below, which does auto-detection. I created a YouTube video to explain how to modify official firmware to use an external IR Blaster. Here is the [original video](https://www.youtube.com/watch?v=o_Tz68ju4Dg). In the comments **Jeff-ss6qt** posted a question about if we can detect the IR hardware and switch automatically. It turns out that we can! This tutorial will show you how! Here is the [updated video](https://youtu.be/gRizmfNbIsM) that discusses the patch below, which does auto-detection.
[![Flipper Zero: Auto-detect IR Blaster](https://img.youtube.com/vi/gRizmfNbIsM/0.jpg)](https://youtu.be/gRizmfNbIsM) [![Flipper Zero: Auto-detect IR Blaster](https://img.youtube.com/vi/gRizmfNbIsM/0.jpg)](https://youtu.be/gRizmfNbIsM)

View File

@ -0,0 +1,272 @@
/**
* UartHelper is a utility class that helps with reading lines of data from a UART.
* It uses a stream buffer to receive data from the UART ISR, and a worker thread
* to dequeue data from the stream buffer and process it. The worker thread uses
* a ring buffer to hold data until a delimiter is found, at which point the line
* is extracted and the process_line callback is invoked.
*
* @author CodeAllNight
*/
#include <furi_hal.h>
#include "ring_buffer.h"
/**
* Callback invoked when a line is read from the UART.
*/
typedef void (*ProcessLine)(FuriString* line, void* context);
/**
* UartHelper is a utility class that helps with reading lines of data from a UART.
*/
typedef struct {
// UART bus & channel to use
FuriHalBus uart_bus;
FuriHalSerialHandle* serial_handle;
bool uart_init_by_app;
// Stream buffer to hold incoming data (worker will dequeue and process)
FuriStreamBuffer* rx_stream;
// Worker thread that dequeues data from the stream buffer and processes it
FuriThread* worker_thread;
// Buffer to hold data until a delimiter is found
RingBuffer* ring_buffer;
// Callback to invoke when a line is read
ProcessLine process_line;
void* context;
} UartHelper;
/**
* WorkerEventFlags are used to signal the worker thread to exit or to process data.
* Each flag is a bit in a 32-bit integer, so we can use the FuriThreadFlags API to
* wait for either flag to be set.
*/
typedef enum {
WorkerEventDataWaiting = 1 << 0, // bit flag 0 - data is waiting to be processed
WorkerEventExiting = 1 << 1, // bit flag 1 - worker thread is exiting
} WorkerEventFlags;
/**
* Invoked when a byte of data is received on the UART bus. This function
* adds the byte to the stream buffer and sets the WorkerEventDataWaiting flag.
*
* @param handle Serial handle
* @param event FuriHalSerialRxEvent
* @param context UartHelper instance
*/
static void uart_helper_received_byte_callback(
FuriHalSerialHandle* handle,
FuriHalSerialRxEvent event,
void* context) {
UartHelper* helper = context;
if(event == FuriHalSerialRxEventData) {
uint8_t data = furi_hal_serial_async_rx(handle);
furi_stream_buffer_send(helper->rx_stream, (void*)&data, 1, 0);
furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventDataWaiting);
}
}
/**
* Worker thread that dequeues data from the stream buffer and processes it. When
* a delimiter is found in the data, the line is extracted and the process_line callback
* is invoked. This thread will exit when the WorkerEventExiting flag is set.
*
* @param context UartHelper instance
* @return 0
*/
static int32_t uart_helper_worker(void* context) {
UartHelper* helper = context;
FuriString* line = furi_string_alloc();
uint32_t events;
do {
events = furi_thread_flags_wait(
WorkerEventDataWaiting | WorkerEventExiting, FuriFlagWaitAny, FuriWaitForever);
if(events & WorkerEventDataWaiting) {
size_t length_read = 0;
do {
// We will read out of the stream into this temporary read_buffer in chunks
// of up to 32 bytes. A tricker implementation would be to read directly
// into our ring_buffer, avoiding the extra copy.
uint8_t read_buffer[32];
size_t available = ring_buffer_available(helper->ring_buffer);
if(available == 0) {
// In our case, the buffer is large enough to hold the entire response,
// so we should never hit this case & data loss is fine in the
// exceptional case.
// If loosing data is unacceptable, you could transfer the buffer into
// a string and prepend that to the response, or you could increase the
// size of the buffer.
// Buffer is full, so data loss will happen at beginning of string!
// We read one byte (hopefully the delimiter).
available = 1;
}
// We will read up to 32 bytes, but no more than the available space in the
// ring buffer.
size_t request_bytes = available < COUNT_OF(read_buffer) ? available :
COUNT_OF(read_buffer);
// hasDelim will be true if at least one delimiter was found in the data.
bool hasDelim = false;
// Read up to 32 bytes from the stream buffer into our temporary read_buffer.
length_read =
furi_stream_buffer_receive(helper->rx_stream, read_buffer, request_bytes, 0);
// Add the data to the ring buffer, check for delimiters, and process lines.
if(length_read > 0) {
// Add the data to the ring buffer. If at least one delimiter is found,
// hasDelim will be true.
hasDelim = ring_buffer_add(helper->ring_buffer, read_buffer, length_read);
if(hasDelim) {
size_t index;
do {
// Find the next delimiter in the ring buffer.
index = ring_buffer_find_delim(helper->ring_buffer);
// If a delimiter was found, extract the line and process it.
if(index != FURI_STRING_FAILURE) {
// Extract the line from the ring buffer, advancing the read
// pointer to the next byte after the delimiter.
ring_buffer_extract_line(helper->ring_buffer, index, line);
// Invoke the callback to process the line.
if(helper->process_line) {
helper->process_line(line, helper->context);
}
}
} while(index != FURI_STRING_FAILURE);
}
}
// Keep reading until we have read all of the data from the stream buffer.
} while(length_read > 0);
}
// Keep processing data until the WorkerEventExiting flag is set.
} while((events & WorkerEventExiting) != WorkerEventExiting);
furi_string_free(line);
return 0;
}
UartHelper* uart_helper_alloc() {
// rx_buffer_size should be large enough to hold the entire response from the device.
const size_t rx_buffer_size = 2048;
// worker_stack_size should be large enough stack for the worker thread (including functions it calls).
const size_t worker_stack_size = 1024;
// uart_baud is the default baud rate for the UART.
const uint32_t uart_baud = 115200;
// The uart_helper uses USART1.
UartHelper* helper = malloc(sizeof(UartHelper));
helper->uart_bus = FuriHalBusUSART1;
helper->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
// Typically the UART is already enabled and will assert if you try to enable again.
helper->uart_init_by_app = !furi_hal_bus_is_enabled(helper->uart_bus);
if(helper->uart_init_by_app) {
furi_hal_serial_init(helper->serial_handle, uart_baud);
} else {
// If UART is already initialized, disable the console so it doesn't write to the UART.
// furi_hal_console_disable();
}
// process_line callback gets invoked when a line is read. By default the callback is not set.
helper->process_line = NULL;
// Set the baud rate for the UART
furi_hal_serial_set_br(helper->serial_handle, uart_baud);
// When a byte of data is received, it will be appended to the rx_stream. A worker thread
// will dequeue the data from the rx_stream.
helper->rx_stream = furi_stream_buffer_alloc(rx_buffer_size, 1);
// The worker thread will remove the data from the rx_stream and copy it into the ring_buffer.
// The worker thread will then process the data in the ring_buffer, invoking the process_line
// callback whenever a delimiter is found in the data.
helper->ring_buffer = ring_buffer_alloc();
// worker_thread is the routine that will process data from the rx_stream.
helper->worker_thread =
furi_thread_alloc_ex("UartHelperWorker", worker_stack_size, uart_helper_worker, helper);
furi_thread_start(helper->worker_thread);
// Set the callback to invoke when data is received.
furi_hal_serial_async_rx_start(
helper->serial_handle, uart_helper_received_byte_callback, helper, false);
return helper;
}
void uart_helper_set_delimiter(UartHelper* helper, char delimiter, bool include_delimiter) {
// Update the delimiter character and flag to determine if delimiter should be part
// of the response to the process_line callback.
ring_buffer_set_delimiter(helper->ring_buffer, delimiter, include_delimiter);
}
void uart_helper_set_callback(UartHelper* helper, ProcessLine process_line, void* context) {
// Set the process_line callback and context.
helper->process_line = process_line;
helper->context = context;
}
void uart_helper_set_baud_rate(UartHelper* helper, uint32_t baud_rate) {
// Update the baud rate for the UART.
furi_hal_serial_set_br(helper->serial_handle, baud_rate);
ring_buffer_clear(helper->ring_buffer);
}
void uart_helper_send(UartHelper* helper, const char* data, size_t length) {
// Transmit data via UART TX.
furi_hal_serial_tx(helper->serial_handle, (uint8_t*)data, length);
}
void uart_helper_send_string(UartHelper* helper, FuriString* string) {
const char* str = furi_string_get_cstr(string);
// UTF-8 strings can have character counts different then lengths!
// Count the bytes until a null is encountered.
size_t length = 0;
while(str[length] != 0) {
length++;
}
// Transmit data
uart_helper_send(helper, str, length);
}
void uart_helper_free(UartHelper* helper) {
// Signal that we want the worker to exit. It may be doing other work.
furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventExiting);
// Wait for the worker_thread to complete it's work and release its resources.
furi_thread_join(helper->worker_thread);
// Free the worker_thread.
furi_thread_free(helper->worker_thread);
furi_hal_serial_control_release(helper->serial_handle);
// If the UART was initialized by the application, deinitialize it, otherwise re-enable the console.
if(helper->uart_init_by_app) {
furi_hal_serial_deinit(helper->serial_handle);
} else {
// furi_hal_console_enable();
}
// Free the rx_stream and ring_buffer.
furi_stream_buffer_free(helper->rx_stream);
ring_buffer_free(helper->ring_buffer);
free(helper);
}

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 # 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. ## Overview
<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.
Document sections: Version 3.x no longer requires changes to the firmware! The application includes the Genie protocol encoder/decoder, thanks to @MMX for the suggestion.
- [Description](#description)
- [Installing](#installing) - [Warnings](#warnings)
- [Determine Genie Frequency](#determine-genie-frequency)
- [Connecting to remote](#connecting-to-remote) - [Connecting to remote](#connecting-to-remote)
- [Capture codes](#capture-codes) - [Capture codes](#capture-codes)
- [Sub-GHz Read](#sub-ghz-read) - [Sending signals](#sending-signals)
- [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)
- [.GNE file format](#gne-file-format) - [.GNE file format](#gne-file-format)
## Description 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.
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. The application also has the ability to send the next code to the garage door opener. This starts from the first captured signal.
## Installing 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.
- 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 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.
- 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. ## Warnings
- Step 4. Change the ``Frequency`` to 315000000.
- Step 5. Press the BACK button to continue scanning on the new frequency. 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).
- Step 6. Press a button on your remote to see if it is detected.
- Step 7. If it is NOT detected, try 390000000. 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 ## Connecting to remote
<img src="wiring.png" width="45%"/> <img src="wiring.png" width="45%"/>
<img src="wiring-2.jpg" width="40%"/> <img src="wiring-2.jpg" width="40%"/>
<p/> <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 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). - 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). - 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. - 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 ## 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 [Connecting to remote](#connecting-to-remote) section.
- Step 1. Be sure you have followed the steps in the [Capturing codes](#capture-codes) 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 2. On your Flipper, press the BACK button until you are at the Desktop. - Step 3. Run the Genie Recorder application.
- Step 3. On your Flipper, press the OK button and choose "Sub-GHz". - 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 4. Choose "Read" to start scanning. - Step 5. Press `Back` button to return to the main application.
- Step 5. Press the LEFT button to edit the Config. - Step 6. Choose `Learn` to start capturing and learning signals.
- Step 6. Change the ``Frequency`` to the frequency you determined above. - `Remaining code` - how many codes still need to be captured.
- Step 7. Press the BACK button to continue scanning on the new frequency. - `Click` - how many times a click has been sent to the remote.
- Step 8. Press a button on your Genie remote. If read successfully you should hear a beep and see "Genie" followed by some code. - `Prev` - how many codes were previously captured (no codes captured this session yet).
- Step 9. Press the OK button to see the details of the code. - `Got` - total number of codes captured.
- 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. - 16 digit hex code - the last code that was captured.
- Step 11. If you press "Send", it should generate the next code and open the garage door. - 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 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 2. Run the Genie Recorder application.
- Step 3. On your Flipper, press the OK button and choose "Sub-GHz". - Step 3. Choose `Config` and set the frequency to the frequency your remote runs at.
- Step 4. Choose "Saved". - Step 4. Choose `Genie File` and select the .GNE file that was saved during the [Capture codes](#capture-codes) section steps.
- Step 5. Select the file that you saved during the [Sub-GHz Read](#sub-ghz-read) section steps. - Step 5. Press `Back` button to return to the main application.
- Step 6. Choose "Emulate". - Step 6. Choose `Send` to load the send screen.
- Step 7. Press the OK button to generate the next code and open the garage door! - `NO .GNE FILE LOADED` - Confirm you selected a valid file in step 4.
- 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. - `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 ## 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 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.
- If sending signal is not working, 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.
## .GNE file format ## .GNE file format
The file format is as follows: The file format is as follows:
- 2 bytes: "G*" (0x47 0x2A)
- 2 bytes: "G\*" (0x47 0x2A)
- 1 byte: Major version (0x02) - 1 byte: Major version (0x02)
- 1 byte: Minor version (e.g. 0x05) - 1 byte: Minor version (e.g. 0x05)
- 4 bytes: Fix code from remote (matches filename.GNE) - 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: Reserved for future use.
- 4 bytes x 65536: The dynamic part of the code (0x00000000 if no code has been received) - 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( App(
appid="genie_record_v2", appid="genie_record_v3",
name="Genie Door Recorder v2", name="Genie Door Recorder v3",
apptype=FlipperAppType.EXTERNAL, apptype=FlipperAppType.EXTERNAL,
entry_point="genie_record_app", entry_point="genie_record_app",
requires=["gui", "subghz"], requires=["gui", "subghz"],
stack_size=4 * 1024, stack_size=4 * 1024,
fap_version=(2, 5), fap_version=(3, 0),
fap_icon="genie.png", fap_icon="genie.png",
fap_category="Sub-GHz", fap_category="Sub-GHz",
fap_icon_assets="assets", 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/variable_item_list.h>
#include <gui/modules/widget.h> #include <gui/modules/widget.h>
#include "genie_subghz_receive.h"
#include "genie_about.h" #include "genie_about.h"
#include "genie_app.h"
#include "genie_file.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; 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) { int32_t genie_record_app(void* p) {
UNUSED(p); UNUSED(p);
GenieApp* app = genie_app_alloc(); GenieApp* app = genie_app_alloc();
view_dispatcher_run(app->view_dispatcher); view_dispatcher_run(genie_app_get_view_dispatcher(app));
genie_app_free(app); genie_app_free(app);
return 0; return 0;
} }

View File

@ -2,21 +2,17 @@
#define GENIE_ABOUT_TEXT \ #define GENIE_ABOUT_TEXT \
"Genie garage door recorder.\n" \ "Genie garage door recorder.\n" \
"Version 2.5\n---\n" \ "Version 3.0\n---\n" \
"Custom genie firmware is\n" \
"required for this app.\n" \
"---\n" \
"This app clicks your remote\n" \ "This app clicks your remote\n" \
"and records the keys, so you\n" \ "and records the keys, so you\n" \
"can replay them later when\n" \ "can replay them later with\n" \
"you load a Sub-GHz file!\n" \ "the application!\n" \
"---\n" \ "---\n" \
"Connect Genie remote to pin\n" \ "Connect Genie remote to pin\n" \
"A7 and GND. Wiring diagram\n" \ "A7 and GND. Wiring diagram\n" \
"at https://tinyurl.com/genierecorder or github.\n" \ "at https://tinyurl.com/genierecorder or github.\n" \
"Once remote connected and\n" \ "Once remote connected," \
"firmware is updated,\n" \ "click Learn to capture!\n---\n" \
"click Start to capture!\n---\n" \
"author: @codeallnight\n" \ "author: @codeallnight\n" \
"https://discord.com/invite/NsjCvqwPAd\n" \ "https://discord.com/invite/NsjCvqwPAd\n" \
"https://youtube.com/@MrDerekJamison\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 <storage/storage.h>
#include <flipper_format.h> #include <flipper_format.h>
@ -19,6 +21,13 @@
#endif #endif
#define TAG "GenieFile" #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) { static void ensure_dir_exists(Storage* storage, char* dir) {
if(!storage_dir_exists(storage, dir)) { if(!storage_dir_exists(storage, dir)) {
FURI_LOG_I(TAG, "Creating directory: %s", dir); FURI_LOG_I(TAG, "Creating directory: %s", dir);
@ -83,7 +92,7 @@ enum {
GENIE_REC_COUNT = 10, // 2 bytes GENIE_REC_COUNT = 10, // 2 bytes
GENIE_RESERVED = 12, // 4 bytes GENIE_RESERVED = 12, // 4 bytes
GENIE_DATA = 16, // 64K bytes GENIE_DATA = 16, // 64K bytes
} genie_file; } genie_file_layout;
static void genie_create_file(Storage* storage, char* name, uint32_t low) { static void genie_create_file(Storage* storage, char* name, uint32_t low) {
File* file = storage_file_alloc(storage); File* file = storage_file_alloc(storage);
@ -120,6 +129,181 @@ static uint32_t hex_to_i32(const char* data) {
return value; 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 genie_save_bin(const char* key) {
uint32_t result = 0; uint32_t result = 0;
uint32_t key_high = 0; uint32_t key_high = 0;
@ -288,9 +472,10 @@ bool genie_save(uint32_t count, FuriString* key) {
uint32_t genie_load() { uint32_t genie_load() {
uint32_t count = 0; uint32_t count = 0;
uint32_t key_hi = 0;
uint32_t key_lo = 0;
Storage* storage = furi_record_open(RECORD_STORAGE); Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* buffer = furi_string_alloc(); FuriString* buffer = furi_string_alloc();
File* file = NULL; File* file = NULL;
do { do {
if(!storage) { if(!storage) {
@ -310,9 +495,18 @@ uint32_t genie_load() {
char data[8 + 1 + 16 + 2 + 1] = {0}; 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)) { while(storage_file_read(file, data, 8 + 1 + 16 + 2)) {
sscanf(data, "%08lX", &count); sscanf(data, "%08lX,%08lX%08lX", &count, &key_hi, &key_lo);
FURI_LOG_D(TAG, "Read: %s, count: %ld", data, count); FURI_LOG_D(
TAG, "Read: %s, count: %ld, hi: %08lx, lo: %08lx", data, count, key_hi, key_lo);
} }
} }
} while(false); } while(false);
@ -324,5 +518,13 @@ uint32_t genie_load() {
furi_string_free(buffer); furi_string_free(buffer);
furi_record_close(RECORD_STORAGE); 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; return count;
} }

View File

@ -2,6 +2,16 @@
#include <furi.h> #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); bool genie_save(uint32_t count, FuriString* key);
uint32_t genie_save_bin(const char* key); uint32_t genie_save_bin(const char* key);
uint32_t genie_load(); 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 #endif
#define TAG "GenieSubGHzReceive" #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() { static SubGhzEnvironment* load_environment() {
SubGhzEnvironment* environment = subghz_environment_alloc(); SubGhzEnvironment* environment = subghz_environment_alloc();
subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME); subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry_genie);
subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME);
subghz_environment_set_came_atomo_rainbow_table_file_name(
environment, SUBGHZ_CAME_ATOMO_DIR_NAME);
subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
return environment; return environment;
} }

View File

@ -4,9 +4,9 @@
#include <furi_hal.h> #include <furi_hal.h>
#include <lib/subghz/receiver.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/cc1101_int/cc1101_int_interconnect.h>
#include <lib/subghz/devices/devices.h> #include <lib/subghz/devices/devices.h>
#include "protocols/genie.h"
typedef void (*SubghzPacketCallback)(FuriString* buffer, void* context); typedef void (*SubghzPacketCallback)(FuriString* buffer, void* context);
@ -18,7 +18,9 @@ typedef enum {
SUBGHZ_RECEIVER_UNINITIALIZED, SUBGHZ_RECEIVER_UNINITIALIZED,
} SubghzReceiverState; } SubghzReceiverState;
typedef struct { typedef struct GenieSubGhz GenieSubGhz;
struct GenieSubGhz {
SubGhzEnvironment* environment; SubGhzEnvironment* environment;
FuriStreamBuffer* stream; FuriStreamBuffer* stream;
FuriThread* thread; FuriThread* thread;
@ -27,7 +29,7 @@ typedef struct {
SubghzReceiverState status; SubghzReceiverState status;
SubghzPacketCallback callback; SubghzPacketCallback callback;
void* callback_context; void* callback_context;
} GenieSubGhz; };
GenieSubGhz* genie_subghz_alloc(); GenieSubGhz* genie_subghz_alloc();
void genie_subghz_free(GenieSubGhz* subghz); 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 "genie.h"
#include "keeloq_common.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" #define TAG "SubGhzProtocolGenie"
// Should be major version of supported Genie Recorder (.gne) files // 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 * @param instance Pointer to a SubGhzProtocolEncoderGenie instance
* @return true On success * @return true On success
*/ */
static bool static bool subghz_protocol_encoder_genie_get_upload(
subghz_protocol_encoder_genie_get_upload(SubGhzProtocolEncoderGenie* instance, uint8_t btn) { SubGhzProtocolEncoderGenie* instance,
uint8_t btn,
bool counter_up) {
furi_assert(instance); furi_assert(instance);
// Generate next key // Generate next key
if(!subghz_protocol_genie_gen_data(instance, btn, true)) { if(!subghz_protocol_genie_gen_data(instance, btn, counter_up)) {
return false; return false;
} }
@ -465,12 +461,21 @@ SubGhzProtocolStatus
subghz_protocol_genie_set_sn_and_btn(&instance->generic); 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 //optional parameter parameter
flipper_format_read_uint32( flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
// Get_upload will generate the next key. // 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; ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break; break;
} }
@ -582,10 +587,14 @@ void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t dura
subghz_protocol_genie_const.te_delta)) { subghz_protocol_genie_const.te_delta)) {
// Found end TX // Found end TX
instance->decoder.parser_step = GenieDecoderStepReset; 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 >= if((instance->decoder.decode_count_bit >=
subghz_protocol_genie_const.min_count_bit_for_found) && subghz_protocol_genie_const.min_count_bit_for_found) &&
(instance->decoder.decode_count_bit <= (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) { if(instance->generic.data != instance->decoder.decode_data) {
instance->generic.data = instance->decoder.decode_data; instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->generic.data_count_bit =

View File

@ -1,6 +1,12 @@
#pragma once #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" #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