Compare commits

...

3 Commits

Author SHA1 Message Date
d4rks1d33 86f5aae002 Remove spanish comments
Build Dev Firmware / build (push) Failing after 14m41s
2026-05-18 22:54:40 -03:00
d4rks1d33 46f3a5c993 More updates :D 2026-05-18 22:53:07 -03:00
D4rk$1d3 52015fb289 Update README
Build Dev Firmware / build (push) Failing after 16s
Added images for Custom Emulation Settings and Scene.
2026-05-18 20:52:27 -03:00
8 changed files with 216 additions and 221 deletions
+8 -61
View File
@@ -1,100 +1,47 @@
name: Build Dev Firmware
on:
push:
branches:
- main
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get latest semantic version tag
id: version
shell: bash
run: |
git fetch --tags
LAST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true)
if [ -z "$LAST_TAG" ]; then
NEW_TAG="1.0.0"
else
IFS='.' read -r MAJOR MINOR PATCH <<< "$LAST_TAG"
PATCH=$((PATCH + 1))
NEW_TAG="$MAJOR.$MINOR.$PATCH"
fi
echo "Latest tag: $LAST_TAG"
echo "New tag: $NEW_TAG"
echo "NEW_TAG=$NEW_TAG" >> $GITHUB_OUTPUT
- name: Create Git tag
shell: bash
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag ${{ steps.version.outputs.NEW_TAG }}
git push origin ${{ steps.version.outputs.NEW_TAG }}
- uses: actions/checkout@v4
- name: Build firmware
shell: bash
run: |
export DIST_SUFFIX=Flipper-ARF
chmod +x fbt
./fbt COMPACT=1 DEBUG=0 updater_package
- name: Generate tag name
id: tag
run: echo "TAG=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Detect firmware updater
id: firmware
shell: bash
run: |
DIR=$(ls -d dist/f7-* | head -n 1)
FILE="$DIR/flipper-z-f7-update-Flipper-ARF.tgz"
if [ ! -f "$FILE" ]; then
echo "Firmware file not found!"
exit 1
fi
echo "Found firmware: $FILE"
echo "FILE=$FILE" >> $GITHUB_OUTPUT
- name: Read changelog
id: changelog
shell: bash
run: |
{
echo 'CHANGELOG<<EOF'
cat CHANGELOG.md
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.NEW_TAG }}
name: Release ${{ steps.version.outputs.NEW_TAG }}
tag_name: ${{ steps.tag.outputs.TAG }}
name: Dev Build ${{ steps.tag.outputs.TAG }}
body: ${{ steps.changelog.outputs.CHANGELOG }}
make_latest: true
files: |
${{ steps.firmware.outputs.FILE }}
files: ${{ steps.firmware.outputs.FILE }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+14 -118
View File
@@ -2,122 +2,18 @@
---
# Added
### Added
- Protocol name allowlist filter: in Receiver Config, a new "Proto Filter"
field accepts a comma-separated list of protocol names (e.g. "Ford V2,VAG").
When set, the receiver ignores all decoded signals that are not in the list,
reducing RAM usage and increasing the chance of capturing the target protocol.
Leave empty to disable (default behavior, all protocols accepted).
Setting is persisted in last_subghz.settings under the ProtocolFilter key.
## CarEmulate Scene
* Added custom automotive key emulation view with:
* D-pad button layout
* TX overlay animation
* Counter delta display
* TX power control integration
* Added `CarEmulateSettings` scene with:
* Enable/disable toggle for the custom emulate UI
* TX power selector
* Persistent storage through `SubGhzLastSettings`
### Special Thanks
Special thanks to [RocketGod-git](https://github.com/RocketGod-git) for inspiration from the Proto Pirate application and related UI/interaction concepts that helped shape the CarEmulate workflow.
---
## Generic Custom Button API (`custom_btn_i.h`)
Protocols can now expose UP/DOWN/LEFT/RIGHT button cycling in the transmitter view using a single macro instead of manually wiring switch/case handlers and scattered `set_original()` / `set_max()` calls.
### New macro
```
SUBGHZ_CUSTOM_BTN_DEFINE_MAP(
my_proto,
{SUBGHZ_CUSTOM_BTN_OK, 0xAA},
{SUBGHZ_CUSTOM_BTN_UP, 0xBB},
{SUBGHZ_CUSTOM_BTN_DOWN, 0xCC},
{SUBGHZ_CUSTOM_BTN_LEFT, 0xDD},
{SUBGHZ_CUSTOM_BTN_RIGHT, 0xEE},
)
```
The macro automatically generates:
* `my_proto_custom_btn_to_code()`
* `my_proto_code_to_custom_btn()`
* `my_proto_custom_btn_init()`
---
## New Protocols
### Land Rover
* Added support for the Land Rover Sub-GHz protocol.
### Special Thanks
Thanks to [Zero-Mega](https://github.com/Zero-Mega) for research, references, and contributions related to Land Rover protocol support.
---
# Fixed
## CarEmulate
* Fixed `car_emulate_apply_button()` being fully commented out.
* D-pad input never reached the `custom_btn` system.
* Protocols always transmitted the originally captured button regardless of the selected key.
* Fixed `custom_btn_id` potentially being lost if `subghz_tx_start()` internally reset SubGhz state before encoder `deserialize()` execution.
* The selected button is now passed into `car_emulate_start_tx()`
* The button is re-applied immediately before TX begins.
* Fixed hardcoded button labels in the CarEmulate view.
* Previous labels:
* `LOCK`
* `UNLOCK`
* `PANIC`
* `BOOT`
* `XXX`
* Labels are now dynamically stored in the view model and configured by the scene through:
* `subghz_car_emulate_view_set_labels()`
---
# Internal Changes
## `custom_btn_i.h`
* Added `SubGhzCustomBtnEntry`
* Added `SUBGHZ_CUSTOM_BTN_DEFINE_MAP`
* Added automatic conversion helpers
* Added automatic `_init()` helper generation
---
# Ongoing Work
* Continued improvements and cleanup across additional automotive protocols.
* Ongoing protocol analysis, decoder refinement, encoder validation, and transmit reliability improvements.
* Additional protocol integrations and UI enhancements are still actively being developed.
---
# Notes
The old approach required:
* Manual switch/case handlers
* Manual button registration
* Repeated `subghz_custom_btn_set_original()`
* Repeated `subghz_custom_btn_set_max()`
* Protocol-specific duplicated logic
The new API centralizes all button mapping behavior into a single reusable macro system.
### Changed
- Protocol Filter: replaced free-text input with a dedicated protocol list
scene (Proto Filter in Receiver Config). All registered protocols are shown
as toggleable items (--- / ONLY). Selecting one or more protocols restricts
the receiver to only show those; leaving all as --- disables the filter.
The active count is shown inline in Receiver Config ("N set" or "All").
Filter is persisted across sessions and cleared by Reset to default.
+2
View File
@@ -36,6 +36,8 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| Keeloq Key Manager | Mod Hopping Config |
| ![PSA Decrypt](.arf_pictures/psa_decrypt_builtin.png) | ![Counter BruteForce](.arf_pictures/counter_bruteforce.png) |
| PSA XTEA Decrypt | Counter BruteForce |
| ![Custom Emulation Settings](.arf_pictures/custom_emulation_settings.png) | ![Custom Emulation Scene](.arf_pictures/custom_emulation_scene.png) |
| Custom Emulation Settings | Custom Emulation Scene |
---
@@ -1,53 +1,135 @@
#include "../subghz_i.h"
#include <lib/subghz/subghz_protocol_registry.h>
void subghz_scene_protocol_list_submenu_callback(void* context, uint32_t index) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
}
#define TAG "SubGhzSceneProtocolList"
void subghz_scene_protocol_list_on_enter(void* context) {
SubGhz* subghz = context;
/* ── helpers ──────────────────────────────────────────────────────────────── */
submenu_reset(subghz->submenu);
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
char header_str[32];
snprintf(header_str, sizeof(header_str), "Protocols: %zu", protocol_count);
submenu_set_header(subghz->submenu, header_str);
for(size_t i = 0; i < protocol_count; i++) {
const SubGhzProtocol* protocol =
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
if(protocol) {
submenu_add_item(
subghz->submenu,
protocol->name,
i,
subghz_scene_protocol_list_submenu_callback,
subghz);
}
}
submenu_set_selected_item(
subghz->submenu,
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
}
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneProtocolList, event.event);
return true;
static bool proto_filter_contains(const char* filter, const char* name) {
const char* p = filter;
while(*p) {
const char* comma = strchr(p, ',');
size_t len = comma ? (size_t)(comma - p) : strlen(p);
if(len == strlen(name) && strncmp(p, name, len) == 0) return true;
if(!comma) break;
p = comma + 1;
}
return false;
}
static void proto_filter_toggle(char* filter, size_t filter_size, const char* name) {
if(proto_filter_contains(filter, name)) {
/* remove it */
char tmp[256] = {0};
const char* p = filter;
bool first = true;
while(*p) {
const char* comma = strchr(p, ',');
size_t len = comma ? (size_t)(comma - p) : strlen(p);
if(!(len == strlen(name) && strncmp(p, name, len) == 0)) {
if(!first) strncat(tmp, ",", sizeof(tmp) - strlen(tmp) - 1);
strncat(tmp, p, len < sizeof(tmp) - strlen(tmp) - 1 ? len : 0);
first = false;
}
if(!comma) break;
p = comma + 1;
}
strncpy(filter, tmp, filter_size - 1);
filter[filter_size - 1] = '\0';
} else {
/* add it */
if(filter[0] != '\0') strncat(filter, ",", filter_size - strlen(filter) - 1);
strncat(filter, name, filter_size - strlen(filter) - 1);
}
}
/* ── callbacks ────────────────────────────────────────────────────────────── */
static void subghz_scene_protocol_list_item_changed(VariableItem* item) {
SubGhz* subghz = variable_item_get_context(item);
uint8_t value_index = variable_item_get_current_value_index(item);
uint32_t proto_idx =
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList);
size_t selected =
variable_item_list_get_selected_item_index(subghz->variable_item_list);
UNUSED(proto_idx);
const SubGhzProtocol* protocol =
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, selected);
if(!protocol) return;
const char* name = protocol->name;
bool currently_in =
proto_filter_contains(subghz->last_settings->protocol_filter, name);
if((value_index == 1) != currently_in) {
proto_filter_toggle(
subghz->last_settings->protocol_filter,
sizeof(subghz->last_settings->protocol_filter),
name);
}
variable_item_set_current_value_text(item, value_index ? "ONLY" : "---");
subghz_last_settings_save(subghz->last_settings);
}
/* ── scene callbacks ──────────────────────────────────────────────────────── */
void subghz_scene_protocol_list_on_enter(void* context) {
SubGhz* subghz = context;
VariableItemList* list = subghz->variable_item_list;
variable_item_list_reset(list);
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
for(size_t i = 0; i < protocol_count; i++) {
const SubGhzProtocol* protocol =
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
if(!protocol) continue;
VariableItem* item = variable_item_list_add(
list,
protocol->name,
2,
subghz_scene_protocol_list_item_changed,
subghz);
bool enabled = proto_filter_contains(
subghz->last_settings->protocol_filter, protocol->name);
variable_item_set_current_value_index(item, enabled ? 1 : 0);
variable_item_set_current_value_text(item, enabled ? "ONLY" : "---");
}
variable_item_list_set_selected_item(
list,
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
}
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneProtocolList, event.event);
consumed = true;
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(subghz->scene_manager);
consumed = true;
}
return consumed;
}
void subghz_scene_protocol_list_on_exit(void* context) {
SubGhz* subghz = context;
submenu_reset(subghz->submenu);
scene_manager_set_scene_state(
subghz->scene_manager,
SubGhzSceneProtocolList,
variable_item_list_get_selected_item_index(subghz->variable_item_list));
variable_item_list_reset(subghz->variable_item_list);
}
@@ -105,6 +105,29 @@ static void subghz_scene_add_to_history_callback(
SubGhz* subghz = context;
// The check can be moved to /lib/subghz/receiver.c, but may result in false positives
/* Protocol name allowlist filter — if non-empty, drop anything not in the list */
if(subghz->last_settings->protocol_filter[0] != '\0') {
const char* proto_name = decoder_base->protocol->name;
const char* filter = subghz->last_settings->protocol_filter;
bool allowed = false;
/* Walk the comma-separated list */
const char* p = filter;
while(*p) {
const char* comma = strchr(p, ',');
size_t len = comma ? (size_t)(comma - p) : strlen(p);
if(len == strlen(proto_name) && strncmp(p, proto_name, len) == 0) {
allowed = true;
break;
}
if(!comma) break;
p = comma + 1;
}
if(!allowed) {
FURI_LOG_D(TAG, "%s filtered by allowlist", proto_name);
return;
}
}
if((decoder_base->protocol->flag & subghz->ignore_filter) == 0) {
SubGhzHistory* history = subghz->history;
FuriString* item_name = furi_string_alloc();
@@ -17,6 +17,7 @@ enum SubGhzSettingIndex {
SubGhzSettingIndexIgnoreNiceFlorS,
SubGhzSettingIndexDeleteOldSignals,
SubGhzSettingIndexSound,
SubGhzSettingIndexProtoFilter,
SubGhzSettingIndexResetToDefault,
SubGhzSettingIndexLock,
SubGhzSettingIndexRAWThresholdRSSI,
@@ -445,7 +446,9 @@ static void subghz_scene_receiver_config_set_delete_old_signals(VariableItem* it
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
furi_assert(context);
SubGhz* subghz = context;
if(index == SubGhzSettingIndexLock) {
if(index == SubGhzSettingIndexProtoFilter) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneProtocolList);
} else if(index == SubGhzSettingIndexLock) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock);
} else if(index == SubGhzSettingIndexResetToDefault) {
@@ -473,6 +476,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
subghz->last_settings->filter = subghz->filter;
subghz->last_settings->delete_old_signals = false;
subghz->last_settings->tx_power = subghz->tx_power = 0;
subghz->last_settings->protocol_filter[0] = '\0';
subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[default_index]);
subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[default_index]);
@@ -668,6 +672,25 @@ void subghz_scene_receiver_config_on_enter(void* context) {
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
SubGhzCustomEventManagerSet) {
/* Protocol filter */
item = variable_item_list_add(
subghz->variable_item_list,
"Proto Filter",
1,
NULL,
subghz);
if(subghz->last_settings->protocol_filter[0] == '\0') {
variable_item_set_current_value_text(item, "All");
} else {
uint8_t count = 1;
for(const char* p = subghz->last_settings->protocol_filter; *p; p++) {
if(*p == ',') count++;
}
static char filter_count_str[8];
snprintf(filter_count_str, sizeof(filter_count_str), "%u set", count);
variable_item_set_current_value_text(item, filter_count_str);
}
// Reset to default
variable_item_list_add(subghz->variable_item_list, "Reset to default", 1, NULL, NULL);
@@ -23,6 +23,7 @@
#define SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP "LedAndPowerAmp"
#define SUBGHZ_LAST_SETTING_FIELD_TX_POWER "TXPower"
#define SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE "CustomCarEmulate"
#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER "ProtocolFilter"
SubGhzLastSettings* subghz_last_settings_alloc(void) {
SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings));
@@ -51,6 +52,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
instance->enable_preset_hopping = false;
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
instance->leds_and_amp = true;
instance->protocol_filter[0] = '\0';
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
@@ -172,6 +174,19 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
instance->custom_car_emulate = false;
flipper_format_rewind(fff_data_file);
}
FuriString* filter_str = furi_string_alloc();
if(flipper_format_read_string(
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER, filter_str)) {
strncpy(
instance->protocol_filter,
furi_string_get_cstr(filter_str),
sizeof(instance->protocol_filter) - 1);
instance->protocol_filter[sizeof(instance->protocol_filter) - 1] = '\0';
} else {
instance->protocol_filter[0] = '\0';
flipper_format_rewind(fff_data_file);
}
furi_string_free(filter_str);
} while(0);
} else {
@@ -297,6 +312,12 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
1)) {
break;
}
if(!flipper_format_write_string_cstr(
file,
SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER,
instance->protocol_filter)) {
break;
}
saved = true;
} while(0);
@@ -31,6 +31,7 @@ typedef struct {
bool leds_and_amp;
uint8_t tx_power;
bool custom_car_emulate;
char protocol_filter[256]; /* comma-separated allowlist, empty = disabled */
} SubGhzLastSettings;
SubGhzLastSettings* subghz_last_settings_alloc(void);