Compare commits

..

3 Commits

Author SHA1 Message Date
d4rks1d33 5825020a20 more fixes
Build Dev Firmware / build (push) Failing after 13s
2026-05-08 14:52:08 +00:00
d4rks1d33 91e9a4cbac fix workflow 2026-05-08 14:49:18 +00:00
d4rks1d33 c92b1db8b7 some updates
Build Dev Firmware / build (push) Failing after 20s
2026-05-08 14:43:24 +00:00
35 changed files with 2229 additions and 2363 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

+60 -17
View File
@@ -1,47 +1,90 @@
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:
- uses: actions/checkout@v4
- 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 }}
- 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
run: |
{
echo 'CHANGELOG<<EOF'
cat CHANGELOG.md
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Create Release
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.TAG }}
name: Dev Build ${{ steps.tag.outputs.TAG }}
body: ${{ steps.changelog.outputs.CHANGELOG }}
files: ${{ steps.firmware.outputs.FILE }}
tag_name: ${{ steps.version.outputs.NEW_TAG }}
name: Release ${{ steps.version.outputs.NEW_TAG }}
generate_release_notes: true
make_latest: true
files: |
${{ steps.firmware.outputs.FILE }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-19
View File
@@ -1,19 +0,0 @@
# Changelog
---
### 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.
### 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,8 +36,6 @@ 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 |
---
@@ -64,10 +64,6 @@ typedef enum {
SubGhzCustomEventViewFreqAnalOkLong,
SubGhzCustomEventByteInputDone,
SubGhzCustomEventCarEmulateTransmit,
SubGhzCustomEventCarEmulateStop,
SubGhzCustomEventCarEmulateExit,
} SubGhzCustomEvent;
typedef enum {
@@ -94,7 +94,6 @@ typedef enum {
SubGhzViewIdReadRAW,
SubGhzViewIdPsaDecrypt,
SubGhzViewIdKeeloqDecrypt,
SubGhzViewIdCarEmulate,
} SubGhzViewId;
@@ -0,0 +1,42 @@
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
Filetype: Flipper SubGhz Setting File
Version: 1
# Add Standard frequencies included with firmware and place user frequencies after them
#Add_standard_frequencies: true
# Default Frequency: used as default for "Read" and "Read Raw"
#Default_frequency: 433920000
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
#Frequency: 300000000
#Frequency: 310000000
#Frequency: 320000000
# Frequencies used for hopping mode (keep this list small or flipper will miss signal)
#Hopper_frequency: 300000000
#Hopper_frequency: 310000000
#Hopper_frequency: 310000000
# Custom preset
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
#Custom_preset_name: FM95
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
#2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping
#Custom_preset_name: FM15k
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0
#Custom_preset_name: Pagers
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
#Custom_preset_name: AM_1
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
#Custom_preset_name: AM_2
#Custom_preset_module: CC1101
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
@@ -1,499 +0,0 @@
/**
* Scene: CarEmulate
* Custom automotive-key emulation GUI ported from ProtoPirate.
* Activated when SubGhzLastSettings::custom_car_emulate == true and the
* user presses "Emulate" on a saved dynamic protocol.
*
* Flow:
* SavedMenu → Emulate → (custom_car_emulate?) CarEmulate : Transmitter
*/
#include "../subghz_i.h"
#include "../views/subghz_car_emulate.h"
#include "../helpers/subghz_custom_event.h"
#include <lib/subghz/blocks/generic.h>
#include <notification/notification_messages.h>
#include "../helpers/subghz_txrx_i.h"
#include <lib/subghz/blocks/custom_btn_i.h>
#define TAG "SubGhzSceneCarEmulate"
#define MIN_TX_TICKS 66U /* ~666 ms at 100 ms tick */
/* ── Per-session state (heap, freed on exit) ─────────────────────────────── */
typedef struct {
/* Signal metadata read from fff_data */
char protocol_name[48];
uint32_t serial;
uint8_t original_button;
uint32_t original_counter;
uint32_t current_counter;
uint32_t freq;
char preset_short[12]; /* "AM650", "FM476", … */
/* TX state */
bool is_transmitting;
bool stop_pending; /* stop requested before MIN_TX_TICKS elapsed */
uint32_t tx_start_tick;
/* Pending button key (InputKey) decoded from the packed custom event */
uint8_t pending_button;
} CarEmulateState;
static CarEmulateState* s_state = NULL;
/* ═══════════════════════════════════════════════════════════════════════════
* Button mapping (protocol-name → InputKey → button byte)
* Ported verbatim from protopirate_scene_emulate.c
* ═════════════════════════════════════════════════════════════════════════*/
//static uint8_t car_emulate_map_button(
// const char* protocol,
// InputKey key,
// uint8_t original) {
/* Land Rover V0 */
// if(strstr(protocol, "Land Rover")) {
// switch(key) {
// case InputKeyUp: return 0x02; /* Lock */
// case InputKeyOk: return 0x04; /* Unlock */
// default: return original;
// }
// }
/* Mazda */
// if(strstr(protocol, "Mazda")) {
// switch(key) {
// case InputKeyUp: return 0x01;
// case InputKeyOk: return 0x02;
// case InputKeyDown: return 0x04;
// case InputKeyRight: return 0x08;
// default: return original;
// }
// }
/* PSA */
// if(strstr(protocol, "PSA")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x4;
// case InputKeyLeft: return 0x8;
// default: return original;
// }
// }
/* VAG */
// if(strstr(protocol, "VAG")) {
// if(original == 0x10 || original == 0x20 || original == 0x40) {
// switch(key) {
// case InputKeyUp: return 0x20;
// case InputKeyOk: return 0x10;
// case InputKeyDown: return 0x40;
// default: return original;
// }
// }
// switch(key) {
// case InputKeyUp: return 0x2;
// case InputKeyOk: return 0x1;
// case InputKeyDown: return 0x4;
// case InputKeyLeft: return 0x8;
// case InputKeyRight: return 0x3;
// default: return original;
// }
// }
/* Honda Static */
// if(strstr(protocol, "Honda Static")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x4;
// case InputKeyRight: return 0x5;
// case InputKeyLeft: return 0x8;
// default: return original;
// }
// }
/* Ford */
// if(strstr(protocol, "Ford")) {
// switch(key) {
// case InputKeyLeft: return 0x1;
// case InputKeyUp: return 0x2;
// case InputKeyOk: return 0x4;
// case InputKeyDown: return 0x8;
// case InputKeyRight: return 0x10;
// default: return original;
// }
// }
/* Chrysler */
// if(strstr(protocol, "Chrysler")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// default: return original;
// }
// }
/* Subaru */
// if(strstr(protocol, "Subaru")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x3;
// case InputKeyLeft: return 0x4;
// case InputKeyRight: return 0x8;
// default: return original;
// }
// }
/* Fiat V1 */
// if(strstr(protocol, "Fiat V1")) {
// switch(key) {
// case InputKeyUp: return 0x8;
// case InputKeyOk: return 0x0;
// case InputKeyDown: return 0xD;
// default: return original;
// }
// }
/* Generic KeeLoq / KIA etc. simple 4-button layout */
// if(strstr(protocol, "Kia") || strstr(protocol, "KIA") ||
// strstr(protocol, "KeeLoq") || strstr(protocol, "Keeloq")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x3;
// case InputKeyLeft: return 0x4;
// case InputKeyRight: return 0x8;
// default: return original;
// }
// }
// return original;
//}
/* ═══════════════════════════════════════════════════════════════════════════
* TX helpers
* ═════════════════════════════════════════════════════════════════════════*/
/**
* Read frequency and short preset name from fff_data.
* Falls back to 433.92 MHz / "AM650" on failure.
*/
static void car_emulate_read_freq_preset(SubGhz* subghz, CarEmulateState* st) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
st->freq = 433920000UL;
strncpy(st->preset_short, "AM650", sizeof(st->preset_short) - 1);
if(!fff) return;
uint32_t freq = 0;
flipper_format_rewind(fff);
if(flipper_format_read_uint32(fff, "Frequency", &freq, 1) && freq > 0) {
st->freq = freq;
}
FuriString* preset_str = furi_string_alloc();
flipper_format_rewind(fff);
if(flipper_format_read_string(fff, "Preset", preset_str)) {
/* Convert long FuriHal name → short token used by the setting */
const char* raw = furi_string_get_cstr(preset_str);
const char* short_name = "AM650";
if(strstr(raw, "Ook270")) short_name = "AM270";
else if(strstr(raw, "Ook650")) short_name = "AM650";
else if(strstr(raw, "238")) short_name = "FM238";
else if(strstr(raw, "12K")) short_name = "FM12K";
else if(strstr(raw, "476")) short_name = "FM476";
else if(strstr(raw, "Custom")) short_name = "CUST";
strncpy(st->preset_short, short_name, sizeof(st->preset_short) - 1);
}
furi_string_free(preset_str);
}
/** Update Btn and Cnt fields in fff_data so the transmitter re-serialises them. */
static void car_emulate_apply_button(SubGhz* subghz, InputKey key) {
UNUSED(subghz);
uint8_t custom_btn_id;
switch(key) {
case InputKeyUp: custom_btn_id = SUBGHZ_CUSTOM_BTN_UP; break;
case InputKeyDown: custom_btn_id = SUBGHZ_CUSTOM_BTN_DOWN; break;
case InputKeyLeft: custom_btn_id = SUBGHZ_CUSTOM_BTN_LEFT; break;
case InputKeyRight: custom_btn_id = SUBGHZ_CUSTOM_BTN_RIGHT; break;
case InputKeyOk:
default: custom_btn_id = SUBGHZ_CUSTOM_BTN_OK; break;
}
subghz_custom_btn_set(custom_btn_id);
}
/** Update Cnt in fff_data (Btn is handled by the protocol via custom_btn). */
static void car_emulate_update_fff(SubGhz* subghz, uint32_t counter) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
if(!fff) return;
flipper_format_rewind(fff);
flipper_format_insert_or_update_uint32(fff, "Cnt", &counter, 1);
}
/** Apply tx_power to the current preset and start a single transmission burst. */
static bool car_emulate_start_tx(SubGhz* subghz, uint8_t custom_btn_id) {
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
if(preset.data && preset.data_size > 0 && subghz->tx_power > 0) {
subghz_txrx_set_tx_power(preset.data, preset.data_size, subghz->tx_power);
FURI_LOG_I(TAG, "TX power index applied: %u", subghz->tx_power);
}
subghz_custom_btn_set(custom_btn_id);
bool ok = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
if(ok) {
subghz->state_notifications = SubGhzNotificationStateTx;
notification_message(subghz->notifications, &sequence_blink_magenta_10);
FURI_LOG_I(TAG, "TX started");
} else {
FURI_LOG_E(TAG, "subghz_tx_start failed");
}
return ok;
}
/** Stop an active transmission. */
static void car_emulate_stop_tx(SubGhz* subghz) {
subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE;
notification_message(subghz->notifications, &sequence_blink_stop);
FURI_LOG_I(TAG, "TX stopped");
}
/* ═══════════════════════════════════════════════════════════════════════════
* View callback (fired from the View's input handler)
* ═════════════════════════════════════════════════════════════════════════*/
static void subghz_scene_car_emulate_view_callback(uint32_t event, void* context) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Helpers to keep the view in sync
* ═════════════════════════════════════════════════════════════════════════*/
static void car_emulate_refresh_view(SubGhz* subghz) {
furi_assert(s_state);
subghz_car_emulate_view_set_data(
subghz->car_emulate_view,
s_state->protocol_name,
s_state->serial,
s_state->current_counter,
s_state->original_counter,
s_state->freq,
s_state->preset_short,
s_state->is_transmitting);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Scene on_enter
* ═════════════════════════════════════════════════════════════════════════*/
void subghz_scene_car_emulate_on_enter(void* context) {
SubGhz* subghz = context;
furi_assert(subghz);
/* Allocate per-session state */
s_state = malloc(sizeof(CarEmulateState));
furi_check(s_state);
memset(s_state, 0, sizeof(CarEmulateState));
/* ── Read metadata from the loaded fff_data ── */
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
if(fff) {
FuriString* tmp = furi_string_alloc();
flipper_format_rewind(fff);
if(flipper_format_read_string(fff, "Protocol", tmp)) {
strncpy(
s_state->protocol_name,
furi_string_get_cstr(tmp),
sizeof(s_state->protocol_name) - 1);
}
flipper_format_rewind(fff);
flipper_format_read_uint32(fff, "Serial", &s_state->serial, 1);
flipper_format_rewind(fff);
uint32_t btn_tmp = 0;
if(flipper_format_read_uint32(fff, "Btn", &btn_tmp, 1)) {
s_state->original_button = (uint8_t)btn_tmp;
}
flipper_format_rewind(fff);
flipper_format_read_uint32(fff, "Cnt", &s_state->original_counter, 1);
s_state->current_counter = s_state->original_counter;
furi_string_free(tmp);
}
/* ── Initialize the custom_btn system ──────────────────────────────────
* Reset first so any leftover state from a previous session is cleared.
* Then deserialize the decoder once: this causes the protocol's own
* deserialize() to call subghz_custom_btn_set_original() and
* subghz_custom_btn_set_max(), which is exactly what the standard
* Transmitter scene does via subghz_scene_transmitter_update_data_show().
* After this call:
* - subghz_custom_btn_get_original() → the button that was in the file
* - subghz_custom_btn_is_allowed() → true if protocol supports it
* - subghz_custom_btn_get_max() → number of buttons available */
subghz_custom_btns_reset();
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
if(decoder && fff) {
flipper_format_rewind(fff);
subghz_protocol_decoder_base_deserialize(decoder, fff);
/* Rewind again so subsequent reads in car_emulate_read_freq_preset()
* start from the beginning of the file. */
flipper_format_rewind(fff);
}
subghz_car_emulate_view_set_labels(
subghz->car_emulate_view,
"UNLOCK", /* OK */
"LOCK", /* Up */
"TRUNK", /* Down */
"PANIC", /* Left */
"START" /* Right */
);
car_emulate_read_freq_preset(subghz, s_state);
/* ── Configure the view ── */
subghz_car_emulate_view_set_callback(
subghz->car_emulate_view, subghz_scene_car_emulate_view_callback, subghz);
car_emulate_refresh_view(subghz);
subghz->state_notifications = SubGhzNotificationStateIDLE;
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Scene on_event
* ═════════════════════════════════════════════════════════════════════════*/
bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
furi_assert(s_state);
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
/* ── Transmit ── */
if((event.event & 0xFFFFU) == SubGhzCustomEventCarEmulateTransmit) {
InputKey key = (InputKey)((event.event >> 16) & 0xFFU);
/* Stop any ongoing TX first */
if(subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
}
/* Bump counter */
s_state->current_counter++;
/* Set the custom button BEFORE deserialize() is called inside
* subghz_tx_start() → subghz_txrx_tx_start().
* The protocol's deserialize() will call subghz_custom_btn_get()
* to pick the right button code. */
car_emulate_apply_button(subghz, key);
/* Only update the counter in fff_data; the protocol handles Btn. */
car_emulate_update_fff(subghz, s_state->current_counter);
s_state->is_transmitting = true;
s_state->stop_pending = false;
s_state->tx_start_tick = (uint32_t)furi_get_tick();
uint8_t cur_btn = subghz_custom_btn_get();
if(!car_emulate_start_tx(subghz, cur_btn)) {
s_state->is_transmitting = false;
notification_message(subghz->notifications, &sequence_error);
}
car_emulate_refresh_view(subghz);
consumed = true;
/* ── Stop ── */
} else if(event.event == SubGhzCustomEventCarEmulateStop) {
if(s_state->is_transmitting &&
subghz->state_notifications == SubGhzNotificationStateTx) {
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
if(elapsed >= MIN_TX_TICKS) {
car_emulate_stop_tx(subghz);
s_state->is_transmitting = false;
s_state->stop_pending = false;
} else {
s_state->stop_pending = true;
}
}
car_emulate_refresh_view(subghz);
consumed = true;
/* ── Exit ── */
} else if(event.event == SubGhzCustomEventCarEmulateExit) {
if(subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
}
scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneSavedMenu);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
if(s_state->is_transmitting &&
subghz->state_notifications == SubGhzNotificationStateTx) {
/* Check if hardware is done */
if(subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
subghz->state_notifications = SubGhzNotificationStateIDLE;
subghz_txrx_stop(subghz->txrx);
if(s_state->stop_pending) {
s_state->is_transmitting = false;
s_state->stop_pending = false;
notification_message(subghz->notifications, &sequence_blink_stop);
}
} else {
/* Still transmitting blink LED */
notification_message(subghz->notifications, &sequence_blink_magenta_10);
}
/* Enforce MIN_TX_TICKS stop gate */
if(s_state->stop_pending) {
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
if(elapsed >= MIN_TX_TICKS) {
car_emulate_stop_tx(subghz);
s_state->is_transmitting = false;
s_state->stop_pending = false;
}
}
}
/* Refresh view every tick for animation */
car_emulate_refresh_view(subghz);
consumed = true;
}
return consumed;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Scene on_exit
* ═════════════════════════════════════════════════════════════════════════*/
void subghz_scene_car_emulate_on_exit(void* context) {
SubGhz* subghz = context;
if(subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
}
subghz->state_notifications = SubGhzNotificationStateIDLE;
notification_message(subghz->notifications, &sequence_blink_stop);
/* Clear view callbacks */
subghz_car_emulate_view_set_callback(subghz->car_emulate_view, NULL, NULL);
/* Free per-session state */
if(s_state) {
free(s_state);
s_state = NULL;
}
}
@@ -1,109 +0,0 @@
/**
* Scene: CarEmulateSettings
* Toggle: Custom Emulate Off / On
* Selector: TX Power (reuses the same table as Radio Settings)
* Both settings are persisted in SubGhzLastSettings.
*/
#include "../subghz_i.h"
#include <lib/toolbox/value_index.h>
#define TAG "SubGhzCarEmulateSettings"
/* ── Toggle ──────────────────────────────────────────────────────────────── */
static const char* const toggle_text[] = {"Off", "On"};
static void subghz_scene_car_emulate_settings_toggle_changed(VariableItem* item) {
SubGhz* subghz = variable_item_get_context(item);
furi_assert(subghz);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, toggle_text[index]);
subghz->last_settings->custom_car_emulate = (index == 1);
subghz_last_settings_save(subghz->last_settings);
}
/* ── TX Power ────────────────────────────────────────────────────────────── */
/* Must match the table in subghz_scene_radio_settings.c exactly */
#define CE_TX_POWER_COUNT 9
static const char* const ce_tx_power_text[CE_TX_POWER_COUNT] = {
"Preset", /* index 0 → use whatever the preset has baked in */
"10dBm +",
"7dBm",
"5dBm",
"0dBm",
"-10dBm",
"-15dBm",
"-20dBm",
"-30dBm",
};
static void subghz_scene_car_emulate_settings_power_changed(VariableItem* item) {
SubGhz* subghz = variable_item_get_context(item);
furi_assert(subghz);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, ce_tx_power_text[index]);
/* Mirror the same fields that Radio Settings touches so the value is
* visible everywhere and survives app restart. */
subghz->tx_power = index;
subghz->last_settings->tx_power = index;
subghz_last_settings_save(subghz->last_settings);
/* Patch the live preset buffer immediately so any subsequent TX in this
* session uses the new power without needing a restart. */
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
if(preset.data && preset.data_size > 0) {
subghz_txrx_set_tx_power(preset.data, preset.data_size, index);
}
}
/* ── Scene callbacks ─────────────────────────────────────────────────────── */
void subghz_scene_car_emulate_settings_on_enter(void* context) {
SubGhz* subghz = context;
furi_assert(subghz);
VariableItemList* list = subghz->variable_item_list;
variable_item_list_reset(list);
/* ── Row 1: Custom Emulate toggle ── */
VariableItem* item = variable_item_list_add(
list,
"Custom Emulate",
2,
subghz_scene_car_emulate_settings_toggle_changed,
subghz);
uint8_t toggle_idx = subghz->last_settings->custom_car_emulate ? 1 : 0;
variable_item_set_current_value_index(item, toggle_idx);
variable_item_set_current_value_text(item, toggle_text[toggle_idx]);
/* ── Row 2: TX Power ── */
item = variable_item_list_add(
list,
"TX Power",
CE_TX_POWER_COUNT,
subghz_scene_car_emulate_settings_power_changed,
subghz);
/* Clamp stored value to valid range in case settings file is corrupt */
uint8_t power_idx = subghz->tx_power;
if(power_idx >= CE_TX_POWER_COUNT) power_idx = 0;
variable_item_set_current_value_index(item, power_idx);
variable_item_set_current_value_text(item, ce_tx_power_text[power_idx]);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
}
bool subghz_scene_car_emulate_settings_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void subghz_scene_car_emulate_settings_on_exit(void* context) {
SubGhz* subghz = context;
variable_item_list_reset(subghz->variable_item_list);
}
@@ -34,5 +34,3 @@ ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
ADD_SCENE(subghz, kl_bf_cleanup, KlBfCleanup)
ADD_SCENE(subghz, counter_bf, CounterBf)
ADD_SCENE(subghz, car_emulate, CarEmulate)
ADD_SCENE(subghz, car_emulate_settings, CarEmulateSettings)
@@ -1,135 +1,53 @@
#include "../subghz_i.h"
#include <lib/subghz/subghz_protocol_registry.h>
#define TAG "SubGhzSceneProtocolList"
/* ── helpers ──────────────────────────────────────────────────────────────── */
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;
void subghz_scene_protocol_list_submenu_callback(void* context, uint32_t index) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
}
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);
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) 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" : "---");
if(protocol) {
submenu_add_item(
subghz->submenu,
protocol->name,
i,
subghz_scene_protocol_list_submenu_callback,
subghz);
}
}
variable_item_list_set_selected_item(
list,
submenu_set_selected_item(
subghz->submenu,
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
}
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;
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneProtocolList, event.event);
return true;
}
return consumed;
return false;
}
void subghz_scene_protocol_list_on_exit(void* context) {
SubGhz* subghz = context;
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);
submenu_reset(subghz->submenu);
}
@@ -105,29 +105,6 @@ 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,7 +17,6 @@ enum SubGhzSettingIndex {
SubGhzSettingIndexIgnoreNiceFlorS,
SubGhzSettingIndexDeleteOldSignals,
SubGhzSettingIndexSound,
SubGhzSettingIndexProtoFilter,
SubGhzSettingIndexResetToDefault,
SubGhzSettingIndexLock,
SubGhzSettingIndexRAWThresholdRSSI,
@@ -446,9 +445,7 @@ 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 == SubGhzSettingIndexProtoFilter) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneProtocolList);
} else if(index == SubGhzSettingIndexLock) {
if(index == SubGhzSettingIndexLock) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock);
} else if(index == SubGhzSettingIndexResetToDefault) {
@@ -476,7 +473,6 @@ 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]);
@@ -672,25 +668,6 @@ 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);
@@ -6,8 +6,7 @@ enum SubmenuIndex {
SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexSignalSettings,
SubmenuIndexCounterBf, /* <-- comma was missing here */
SubmenuIndexCarEmulateSettings,
SubmenuIndexCounterBf
};
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
@@ -78,13 +77,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
subghz_scene_saved_menu_submenu_callback,
subghz);
submenu_add_item(
subghz->submenu,
"Custom Emulate Settings",
SubmenuIndexCarEmulateSettings,
subghz_scene_saved_menu_submenu_callback,
subghz);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
submenu_add_item(
subghz->submenu,
@@ -117,22 +109,7 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
if(event.event == SubmenuIndexEmulate) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
bool use_custom = subghz->last_settings->custom_car_emulate;
if(use_custom) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
uint32_t cnt_tmp = 0;
flipper_format_rewind(fff);
if(!flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1)) {
use_custom = false;
}
}
if(use_custom) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulate);
} else {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
}
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
return true;
} else if(event.event == SubmenuIndexPsaDecrypt) {
scene_manager_set_scene_state(
@@ -159,14 +136,6 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCounterBf);
return true;
} else if(event.event == SubmenuIndexCarEmulateSettings) {
/* <-- was outside the if block due to misplaced brace, now fixed */
scene_manager_set_scene_state(
subghz->scene_manager,
SubGhzSceneSavedMenu,
SubmenuIndexCarEmulateSettings);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulateSettings);
return true;
}
}
return false;
-10
View File
@@ -206,12 +206,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
SubGhzViewIdKeeloqDecrypt,
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
subghz->car_emulate_view = subghz_car_emulate_view_alloc();
view_dispatcher_add_view(
subghz->view_dispatcher,
SubGhzViewIdCarEmulate,
subghz_car_emulate_view_get_view(subghz->car_emulate_view));
//init threshold rssi
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
@@ -327,10 +321,6 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
// Custom car-emulate view
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
subghz_car_emulate_view_free(subghz->car_emulate_view);
// Read RAW
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
subghz_read_raw_free(subghz->subghz_read_raw);
-3
View File
@@ -43,8 +43,6 @@
#include "helpers/subghz_txrx.h"
#include "helpers/subghz_keeloq_keys.h"
#include "views/subghz_car_emulate.h"
#define SUBGHZ_MAX_LEN_NAME 64
#define SUBGHZ_EXT_PRESET_NAME true
#define SUBGHZ_RAW_THRESHOLD_MIN (-90.0f)
@@ -78,7 +76,6 @@ struct SubGhz {
SubGhzReadRAW* subghz_read_raw;
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
SubGhzCarEmulateView* car_emulate_view;
bool raw_send_only;
bool save_datetime_set;
@@ -22,8 +22,6 @@
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD "HoppingThreshold"
#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));
@@ -52,7 +50,6 @@ 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);
@@ -166,27 +163,6 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
1)) {
flipper_format_rewind(fff_data_file);
}
if(!flipper_format_read_bool(
fff_data_file,
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
&instance->custom_car_emulate,
1)) {
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 {
@@ -305,19 +281,6 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
file, SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP, &instance->leds_and_amp, 1)) {
break;
}
if(!flipper_format_write_bool(
file,
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
&instance->custom_car_emulate,
1)) {
break;
}
if(!flipper_format_write_string_cstr(
file,
SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER,
instance->protocol_filter)) {
break;
}
saved = true;
} while(0);
@@ -30,8 +30,6 @@ typedef struct {
float preset_hopping_threshold;
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);
@@ -1,264 +0,0 @@
#include "subghz_car_emulate.h"
#include "../helpers/subghz_custom_event.h"
#include <gui/elements.h>
#include <input/input.h>
#include <furi.h>
#define TAG "SubGhzCarEmulateView"
/* ── Model ──────────────────────────────────────────────────────────────── */
typedef struct {
char protocol_name[32];
uint32_t serial;
uint32_t counter;
uint32_t original_counter;
uint32_t freq;
char preset[12];
bool is_transmitting;
uint8_t anim_frame;
char label_ok[12];
char label_up[12];
char label_down[12];
char label_left[12];
char label_right[12];
} SubGhzCarEmulateViewModel;
/* ── Handle ─────────────────────────────────────────────────────────────── */
struct SubGhzCarEmulateView {
View* view;
SubGhzCarEmulateViewCallback callback;
void* context;
};
/* ── Draw ───────────────────────────────────────────────────────────────── */
static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
SubGhzCarEmulateViewModel* m = model_ptr;
m->anim_frame = (m->anim_frame + 1) % 8;
canvas_clear(canvas);
/* Header bar */
canvas_draw_box(canvas, 0, 0, 128, 11);
canvas_invert_color(canvas);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, m->protocol_name);
canvas_invert_color(canvas);
/* Info row 1: serial + counter */
canvas_set_font(canvas, FontSecondary);
char buf[32];
if(m->serial <= 0xFFFFFFUL) {
snprintf(buf, sizeof(buf), "SN:%06lX", (unsigned long)(m->serial & 0xFFFFFFUL));
} else {
snprintf(buf, sizeof(buf), "SN:%08lX", (unsigned long)m->serial);
}
canvas_draw_str(canvas, 2, 20, buf);
snprintf(buf, sizeof(buf), "CNT:%04lX", (unsigned long)m->counter);
canvas_draw_str(canvas, 68, 20, buf);
if(m->counter > m->original_counter) {
snprintf(buf, sizeof(buf), "+%ld", (long)(m->counter - m->original_counter));
canvas_draw_str(canvas, 112, 20, buf);
}
/* Info row 2: frequency + preset */
snprintf(
buf,
sizeof(buf),
"F:%lu.%02lu",
(unsigned long)(m->freq / 1000000UL),
(unsigned long)((m->freq % 1000000UL) / 10000UL));
canvas_draw_str(canvas, 2, 30, buf);
canvas_draw_str(canvas, 95, 30, m->preset);
/* ── Button labels ── */
const uint8_t font_h = canvas_current_font_height(canvas);
/* Centre → UNLOCK (OK button) */
{
const char* lbl = m->label_ok;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 45 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 64, 49, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Up → LOCK */
{
const char* lbl = m->label_up;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 33 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 64, 37, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Left → PANIC */
{
const char* lbl = m->label_left;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 0, 46 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, w / 2, 50, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Right → generic extra */
{
const char* lbl = m->label_right;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 127 - w, 46 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 127 - w / 2, 50, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Down → BOOT */
{
const char* lbl = m->label_down;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 57 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 64, 61, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* TX overlay */
if(m->is_transmitting) {
canvas_draw_rbox(canvas, 24, 18, 80, 18, 3);
canvas_invert_color(canvas);
int wave = m->anim_frame % 3;
canvas_draw_str(canvas, 28 + wave * 2, 25, ")))");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, "TX");
canvas_invert_color(canvas);
}
}
/* ── Input ──────────────────────────────────────────────────────────────── */
static bool subghz_car_emulate_view_input(InputEvent* event, void* context) {
SubGhzCarEmulateView* instance = context;
furi_assert(instance);
if(event->type == InputTypePress) {
if(event->key == InputKeyBack) {
if(instance->callback) {
instance->callback(SubGhzCustomEventCarEmulateExit, instance->context);
}
return true;
}
/* Any directional / OK key → start TX */
if(instance->callback) {
/* Pack the raw InputKey into the upper bits of the event so the
scene can read which button was pressed.
Lower 16 bits = SubGhzCustomEventCarEmulateTransmit marker,
upper 16 bits = InputKey value. */
uint32_t ev = ((uint32_t)event->key << 16) |
(uint32_t)SubGhzCustomEventCarEmulateTransmit;
instance->callback(ev, instance->context);
}
return true;
} else if(event->type == InputTypeRelease) {
if(event->key != InputKeyBack) {
if(instance->callback) {
instance->callback(SubGhzCustomEventCarEmulateStop, instance->context);
}
return true;
}
}
return false;
}
/* ── Alloc / Free ───────────────────────────────────────────────────────── */
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void) {
SubGhzCarEmulateView* instance = malloc(sizeof(SubGhzCarEmulateView));
furi_check(instance);
instance->view = view_alloc();
instance->callback = NULL;
instance->context = NULL;
view_set_context(instance->view, instance);
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzCarEmulateViewModel));
view_set_draw_callback(instance->view, subghz_car_emulate_view_draw);
view_set_input_callback(instance->view, subghz_car_emulate_view_input);
return instance;
}
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance) {
furi_check(instance);
view_free(instance->view);
free(instance);
}
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance) {
furi_check(instance);
return instance->view;
}
void subghz_car_emulate_view_set_callback(
SubGhzCarEmulateView* instance,
SubGhzCarEmulateViewCallback callback,
void* context) {
furi_check(instance);
instance->callback = callback;
instance->context = context;
}
void subghz_car_emulate_view_set_data(
SubGhzCarEmulateView* instance,
const char* protocol_name,
uint32_t serial,
uint32_t counter,
uint32_t original_counter,
uint32_t freq,
const char* preset,
bool is_transmitting) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzCarEmulateViewModel * m,
{
strncpy(m->protocol_name, protocol_name, sizeof(m->protocol_name) - 1);
m->protocol_name[sizeof(m->protocol_name) - 1] = '\0';
m->serial = serial;
m->counter = counter;
m->original_counter = original_counter;
m->freq = freq;
strncpy(m->preset, preset, sizeof(m->preset) - 1);
m->preset[sizeof(m->preset) - 1] = '\0';
m->is_transmitting = is_transmitting;
},
true);
}
void subghz_car_emulate_view_set_labels(
SubGhzCarEmulateView* instance,
const char* ok,
const char* up,
const char* down,
const char* left,
const char* right) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzCarEmulateViewModel * m,
{
strncpy(m->label_ok, ok ? ok : "", sizeof(m->label_ok) - 1);
strncpy(m->label_up, up ? up : "", sizeof(m->label_up) - 1);
strncpy(m->label_down, down ? down : "", sizeof(m->label_down) - 1);
strncpy(m->label_left, left ? left : "", sizeof(m->label_left) - 1);
strncpy(m->label_right, right ? right : "", sizeof(m->label_right) - 1);
},
true);
}
@@ -1,45 +0,0 @@
#pragma once
#include <gui/view.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct SubGhzCarEmulateView SubGhzCarEmulateView;
typedef void (*SubGhzCarEmulateViewCallback)(uint32_t event, void* context);
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void);
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance);
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance);
void subghz_car_emulate_view_set_callback(
SubGhzCarEmulateView* instance,
SubGhzCarEmulateViewCallback callback,
void* context);
/** Update the fields shown on the view.
* All strings are copied internally so the caller can free them after the call.
*/
void subghz_car_emulate_view_set_labels(
SubGhzCarEmulateView* instance,
const char* ok,
const char* up,
const char* down,
const char* left,
const char* right);
void subghz_car_emulate_view_set_data(
SubGhzCarEmulateView* instance,
const char* protocol_name,
uint32_t serial,
uint32_t counter,
uint32_t original_counter,
uint32_t freq,
const char* preset,
bool is_transmitting);
#ifdef __cplusplus
}
#endif
-56
View File
@@ -16,59 +16,3 @@ void subghz_custom_btn_set_max(uint8_t b);
void subghz_custom_btn_set_prog_mode(ProgMode prog_mode);
ProgMode subghz_custom_btn_get_prog_mode(void);
/**
* Helper macro: declare a static button-map table and the two
* conversion functions that every protocol with custom buttons needs.
*
* Usage in your protocol .c file:
*
* SUBGHZ_CUSTOM_BTN_DEFINE_MAP(my_proto,
* {SUBGHZ_CUSTOM_BTN_OK, 0x01}, // OK → Lock
* {SUBGHZ_CUSTOM_BTN_UP, 0x01}, // Up → Lock
* {SUBGHZ_CUSTOM_BTN_DOWN, 0x02}, // Down → Unlock
* {SUBGHZ_CUSTOM_BTN_LEFT, 0x04}, // Left → Boot
* {SUBGHZ_CUSTOM_BTN_RIGHT, 0x08}, // Right → Panic
* )
*
* This generates:
* static uint8_t my_proto_custom_btn_to_code(uint8_t custom_btn);
* static uint8_t my_proto_code_to_custom_btn(uint8_t code);
* static const uint8_t my_proto_custom_btn_max;
*/
typedef struct {
uint8_t custom_btn_id; /* SUBGHZ_CUSTOM_BTN_OK / UP / DOWN / LEFT / RIGHT */
uint8_t protocol_code; /* the actual byte the protocol puts in the frame */
} SubGhzCustomBtnEntry;
#define SUBGHZ_CUSTOM_BTN_DEFINE_MAP(prefix_, ...) \
static const SubGhzCustomBtnEntry prefix_##_btn_map[] = {__VA_ARGS__}; \
static const uint8_t prefix_##_custom_btn_max = \
(sizeof(prefix_##_btn_map) / sizeof(SubGhzCustomBtnEntry)) - 1U; \
\
static uint8_t prefix_##_custom_btn_to_code(uint8_t custom_btn) { \
for(size_t i = 0; i < sizeof(prefix_##_btn_map) / \
sizeof(SubGhzCustomBtnEntry); i++) { \
if(prefix_##_btn_map[i].custom_btn_id == custom_btn) \
return prefix_##_btn_map[i].protocol_code; \
} \
/* fallback: return whatever OK maps to */ \
return prefix_##_btn_map[0].protocol_code; \
} \
\
static uint8_t prefix_##_code_to_custom_btn(uint8_t code) { \
for(size_t i = 0; i < sizeof(prefix_##_btn_map) / \
sizeof(SubGhzCustomBtnEntry); i++) { \
if(prefix_##_btn_map[i].protocol_code == code) \
return prefix_##_btn_map[i].custom_btn_id; \
} \
return SUBGHZ_CUSTOM_BTN_OK; \
} \
\
static void prefix_##_custom_btn_init(uint8_t current_code) { \
uint8_t original = prefix_##_code_to_custom_btn(current_code); \
if(subghz_custom_btn_get_original() == 0) \
subghz_custom_btn_set_original(original); \
subghz_custom_btn_set_max(prefix_##_custom_btn_max); \
}
+252
View File
@@ -0,0 +1,252 @@
#include "aes_common.h"
static const uint8_t aes_sbox[256] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab,
0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4,
0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71,
0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6,
0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb,
0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45,
0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44,
0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a,
0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25,
0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e,
0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1,
0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb,
0x16};
static const uint8_t aes_sbox_inv[256] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7,
0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde,
0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42,
0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c,
0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15,
0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7,
0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc,
0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad,
0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d,
0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8,
0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51,
0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0,
0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c,
0x7d};
static const uint8_t aes_rcon[10] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
static uint8_t gf_mul2(uint8_t x) {
return ((x >> 7) * 0x1b) ^ (x << 1);
}
static void aes_subbytes(uint8_t* state) {
for(uint8_t row = 0; row < 4; row++) {
for(uint8_t col = 0; col < 4; col++) {
state[row + col * 4] = aes_sbox[state[row + col * 4]];
}
}
}
static void aes_subbytes_inv(uint8_t* state) {
for(uint8_t row = 0; row < 4; row++) {
for(uint8_t col = 0; col < 4; col++) {
state[row + col * 4] = aes_sbox_inv[state[row + col * 4]];
}
}
}
static void aes_shiftrows(uint8_t* state) {
uint8_t temp;
temp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = temp;
temp = state[2];
state[2] = state[10];
state[10] = temp;
temp = state[6];
state[6] = state[14];
state[14] = temp;
temp = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = temp;
}
static void aes_shiftrows_inv(uint8_t* state) {
uint8_t temp;
temp = state[13];
state[13] = state[9];
state[9] = state[5];
state[5] = state[1];
state[1] = temp;
temp = state[2];
state[2] = state[10];
state[10] = temp;
temp = state[6];
state[6] = state[14];
state[14] = temp;
temp = state[3];
state[3] = state[7];
state[7] = state[11];
state[11] = state[15];
state[15] = temp;
}
static void aes_mixcolumns(uint8_t* state) {
uint8_t a, b, c, d;
for(uint8_t i = 0; i < 4; i++) {
a = state[i * 4];
b = state[i * 4 + 1];
c = state[i * 4 + 2];
d = state[i * 4 + 3];
uint8_t a2 = gf_mul2(a);
uint8_t b2 = gf_mul2(b);
uint8_t c2 = gf_mul2(c);
uint8_t d2 = gf_mul2(d);
state[i * 4] = a2 ^ b2 ^ b ^ c ^ d;
state[i * 4 + 1] = a ^ b2 ^ c2 ^ c ^ d;
state[i * 4 + 2] = a ^ b ^ c2 ^ d2 ^ d;
state[i * 4 + 3] = a2 ^ a ^ b ^ c ^ d2;
}
}
static void aes_mixcolumns_inv(uint8_t* state) {
uint8_t a, b, c, d;
for(uint8_t i = 0; i < 4; i++) {
a = state[i * 4];
b = state[i * 4 + 1];
c = state[i * 4 + 2];
d = state[i * 4 + 3];
uint8_t a2 = gf_mul2(a);
uint8_t a4 = gf_mul2(a2);
uint8_t a8 = gf_mul2(a4);
uint8_t b2 = gf_mul2(b);
uint8_t b4 = gf_mul2(b2);
uint8_t b8 = gf_mul2(b4);
uint8_t c2 = gf_mul2(c);
uint8_t c4 = gf_mul2(c2);
uint8_t c8 = gf_mul2(c4);
uint8_t d2 = gf_mul2(d);
uint8_t d4 = gf_mul2(d2);
uint8_t d8 = gf_mul2(d4);
state[i * 4] = (a8 ^ a4 ^ a2) ^ (b8 ^ b2 ^ b) ^ (c8 ^ c4 ^ c) ^ (d8 ^ d);
state[i * 4 + 1] = (a8 ^ a) ^ (b8 ^ b4 ^ b2) ^ (c8 ^ c2 ^ c) ^ (d8 ^ d4 ^ d);
state[i * 4 + 2] = (a8 ^ a4 ^ a) ^ (b8 ^ b) ^ (c8 ^ c4 ^ c2) ^ (d8 ^ d2 ^ d);
state[i * 4 + 3] = (a8 ^ a2 ^ a) ^ (b8 ^ b4 ^ b) ^ (c8 ^ c) ^ (d8 ^ d4 ^ d2);
}
}
static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) {
for(uint8_t col = 0; col < 4; col++) {
state[col * 4] ^= round_key[col * 4];
state[col * 4 + 1] ^= round_key[col * 4 + 1];
state[col * 4 + 2] ^= round_key[col * 4 + 2];
state[col * 4 + 3] ^= round_key[col * 4 + 3];
}
}
void aes_key_expansion(const uint8_t* key, uint8_t* round_keys) {
for(uint8_t i = 0; i < 16; i++) {
round_keys[i] = key[i];
}
for(uint8_t i = 4; i < 44; i++) {
uint8_t prev_word_idx = (i - 1) * 4;
uint8_t b0 = round_keys[prev_word_idx];
uint8_t b1 = round_keys[prev_word_idx + 1];
uint8_t b2 = round_keys[prev_word_idx + 2];
uint8_t b3 = round_keys[prev_word_idx + 3];
if((i % 4) == 0) {
uint8_t new_b0 = aes_sbox[b1] ^ aes_rcon[(i / 4) - 1];
uint8_t new_b1 = aes_sbox[b2];
uint8_t new_b2 = aes_sbox[b3];
uint8_t new_b3 = aes_sbox[b0];
b0 = new_b0;
b1 = new_b1;
b2 = new_b2;
b3 = new_b3;
}
uint8_t back_word_idx = (i - 4) * 4;
b0 ^= round_keys[back_word_idx];
b1 ^= round_keys[back_word_idx + 1];
b2 ^= round_keys[back_word_idx + 2];
b3 ^= round_keys[back_word_idx + 3];
uint8_t curr_word_idx = i * 4;
round_keys[curr_word_idx] = b0;
round_keys[curr_word_idx + 1] = b1;
round_keys[curr_word_idx + 2] = b2;
round_keys[curr_word_idx + 3] = b3;
}
}
void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data) {
uint8_t state[16];
memcpy(state, data, 16);
aes_addroundkey(state, &expanded_key[0]);
for(uint8_t round = 1; round < 10; round++) {
aes_subbytes(state);
aes_shiftrows(state);
aes_mixcolumns(state);
aes_addroundkey(state, &expanded_key[round * 16]);
}
aes_subbytes(state);
aes_shiftrows(state);
aes_addroundkey(state, &expanded_key[160]);
memcpy(data, state, 16);
}
void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data) {
uint8_t state[16];
memcpy(state, data, 16);
aes_addroundkey(state, &expanded_key[160]);
for(uint8_t round = 9; round > 0; round--) {
aes_shiftrows_inv(state);
aes_subbytes_inv(state);
aes_addroundkey(state, &expanded_key[round * 16]);
aes_mixcolumns_inv(state);
}
aes_shiftrows_inv(state);
aes_subbytes_inv(state);
aes_addroundkey(state, &expanded_key[0]);
memcpy(data, state, 16);
}
void reverse_bits_in_bytes(uint8_t* data, uint8_t len) {
for(uint8_t i = 0; i < len; i++) {
uint8_t byte = data[i];
uint8_t step1 = ((byte & 0x55) << 1) | ((byte >> 1) & 0x55);
uint8_t step2 = ((step1 & 0x33) << 2) | ((step1 >> 2) & 0x33);
data[i] = ((step2 & 0x0F) << 4) | (step2 >> 4);
}
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "base.h"
#include <furi.h>
void reverse_bits_in_bytes(uint8_t* data, uint8_t len);
void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data);
void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data);
void aes_key_expansion(const uint8_t* key, uint8_t* round_keys);
+376
View File
@@ -0,0 +1,376 @@
/**
* allstar_firefly.c -- Allstar Firefly 318ALD31K native SubGHz protocol
*
* Implements the SubGhzProtocol vtable for the Supertex ED-9 based gate remote.
* Uses subghz_block_generic_serialize / deserialize for standard-format .sub
* files, encoding the 9-position trinary DIP code as a uint64 (base-3, MSB
* first: '+' = 2, '0' = 3, '-' = 0).
*
* Saved file format:
* Filetype: Flipper SubGhz Key File
* Version: 1
* Frequency: 318000000
* Preset: FuriHalSubGhzPresetOok650Async
* Protocol: Allstar Firefly
* Key: 00 00 00 00 00 00 34 B9
* Bit: 18
*
* See allstar_firefly.h for full protocol documentation.
*/
#include "allstar_firefly.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#define TAG "AllstarFirefly"
#define DIP_P 0b11 //(+)
#define DIP_O 0b10 //(0)
#define DIP_N 0b00 //(-)
#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c"
#define SHOW_DIP_P(dip, check_dip) \
((((dip >> 0x10) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_')
static const SubGhzBlockConst subghz_protocol_allstar_firefly_const = {
.te_short = 600,
.te_long = 4000,
.te_delta = 300,
.min_count_bit_for_found = 18,
};
struct SubGhzProtocolDecoderAllstarFirefly {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderAllstarFirefly {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
AllstarFireflyDecoderStepReset = 0,
AllstarFireflyDecoderStepSaveDuration,
AllstarFireflyDecoderStepCheckDuration,
} AllstarFireflyDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder = {
.alloc = subghz_protocol_decoder_allstar_firefly_alloc,
.free = subghz_protocol_decoder_allstar_firefly_free,
.feed = subghz_protocol_decoder_allstar_firefly_feed,
.reset = subghz_protocol_decoder_allstar_firefly_reset,
.get_hash_data = subghz_protocol_decoder_allstar_firefly_get_hash_data,
.serialize = subghz_protocol_decoder_allstar_firefly_serialize,
.deserialize = subghz_protocol_decoder_allstar_firefly_deserialize,
.get_string = subghz_protocol_decoder_allstar_firefly_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder = {
.alloc = subghz_protocol_encoder_allstar_firefly_alloc,
.free = subghz_protocol_encoder_allstar_firefly_free,
.deserialize = subghz_protocol_encoder_allstar_firefly_deserialize,
.stop = subghz_protocol_encoder_allstar_firefly_stop,
.yield = subghz_protocol_encoder_allstar_firefly_yield,
};
const SubGhzProtocol subghz_protocol_allstar_firefly = {
.name = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_allstar_firefly_decoder,
.encoder = &subghz_protocol_allstar_firefly_encoder,
};
void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderAllstarFirefly* instance =
malloc(sizeof(SubGhzProtocolEncoderAllstarFirefly));
instance->base.protocol = &subghz_protocol_allstar_firefly;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 5;
instance->encoder.size_upload = 256;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_allstar_firefly_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderAllstarFirefly* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
*/
static void subghz_protocol_encoder_allstar_firefly_get_upload(
SubGhzProtocolEncoderAllstarFirefly* instance) {
furi_assert(instance);
size_t index = 0;
// Send key and GAP
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_allstar_firefly_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)(subghz_protocol_allstar_firefly_const.te_short * 50) + 400);
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_allstar_firefly_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] = level_duration_make(
true, (uint32_t)subghz_protocol_allstar_firefly_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)(subghz_protocol_allstar_firefly_const.te_short * 50) + 400);
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_allstar_firefly_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderAllstarFirefly* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_allstar_firefly_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_encoder_allstar_firefly_get_upload(instance);
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_allstar_firefly_stop(void* context) {
SubGhzProtocolEncoderAllstarFirefly* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context) {
SubGhzProtocolEncoderAllstarFirefly* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderAllstarFirefly* instance =
malloc(sizeof(SubGhzProtocolDecoderAllstarFirefly));
instance->base.protocol = &subghz_protocol_allstar_firefly;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_allstar_firefly_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAllstarFirefly* instance = context;
free(instance);
}
void subghz_protocol_decoder_allstar_firefly_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAllstarFirefly* instance = context;
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
}
void subghz_protocol_decoder_allstar_firefly_feed(
void* context,
bool level,
volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderAllstarFirefly* instance = context;
// Allstar Firefly Decoder
// 2026 - by @jlaughter
// Remake to match other protocols code base - @xMasterX (MMX)
switch(instance->decoder.parser_step) {
case AllstarFireflyDecoderStepReset:
if((!level) &&
(DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short * 50) <
subghz_protocol_allstar_firefly_const.te_delta * 5)) {
// 30k us
// Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration;
}
break;
case AllstarFireflyDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = AllstarFireflyDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
}
break;
case AllstarFireflyDecoderStepCheckDuration:
if(!level) {
// Bit 1 is long and short timing = 4000us HIGH (te_last) and 600us LOW
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_long) <
subghz_protocol_allstar_firefly_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short) <
subghz_protocol_allstar_firefly_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration;
// Bit 0 is short and long timing = 600us HIGH (te_last) and 4000us LOW
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_short) <
subghz_protocol_allstar_firefly_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_long) <
subghz_protocol_allstar_firefly_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration;
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short * 50) <
subghz_protocol_allstar_firefly_const.te_delta * 5) {
// Found next GAP and add bit 0 or 1
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_long) <
subghz_protocol_allstar_firefly_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}
if((DURATION_DIFF(
instance->decoder.te_last,
subghz_protocol_allstar_firefly_const.te_short) <
subghz_protocol_allstar_firefly_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
// If got 18 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_allstar_firefly_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
} else {
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
}
} else {
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAllstarFirefly* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderAllstarFirefly* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderAllstarFirefly* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_allstar_firefly_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_allstar_firefly_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderAllstarFirefly* instance = context;
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key:0x%05lX Yek:0x%05lX\r\n"
" +: " DIP_PATTERN "\r\n"
" o: " DIP_PATTERN "\r\n"
" -: " DIP_PATTERN "\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0xFFFFF),
(uint32_t)(code_found_reverse & 0xFFFFF),
SHOW_DIP_P(instance->generic.data, DIP_P),
SHOW_DIP_P(instance->generic.data, DIP_O),
SHOW_DIP_P(instance->generic.data, DIP_N));
}
+143
View File
@@ -0,0 +1,143 @@
#pragma once
/**
* allstar_firefly.h - Allstar Firefly 318ALD31K native SubGHz protocol
*
* Registers the Allstar Firefly gate remote as a first-class SubGHz protocol,
* supporting decode from air, save/load of .sub files, and retransmit.
*
* Protocol summary (measured from captured remotes):
* Carrier : 318 MHz OOK (FuriHalSubGhzPresetOok650Async)
* Code type : Static 9-bit trinary (Supertex ED-9 encoder IC)
* Frame : 18 symbols (2 per bit), no separate sync pulse
* Repeats : 20 frames per keypress
* Inter-frame gap: ~30 440 us
*
* Symbol encoding - each bit = two (pulse, gap) pairs:
* '+' ON : H H = [4045us HIGH, 607us LOW] x2
* '-' OFF : L L = [530us HIGH, 4139us LOW] x2
* '0' FLOAT : H L = [4045us HIGH, 607us LOW, 530us HIGH, 4139us LOW]
*
* Save file format:
* Filetype: Flipper SubGhz Key File
* Version: 1
* Frequency: 318000000
* Preset: FuriHalSubGhzPresetOok650Async
* Protocol: Allstar Firefly
* Key: +--000++-
*
* To register with the SubGHz app, in protocol_list.c add:
* #include "allstar_firefly.h"
* &subghz_protocol_allstar_firefly, (in the protocol array)
*/
#include "base.h"
/* Protocol name (must match what is written to .sub files) */
#define SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME "Allstar Firefly"
typedef struct SubGhzProtocolDecoderAllstarFirefly SubGhzProtocolDecoderAllstarFirefly;
typedef struct SubGhzProtocolEncoderAllstarFirefly SubGhzProtocolEncoderAllstarFirefly;
extern const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder;
extern const SubGhzProtocol subghz_protocol_allstar_firefly;
/**
* Allocate SubGhzProtocolEncoderAllstarFirefly.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderAllstarFirefly* pointer to a SubGhzProtocolEncoderAllstarFirefly instance
*/
void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderAllstarFirefly.
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
*/
void subghz_protocol_encoder_allstar_firefly_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize(
void* context,
FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
*/
void subghz_protocol_encoder_allstar_firefly_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderAllstarFirefly.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderAllstarFirefly* pointer to a SubGhzProtocolDecoderAllstarFirefly instance
*/
void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderAllstarFirefly.
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
*/
void subghz_protocol_decoder_allstar_firefly_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderAllstarFirefly.
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
*/
void subghz_protocol_decoder_allstar_firefly_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_allstar_firefly_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderAllstarFirefly.
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderAllstarFirefly.
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize(
void* context,
FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
* @param output Resulting text
*/
void subghz_protocol_decoder_allstar_firefly_get_string(void* context, FuriString* output);
+742
View File
@@ -0,0 +1,742 @@
#include "ditec_gol4.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#define TAG "SubGhzProtocolDitecGOL4"
#define GOL4_RAW_BYTES 7
static const SubGhzBlockConst subghz_protocol_ditec_gol4_const = {
.te_short = 400,
.te_long = 1100,
.te_delta = 200,
.min_count_bit_for_found = 54,
};
struct SubGhzProtocolDecoderDitecGOL4 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderDitecGOL4 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
DitecGOL4DecoderStepReset = 0,
DitecGOL4DecoderStepStartBit,
DitecGOL4DecoderStepSaveDuration,
DitecGOL4DecoderStepCheckDuration,
} DitecGOL4DecoderStep;
const SubGhzProtocolDecoder subghz_protocol_ditec_gol4_decoder = {
.alloc = subghz_protocol_decoder_ditec_gol4_alloc,
.free = subghz_protocol_decoder_ditec_gol4_free,
.feed = subghz_protocol_decoder_ditec_gol4_feed,
.reset = subghz_protocol_decoder_ditec_gol4_reset,
.get_hash_data = subghz_protocol_decoder_ditec_gol4_get_hash_data,
.serialize = subghz_protocol_decoder_ditec_gol4_serialize,
.deserialize = subghz_protocol_decoder_ditec_gol4_deserialize,
.get_string = subghz_protocol_decoder_ditec_gol4_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_ditec_gol4_encoder = {
.alloc = subghz_protocol_encoder_ditec_gol4_alloc,
.free = subghz_protocol_encoder_ditec_gol4_free,
.deserialize = subghz_protocol_encoder_ditec_gol4_deserialize,
.stop = subghz_protocol_encoder_ditec_gol4_stop,
.yield = subghz_protocol_encoder_ditec_gol4_yield,
};
const SubGhzProtocol subghz_protocol_ditec_gol4 = {
.name = SUBGHZ_PROTOCOL_DITEC_GOL4_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_ditec_gol4_decoder,
.encoder = &subghz_protocol_ditec_gol4_encoder,
};
/**
* Defines the button value for the current btn_id
* Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0x0 PROG
* @return Button code
*/
static uint8_t subghz_protocol_ditec_gol4_get_btn_code(void);
static uint8_t gol4_bit_reverse(uint8_t b) {
b &= 0xFF;
uint8_t result = 0;
for(uint8_t i = 0; i < 8; i++) {
result = (uint8_t)((result << 1) | (b & 1));
b >>= 1;
}
return result;
}
static uint8_t gol4_bit_parity(uint8_t b) {
uint8_t p = 0;
for(uint8_t i = 0; i < 8; i++) {
if((b >> i) & 1u) p ^= 1u;
}
return p;
}
static uint8_t gol4_lcg_step(uint8_t seed, uint8_t steps) {
uint8_t x = seed & 0xFF;
steps &= 0xFF;
for(uint8_t i = 0; i < steps; i++) {
x = (uint8_t)((21 * x + 1) & 0xFF);
}
return x;
}
static uint8_t gol4_lcg_inverse(uint8_t target, uint8_t steps) {
steps &= 0xFF;
if(steps == 0) return target & 0xFF;
return gol4_lcg_step(target, (uint8_t)(256 - steps));
}
static void gol4_decode_rotate_and_bitrev(uint8_t* raw) {
uint8_t carry = 0;
for(uint8_t r = 0; r < 3; r++) {
for(uint8_t i = 2; i < 7; i++) {
uint8_t new_carry = raw[i] & 1;
raw[i] = (uint8_t)(((raw[i] >> 1) | (carry << 7)) & 0xFF);
carry = new_carry;
}
}
raw[0] = gol4_bit_reverse(raw[0]);
raw[1] = gol4_bit_reverse(raw[1]);
raw[3] = gol4_bit_reverse(raw[3]);
raw[4] = gol4_bit_reverse(raw[4]);
uint8_t b2 = raw[2] & 0xDF;
b2 = (uint8_t)(((b2 << 4) | (b2 >> 4)) & 0xFF);
b2 = (uint8_t)((~b2) & 0xFF);
raw[2] = gol4_bit_reverse(b2);
raw[5] = gol4_bit_reverse(raw[5]);
raw[6] = gol4_bit_reverse(raw[6]);
}
static bool gol4_decode_lcg_xor(uint8_t* raw) {
if(raw[6] & 0x80) raw[5] ^= 1;
uint8_t out5 = gol4_lcg_inverse(raw[5], 0xFE);
raw[5] = out5;
uint8_t out6 = gol4_lcg_inverse(raw[6], raw[5]);
raw[6] = out6;
raw[5] ^= 0xA7;
raw[6] ^= 0x69;
return true;
}
static bool gol4_rolling_decode(uint8_t* raw) {
gol4_decode_rotate_and_bitrev(raw);
return gol4_decode_lcg_xor(raw);
}
static bool gol4_encode_lcg_xor(uint8_t* raw) {
uint8_t dec5 = (uint8_t)(raw[5] ^ 0xA7);
uint8_t dec6 = (uint8_t)(raw[6] ^ 0x69);
uint8_t enc6 = gol4_lcg_step(dec6, dec5);
uint8_t enc5 = gol4_lcg_step(dec5, 0xFE);
if(enc6 & 0x80) enc5 ^= 1;
raw[5] = enc5;
raw[6] = enc6;
return true;
}
static void gol4_encode_bitrev_and_rotate(uint8_t* raw) {
raw[0] = gol4_bit_reverse(raw[0]);
raw[1] = gol4_bit_reverse(raw[1]);
raw[3] = gol4_bit_reverse(raw[3]);
raw[4] = gol4_bit_reverse(raw[4]);
if(raw[2] == 0x0) {
raw[2] = 0xF0;
}
uint8_t b2 = gol4_bit_reverse(raw[2]);
b2 = (uint8_t)(~b2);
b2 = (uint8_t)(((b2 << 4) | (b2 >> 4)) & 0xFF);
b2 &= 0xDF;
raw[2] = b2;
raw[5] = gol4_bit_reverse(raw[5]);
raw[6] = gol4_bit_reverse(raw[6]);
uint8_t p5 = gol4_bit_parity(raw[5]);
uint8_t p6 = gol4_bit_parity(raw[6]);
uint8_t carry = 0;
for(uint8_t r = 0; r < 3; r++) {
for(int8_t i = 6; i >= 2; i--) {
uint8_t new_carry = (uint8_t)((raw[i] >> 7) & 1);
raw[i] = (uint8_t)(((raw[i] << 1) | carry) & 0xFF);
carry = new_carry;
}
}
raw[6] = (p5 == p6) ? (uint8_t)(raw[6] & 0xFBu) : (uint8_t)(raw[6] | 0x04u);
}
static bool gol4_rolling_encode(uint8_t* raw) {
if(!raw) return false;
if(!gol4_encode_lcg_xor(raw)) return false;
gol4_encode_bitrev_and_rotate(raw);
return true;
}
static void bits_to_raw(const uint8_t* bits, uint8_t* raw) {
memset(raw, 0, GOL4_RAW_BYTES);
for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) {
uint8_t byte_idx = i / 8;
uint8_t bit_idx = 7 - (i % 8);
if(bits[i]) raw[byte_idx] |= (1 << bit_idx);
}
}
static void raw_to_bits(const uint8_t* raw, uint8_t* bits) {
for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) {
uint8_t byte_idx = i / 8;
uint8_t bit_idx = 7 - (i % 8);
bits[i] = (uint8_t)((raw[byte_idx] >> bit_idx) & 1);
}
}
static uint64_t bits_to_data(const uint8_t* bits) {
uint64_t data = 0;
for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) {
data = (data << 1) | (uint64_t)(bits[i] & 1);
}
return data;
}
static uint32_t serial_to_display(const uint8_t* s) {
if(!s) return 0;
return (uint32_t)((s[0] << 24) | (s[4] << 16) | (s[1] << 8) | s[3]);
}
void* subghz_protocol_encoder_ditec_gol4_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderDitecGOL4* instance = malloc(sizeof(SubGhzProtocolEncoderDitecGOL4));
instance->base.protocol = &subghz_protocol_ditec_gol4;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 4;
instance->encoder.size_upload = 128; // 110 actual
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_ditec_gol4_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderDitecGOL4* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
*/
static void
subghz_protocol_encoder_ditec_gol4_get_upload(SubGhzProtocolEncoderDitecGOL4* instance) {
furi_assert(instance);
size_t index = 0;
// Send key and GAP between repeats
//Send gap before data
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_long * 22);
// Start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_short * 2);
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_long);
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_short);
}
}
instance->encoder.size_upload = index;
return;
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_ditec_gol4_decode_key(SubGhzBlockGeneric* instance) {
// Ditec GOL4 Decoder
// 2025 - 2026.02 - @xMasterX (MMX) & @zero-mega
//
// RAW Samples
// 0xCCB2F83208122 - btn 1 = 0011001100101100 101111 100000110010000 01000000100100010
//
// Programming mode:
// 0xCCB1F832103B9 - btn 0 = 0011001100101100 011111 100000110010000 10000001110111001
// Regular buttons:
// 0xCCB2F8320ED66 - btn 1 = 0011001100101100 101111 100000110010000 01110110101100110
// 0xCCB37832104A6 - btn 2 = 0011001100101100 110111 100000110010000 10000010010100110
// 0xCCB3B8320DB4E - btn 4 = 0011001100101100 111011 100000110010000 01101101101001110
// 0xCCB3D8320E855 - btn 8 = 0011001100101100 111101 100000110010000 01110100001010101
//
// Regular buttons:
// Decoded array: CC 34 71 83 09 F8 C1
// Decoded array: CC 34 71 83 09 F9 C1
// Decoded array: CC 34 72 83 09 FA C1
// Decoded array: CC 34 74 83 09 FB C1
// Decoded array: CC 34 78 83 09 FC C1
// Programming mode
// Decoded array: CC 34 F0 83 09 FD C1
// Decoded array: CC 34 F0 83 09 FE C1
//
uint8_t bits[subghz_protocol_ditec_gol4_const.min_count_bit_for_found];
uint64_t data = instance->data;
for(int i = subghz_protocol_ditec_gol4_const.min_count_bit_for_found - 1; i >= 0; i--) {
bits[i] = (uint8_t)(data & 1);
data >>= 1;
}
uint8_t decrypted[GOL4_RAW_BYTES];
bits_to_raw(bits, decrypted);
if(gol4_rolling_decode(decrypted)) {
uint8_t temp_serial[5];
memcpy(temp_serial, decrypted, 5);
instance->serial = serial_to_display(temp_serial);
instance->btn = decrypted[2] & 0x0F;
instance->cnt = (uint16_t)((decrypted[5] | (decrypted[6] << 8)) & 0xFFFF);
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->btn);
}
subghz_custom_btn_set_max(4);
}
}
static void subghz_protocol_ditec_gol4_encode_key(SubGhzBlockGeneric* instance) {
// Encoder crypto part:
//
// TODO: Current issue - last bit at original remote sometimes 0 but we encode as 1, or vice versa.
// This does not affect decoding but may have issue on real receiver
//
uint8_t decrypted[GOL4_RAW_BYTES];
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->btn);
}
instance->btn = subghz_protocol_ditec_gol4_get_btn_code();
// override button if we change it with signal settings button editor
if(subghz_block_generic_global_button_override_get(&instance->btn))
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->btn);
// Check for OFEX (overflow experimental) mode
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) {
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(&instance->cnt)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((instance->cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
instance->cnt = 0;
} else {
instance->cnt += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
if((instance->cnt + 0x1) > 0xFFFF) {
instance->cnt = 0;
} else if(instance->cnt >= 0x1 && instance->cnt != 0xFFFE) {
instance->cnt = 0xFFFE;
} else {
instance->cnt++;
}
}
decrypted[0] = (uint8_t)((instance->serial >> 24) & 0xFF);
decrypted[4] = (uint8_t)((instance->serial >> 16) & 0xFF);
decrypted[1] = (uint8_t)((instance->serial >> 8) & 0xFF);
decrypted[3] = (uint8_t)(instance->serial & 0xFF);
decrypted[2] = (uint8_t)(instance->btn & 0x0F);
uint16_t counter = (uint16_t)(instance->cnt & 0xFFFF);
decrypted[5] = (uint8_t)(counter & 0xFF);
decrypted[6] = (uint8_t)((counter >> 8) & 0xFF);
gol4_rolling_encode(decrypted);
uint8_t bits[subghz_protocol_ditec_gol4_const.min_count_bit_for_found];
raw_to_bits(decrypted, bits);
instance->data = bits_to_data(bits);
}
SubGhzProtocolStatus
subghz_protocol_encoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderDitecGOL4* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_ditec_gol4_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional parameter
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_ditec_gol4_decode_key(&instance->generic);
subghz_protocol_ditec_gol4_encode_key(&instance->generic);
subghz_protocol_encoder_ditec_gol4_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to update Key");
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_ditec_gol4_stop(void* context) {
SubGhzProtocolEncoderDitecGOL4* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_ditec_gol4_yield(void* context) {
SubGhzProtocolEncoderDitecGOL4* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_ditec_gol4_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderDitecGOL4* instance = malloc(sizeof(SubGhzProtocolDecoderDitecGOL4));
instance->base.protocol = &subghz_protocol_ditec_gol4;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_ditec_gol4_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDitecGOL4* instance = context;
free(instance);
}
void subghz_protocol_decoder_ditec_gol4_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDitecGOL4* instance = context;
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
}
void subghz_protocol_decoder_ditec_gol4_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderDitecGOL4* instance = context;
switch(instance->decoder.parser_step) {
case DitecGOL4DecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_long * 22) <
(subghz_protocol_ditec_gol4_const.te_long * 4))) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = DitecGOL4DecoderStepStartBit;
}
break;
case DitecGOL4DecoderStepStartBit:
if((level) && (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_short * 2) <
subghz_protocol_ditec_gol4_const.te_delta)) {
instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration;
} else {
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
}
break;
case DitecGOL4DecoderStepSaveDuration:
if(!level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = DitecGOL4DecoderStepCheckDuration;
} else {
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
}
break;
case DitecGOL4DecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_short) <
subghz_protocol_ditec_gol4_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_long) <
subghz_protocol_ditec_gol4_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_long) <
subghz_protocol_ditec_gol4_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_short) <
subghz_protocol_ditec_gol4_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration;
}
} else {
if(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_long * 20) <
(subghz_protocol_ditec_gol4_const.te_long * 3)) {
if(instance->decoder.decode_count_bit ==
subghz_protocol_ditec_gol4_const.min_count_bit_for_found) {
// 54 bits received, save and continue
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit =
subghz_protocol_ditec_gol4_const.min_count_bit_for_found;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
} else {
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
}
}
break;
}
}
uint8_t subghz_protocol_decoder_ditec_gol4_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDitecGOL4* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_ditec_gol4_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderDitecGOL4* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderDitecGOL4* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_ditec_gol4_const.min_count_bit_for_found);
}
bool subghz_protocol_ditec_gol4_create_data(
void* context,
FlipperFormat* flipper_format,
uint32_t serial,
uint8_t btn,
uint16_t cnt,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolEncoderDitecGOL4* instance = context;
instance->generic.btn = btn;
instance->generic.serial = serial;
instance->generic.cnt = cnt;
instance->generic.data_count_bit = subghz_protocol_ditec_gol4_const.min_count_bit_for_found;
subghz_protocol_ditec_gol4_encode_key(&instance->generic);
return SubGhzProtocolStatusOk ==
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
static uint8_t subghz_protocol_ditec_gol4_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0x1:
btn = 0x2;
break;
case 0x2:
btn = 0x1;
break;
case 0x4:
btn = 0x1;
break;
case 0x8:
btn = 0x1;
break;
case 0x0:
btn = 0x1;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0x1:
btn = 0x4;
break;
case 0x2:
btn = 0x4;
break;
case 0x4:
btn = 0x2;
break;
case 0x8:
btn = 0x4;
break;
case 0x0:
btn = 0x4;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
switch(original_btn_code) {
case 0x1:
btn = 0x8;
break;
case 0x2:
btn = 0x8;
break;
case 0x4:
btn = 0x8;
break;
case 0x8:
btn = 0x2;
break;
case 0x0:
btn = 0x2;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) {
switch(original_btn_code) {
case 0x1:
btn = 0x0;
break;
case 0x2:
btn = 0x0;
break;
case 0x4:
btn = 0x0;
break;
case 0x8:
btn = 0x0;
break;
case 0x0:
btn = 0x8;
break;
default:
break;
}
}
return btn;
}
void subghz_protocol_decoder_ditec_gol4_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderDitecGOL4* instance = context;
subghz_protocol_ditec_gol4_decode_key(&instance->generic);
// push protocol data to global variable
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.cnt_length_bit = 16;
subghz_block_generic_global.current_cnt = instance->generic.cnt;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key:0x%0lX%08lX\r\n"
"Serial:0x%08lX\r\n"
"Btn:%01X %s\r\n"
"Cnt:%04lX",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.serial,
instance->generic.btn,
(instance->generic.btn == 0x0) ? "- Prog" : "",
instance->generic.cnt);
}
+109
View File
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_DITEC_GOL4_NAME "Ditec GOL4"
typedef struct SubGhzProtocolDecoderDitecGOL4 SubGhzProtocolDecoderDitecGOL4;
typedef struct SubGhzProtocolEncoderDitecGOL4 SubGhzProtocolEncoderDitecGOL4;
extern const SubGhzProtocolDecoder subghz_protocol_ditec_gol4_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_ditec_gol4_encoder;
extern const SubGhzProtocol subghz_protocol_ditec_gol4;
/**
* Allocate SubGhzProtocolEncoderDitecGOL4.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderDitecGOL4* pointer to a SubGhzProtocolEncoderDitecGOL4 instance
*/
void* subghz_protocol_encoder_ditec_gol4_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderDitecGOL4.
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
*/
void subghz_protocol_encoder_ditec_gol4_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
*/
void subghz_protocol_encoder_ditec_gol4_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_ditec_gol4_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderDitecGOL4.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderDitecGOL4* pointer to a SubGhzProtocolDecoderDitecGOL4 instance
*/
void* subghz_protocol_decoder_ditec_gol4_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderDitecGOL4.
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
*/
void subghz_protocol_decoder_ditec_gol4_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderDitecGOL4.
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
*/
void subghz_protocol_decoder_ditec_gol4_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_ditec_gol4_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_ditec_gol4_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderDitecGOL4.
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_ditec_gol4_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderDitecGOL4.
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
* @param output Resulting text
*/
void subghz_protocol_decoder_ditec_gol4_get_string(void* context, FuriString* output);
+9 -85
View File
@@ -3,7 +3,6 @@
#include <string.h>
#include <lib/toolbox/manchester_decoder.h>
#include <lib/toolbox/manchester_encoder.h>
#include <lib/subghz/blocks/custom_btn_i.h>
#define FORD_V2_TE_SHORT 200U
#define FORD_V2_TE_LONG 400U
@@ -35,16 +34,6 @@
#define FORD_V2_PREAMBLE_COUNT_MAX 0xFFFFU
#define FORD_V2_ENCODER_DEFAULT_REPEAT 10U
SUBGHZ_CUSTOM_BTN_DEFINE_MAP(
ford_v2,
{SUBGHZ_CUSTOM_BTN_OK, 0x11}, /* OK → Unlock */
{SUBGHZ_CUSTOM_BTN_UP, 0x10}, /* Up → Lock */
{SUBGHZ_CUSTOM_BTN_DOWN, 0x13}, /* Down → Trunk */
{SUBGHZ_CUSTOM_BTN_LEFT, 0x14}, /* Left → Panic */
{SUBGHZ_CUSTOM_BTN_RIGHT, 0x15}, /* Right → RemoteStart */
)
static const uint16_t ford_v2_sync_shift16_inv =
(uint16_t)(~(((uint16_t)FORD_V2_SYNC_0 << 8) | (uint16_t)FORD_V2_SYNC_1));
@@ -219,10 +208,6 @@ static bool ford_v2_decoder_commit_frame(SubGhzProtocolDecoderFordV2* instance)
return false;
}
/* Register this protocol's button map with the custom_btn system so the
* standard transmitter view can show UP/DOWN/LEFT/RIGHT cycling. */
ford_v2_custom_btn_init(instance->generic.btn);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
@@ -476,44 +461,26 @@ static SubGhzProtocolStatus ford_v2_encoder_deserialize_read_header(
return SubGhzProtocolStatusOk;
}
static SubGhzProtocolStatus ford_v2_encoder_deserialize_validate_and_pack(
SubGhzProtocolEncoderFordV2* instance) {
static SubGhzProtocolStatus ford_v2_encoder_deserialize_validate_and_pack(SubGhzProtocolEncoderFordV2* instance) {
ford_v2_encoder_rebuild_raw_from_payload(instance);
if(!ford_v2_button_is_valid(instance->raw_bytes[6])) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint16_t cnt = (uint16_t)(
(((uint16_t)(instance->raw_bytes[7] & 0x7FU)) << 9) |
(((uint16_t)instance->raw_bytes[8]) << 1) |
((uint16_t)(instance->raw_bytes[9] >> 7)));
cnt = (cnt + 1U) & 0x7FFFU;
// raw_bytes[7] bits [6:0] = cnt[14:8], bit[7] = parity(btn)
instance->raw_bytes[7] = (instance->raw_bytes[7] & 0x80U) |
(uint8_t)((cnt >> 9) & 0x7FU);
instance->raw_bytes[8] = (uint8_t)((cnt >> 1) & 0xFFU);
// raw_bytes[9] bit[7] = cnt[0], bits[6:0] = tail
instance->raw_bytes[9] = (instance->raw_bytes[9] & 0x7FU) |
(uint8_t)((cnt & 1U) << 7);
ford_v2_encoder_refresh_data_from_raw(instance);
instance->generic.btn = instance->raw_bytes[6];
instance->generic.serial =
((uint32_t)instance->raw_bytes[2] << 24) |
((uint32_t)instance->raw_bytes[3] << 16) |
((uint32_t)instance->raw_bytes[4] << 8) |
(uint32_t)instance->raw_bytes[5];
instance->generic.cnt = cnt;
instance->generic.serial = ((uint32_t)instance->raw_bytes[2] << 24) |
((uint32_t)instance->raw_bytes[3] << 16) |
((uint32_t)instance->raw_bytes[4] << 8) |
(uint32_t)instance->raw_bytes[5];
instance->generic.cnt = (uint16_t)((((uint16_t)(instance->raw_bytes[7] & 0x7FU)) << 9) |
(((uint16_t)instance->raw_bytes[8]) << 1) |
((uint16_t)(instance->raw_bytes[9] >> 7)));
return SubGhzProtocolStatusOk;
}
static void ford_v2_encoder_deserialize_apply_repeat(SubGhzProtocolEncoderFordV2* instance, FlipperFormat* flipper_format) {
flipper_format_rewind(flipper_format);
uint32_t repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
@@ -556,36 +523,13 @@ SubGhzProtocolStatus
FuriString* temp_str = furi_string_alloc();
furi_check(temp_str);
SubGhzProtocolStatus ret =
ford_v2_encoder_deserialize_read_header(instance, flipper_format, temp_str);
SubGhzProtocolStatus ret = ford_v2_encoder_deserialize_read_header(instance, flipper_format, temp_str);
if(ret == SubGhzProtocolStatusOk) {
ret = ford_v2_encoder_deserialize_validate_and_pack(instance);
}
if(ret == SubGhzProtocolStatusOk) {
ford_v2_custom_btn_init(instance->raw_bytes[6]);
uint8_t btn_sel = subghz_custom_btn_get();
if(btn_sel != SUBGHZ_CUSTOM_BTN_OK) {
uint8_t new_code = ford_v2_custom_btn_to_code(btn_sel);
if(ford_v2_button_is_valid(new_code)) {
instance->raw_bytes[6] = new_code;
const uint8_t k7_msb =
(uint8_t)(ford_v2_uint8_parity(new_code) << 7);
instance->raw_bytes[7] =
(instance->raw_bytes[7] & 0x7FU) | k7_msb;
ford_v2_encoder_refresh_data_from_raw(instance);
instance->generic.btn = new_code;
}
}
instance->extra_data = 0;
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
instance->extra_data =
(instance->extra_data << 8) | (uint64_t)instance->raw_bytes[8U + i];
}
ford_v2_encoder_deserialize_apply_repeat(instance, flipper_format);
ford_v2_encoder_build_upload(instance);
instance->encoder.is_running = true;
@@ -595,7 +539,6 @@ SubGhzProtocolStatus
return ret;
}
void subghz_protocol_encoder_ford_v2_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderFordV2* instance = context;
@@ -797,25 +740,6 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_deserialize(
return SubGhzProtocolStatusErrorParserOthers;
}
/* Keep custom_btn in sync when loading from file. */
ford_v2_custom_btn_init(instance->generic.btn);
uint8_t btn_sel = subghz_custom_btn_get();
if(btn_sel != SUBGHZ_CUSTOM_BTN_OK) {
uint8_t new_code = ford_v2_custom_btn_to_code(btn_sel);
if(ford_v2_button_is_valid(new_code)) {
instance->generic.btn = new_code;
instance->raw_bytes[6] = new_code;
instance->raw_bytes[7] = (instance->raw_bytes[7] & 0x7FU) |
(uint8_t)(ford_v2_uint8_parity(new_code) << 7);
instance->generic.data = 0;
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
instance->generic.data =
(instance->generic.data << 8) | (uint64_t)instance->raw_bytes[i];
}
}
}
return ret;
}
-984
View File
@@ -1,984 +0,0 @@
#include "land_rover_v0.h"
#include <string.h>
#define TAG "LandRoverV0"
static const SubGhzBlockConst subghz_protocol_land_rover_v0_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = 81,
};
#define LAND_ROVER_V0_PREAMBLE_PAIRS 319U
#define LAND_ROVER_V0_MIN_PREAMBLE_PAIRS 64U
#define LAND_ROVER_V0_SYNC_US 750U
#define LAND_ROVER_V0_SYNC_DELTA_US 120U
#define LAND_ROVER_V0_UPLOAD_CAPACITY 1024U
#define LAND_ROVER_V0_GAP_US 50000U
#define LAND_ROVER_V0_BTN_UNKNOWN 0x00U
#define LAND_ROVER_V0_BTN_LOCK 0x02U
#define LAND_ROVER_V0_BTN_UNLOCK 0x04U
#define LAND_ROVER_V0_SIG_UNLOCK 0xA285E3UL
#define LAND_ROVER_V0_SIG_LOCK 0xC20363UL
/* Extra FlipperFormat field names specific to this protocol */
#define LAND_ROVER_V0_FF_BTNSIG "BtnSig"
#define LAND_ROVER_V0_FF_CHECK "Check"
#define LAND_ROVER_V0_FF_TAIL "Tail"
#define LAND_ROVER_V0_FF_EXTRA_BIT "ExtraBit"
/* FlipperFormat field name aliases (replacing the external pp library's FF_* macros) */
#define LR_FF_KEY "Key"
#define LR_FF_SERIAL "Serial"
#define LR_FF_BTN "Btn"
#define LR_FF_CNT "Cnt"
/* ── Decoder struct ──────────────────────────────────────────────────────── */
typedef struct SubGhzProtocolDecoderLandRoverV0 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t preamble_count;
uint8_t raw[10];
uint8_t bit_count;
bool extra_bit;
bool previous_bit;
bool boundary_pad_skipped;
bool pending_short;
uint64_t key;
uint16_t tail;
uint32_t command_signature;
uint32_t serial;
uint32_t count;
uint8_t button;
uint8_t check;
bool check_ok;
bool tail_ok;
} SubGhzProtocolDecoderLandRoverV0;
/* ── Encoder struct ──────────────────────────────────────────────────────── */
typedef struct SubGhzProtocolEncoderLandRoverV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint64_t key;
uint16_t tail;
uint32_t command_signature;
uint32_t serial;
uint32_t count;
uint8_t button;
uint8_t check;
} SubGhzProtocolEncoderLandRoverV0;
/* ── Decoder state machine steps ─────────────────────────────────────────── */
typedef enum {
LandRoverV0DecoderStepReset = 0,
LandRoverV0DecoderStepPreambleLow,
LandRoverV0DecoderStepPreambleHigh,
LandRoverV0DecoderStepSyncLow,
LandRoverV0DecoderStepData,
} LandRoverV0DecoderStep;
/* ═══════════════════════════════════════════════════════════════════════════
* Internal helpers replacing pp_* functions from the external app library
* */
/** Write a uint64_t into 8 bytes in big-endian order. */
static inline void lr_u64_to_bytes_be(uint64_t val, uint8_t out[8]) {
for(int i = 7; i >= 0; i--) {
out[i] = (uint8_t)(val & 0xFFU);
val >>= 8;
}
}
/** Read 8 big-endian bytes and return a uint64_t. */
static inline uint64_t lr_bytes_to_u64_be(const uint8_t in[8]) {
uint64_t val = 0;
for(int i = 0; i < 8; i++) {
val = (val << 8) | in[i];
}
return val;
}
/** Returns true when duration matches te_short within te_delta. */
static inline bool lr_is_short(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_land_rover_v0_const.te_short) <
subghz_protocol_land_rover_v0_const.te_delta;
}
/** Returns true when duration matches te_long within te_delta. */
static inline bool lr_is_long(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_land_rover_v0_const.te_long) <
subghz_protocol_land_rover_v0_const.te_delta;
}
/** Insert-or-update a single uint32 field in a FlipperFormat file. */
static void lr_ff_write_u32(FlipperFormat* ff, const char* key, uint32_t val) {
flipper_format_insert_or_update_uint32(ff, key, &val, 1);
}
/** Read a single uint32 field from a FlipperFormat file. */
static bool lr_ff_read_u32(FlipperFormat* ff, const char* key, uint32_t* out) {
return flipper_format_read_uint32(ff, key, out, 1);
}
/**
* Verify that the "Protocol" field in the FlipperFormat file matches the
* expected protocol name. Returns SubGhzProtocolStatusOk on match.
*/
static SubGhzProtocolStatus lr_verify_protocol_name(
FlipperFormat* ff,
const char* expected_name) {
FuriString* name = furi_string_alloc();
bool ok = false;
if(flipper_format_read_string(ff, "Protocol", name)) {
ok = furi_string_equal_str(name, expected_name);
}
furi_string_free(name);
return ok ? SubGhzProtocolStatusOk : SubGhzProtocolStatusErrorProtocolNotFound;
}
/**
* Read the "Repeat" field from a FlipperFormat file.
* Falls back to default_val when the field is absent.
*/
static uint16_t lr_encoder_read_repeat(FlipperFormat* ff, uint16_t default_val) {
uint32_t repeat = default_val;
flipper_format_rewind(ff);
if(!flipper_format_read_uint32(ff, "Repeat", &repeat, 1)) {
repeat = default_val;
}
return (uint16_t)repeat;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Forward declarations for internal (static) helpers
* */
static uint8_t land_rover_v0_button_from_signature(uint32_t signature);
static const char* land_rover_v0_button_name(uint8_t button);
static uint8_t land_rover_v0_calculate_check(uint32_t count);
static bool land_rover_v0_calculate_tail_msb(uint32_t count);
static uint16_t land_rover_v0_calculate_tail(uint32_t count);
static void land_rover_v0_parse_key_fields(
uint64_t key,
uint32_t* signature,
uint32_t* serial,
uint32_t* count,
uint8_t* button,
uint8_t* check);
static bool land_rover_v0_validate_frame(
uint64_t key,
uint16_t tail,
bool extra_bit,
bool* check_ok,
bool* tail_ok);
static bool land_rover_v0_add_decoded_bit(
SubGhzProtocolDecoderLandRoverV0* instance,
bool bit);
static bool land_rover_v0_process_transition(
SubGhzProtocolDecoderLandRoverV0* instance,
bool level,
uint32_t duration);
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance);
static bool land_rover_v0_encoder_add_level(
SubGhzProtocolEncoderLandRoverV0* instance,
size_t* index,
bool level,
uint32_t duration);
static bool land_rover_v0_encoder_add_bit(
SubGhzProtocolEncoderLandRoverV0* instance,
size_t* index,
bool* previous_bit,
bool bit);
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance);
/* ═══════════════════════════════════════════════════════════════════════════
* Protocol descriptor tables
* */
const SubGhzProtocolDecoder subghz_protocol_land_rover_v0_decoder = {
.alloc = subghz_protocol_decoder_land_rover_v0_alloc,
.free = subghz_protocol_decoder_land_rover_v0_free,
.feed = subghz_protocol_decoder_land_rover_v0_feed,
.reset = subghz_protocol_decoder_land_rover_v0_reset,
.get_hash_data = subghz_protocol_decoder_land_rover_v0_get_hash_data,
.serialize = subghz_protocol_decoder_land_rover_v0_serialize,
.deserialize = subghz_protocol_decoder_land_rover_v0_deserialize,
.get_string = subghz_protocol_decoder_land_rover_v0_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
.alloc = subghz_protocol_encoder_land_rover_v0_alloc,
.free = subghz_protocol_encoder_land_rover_v0_free,
.deserialize = subghz_protocol_encoder_land_rover_v0_deserialize,
.stop = subghz_protocol_encoder_land_rover_v0_stop,
.yield = subghz_protocol_encoder_land_rover_v0_yield,
};
const SubGhzProtocol subghz_protocol_land_rover_v0 = {
.name = LAND_ROVER_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_land_rover_v0_decoder,
.encoder = &subghz_protocol_land_rover_v0_encoder,
};
/* ═══════════════════════════════════════════════════════════════════════════
* Protocol logic helpers
* */
static bool land_rover_v0_is_sync(uint32_t duration) {
return DURATION_DIFF(duration, LAND_ROVER_V0_SYNC_US) < LAND_ROVER_V0_SYNC_DELTA_US;
}
static uint8_t land_rover_v0_button_from_signature(uint32_t signature) {
if(signature == LAND_ROVER_V0_SIG_UNLOCK) return LAND_ROVER_V0_BTN_UNLOCK;
if(signature == LAND_ROVER_V0_SIG_LOCK) return LAND_ROVER_V0_BTN_LOCK;
return LAND_ROVER_V0_BTN_UNKNOWN;
}
static const char* land_rover_v0_button_name(uint8_t button) {
switch(button) {
case LAND_ROVER_V0_BTN_LOCK: return "Lock";
case LAND_ROVER_V0_BTN_UNLOCK: return "Unlock";
default: return "Unknown";
}
}
static uint8_t land_rover_v0_calculate_check(uint32_t count) {
const uint8_t c0 =
((count >> 1) ^ (count >> 2) ^ (count >> 3) ^ (count >> 4) ^ (count >> 6)) & 1U;
const uint8_t c1 =
((count >> 0) ^ (count >> 2) ^ (count >> 3) ^ (count >> 4) ^ (count >> 5) ^
(count >> 6) ^ 1U) & 1U;
const uint8_t c2 =
((count >> 1) ^ (count >> 3) ^ (count >> 4) ^ (count >> 5) ^ (count >> 6)) & 1U;
return (uint8_t)(c0 | (c1 << 1) | (c2 << 2));
}
static bool land_rover_v0_calculate_tail_msb(uint32_t count) {
const uint8_t tail =
((count >> 0) ^ (count >> 2) ^ (count >> 4) ^ (count >> 5)) & 1U;
return tail != 0U;
}
static uint16_t land_rover_v0_calculate_tail(uint32_t count) {
return land_rover_v0_calculate_tail_msb(count) ? 0xFFFFU : 0x7FFFU;
}
static void land_rover_v0_parse_key_fields(
uint64_t key,
uint32_t* signature,
uint32_t* serial,
uint32_t* count,
uint8_t* button,
uint8_t* check) {
uint8_t key_bytes[8];
lr_u64_to_bytes_be(key, key_bytes);
const uint32_t sig =
((uint32_t)key_bytes[0] << 16) | ((uint32_t)key_bytes[1] << 8) | key_bytes[2];
const uint32_t sn =
((uint32_t)key_bytes[3] << 16) | ((uint32_t)key_bytes[4] << 8) | key_bytes[5];
const uint32_t cnt =
((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
if(signature) *signature = sig;
if(serial) *serial = sn;
if(count) *count = cnt;
if(button) *button = land_rover_v0_button_from_signature(sig);
if(check) *check = key_bytes[7] & 0x07U;
}
static bool land_rover_v0_validate_frame(
uint64_t key,
uint16_t tail,
bool extra_bit,
bool* check_ok,
bool* tail_ok) {
uint8_t key_bytes[8];
lr_u64_to_bytes_be(key, key_bytes);
const uint32_t count = ((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
const uint8_t expected_check = land_rover_v0_calculate_check(count);
const uint16_t expected_tail = land_rover_v0_calculate_tail(count);
const bool local_check_ok =
((key_bytes[7] & 0x78U) == 0U) && ((key_bytes[7] & 0x07U) == expected_check);
const bool local_tail_ok = (tail == expected_tail) && extra_bit;
if(check_ok) *check_ok = local_check_ok;
if(tail_ok) *tail_ok = local_tail_ok;
return local_check_ok && local_tail_ok;
}
static bool land_rover_v0_add_decoded_bit(
SubGhzProtocolDecoderLandRoverV0* instance,
bool bit) {
if(instance->bit_count < 80U) {
const uint8_t byte_index = instance->bit_count / 8U;
const uint8_t bit_index = 7U - (instance->bit_count % 8U);
if(bit) instance->raw[byte_index] |= (uint8_t)(1U << bit_index);
} else if(instance->bit_count == 80U) {
instance->extra_bit = bit;
} else {
return false;
}
instance->bit_count++;
return true;
}
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance) {
const uint64_t key = lr_bytes_to_u64_be(instance->raw);
const uint16_t tail =
((uint16_t)instance->raw[8] << 8) | instance->raw[9];
if(!land_rover_v0_validate_frame(
key, tail, instance->extra_bit,
&instance->check_ok, &instance->tail_ok)) {
return false;
}
instance->key = key;
instance->tail = tail;
land_rover_v0_parse_key_fields(
key,
&instance->command_signature,
&instance->serial,
&instance->count,
&instance->button,
&instance->check);
instance->generic.data = instance->key;
instance->generic.data_count_bit =
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
return true;
}
static bool land_rover_v0_process_transition(
SubGhzProtocolDecoderLandRoverV0* instance,
bool level,
uint32_t duration) {
if(!instance->boundary_pad_skipped) {
if(level && lr_is_short(duration)) {
instance->boundary_pad_skipped = true;
return true;
}
instance->boundary_pad_skipped = true;
}
if(instance->pending_short) {
if(!instance->previous_bit && !level && lr_is_short(duration)) {
instance->pending_short = false;
return land_rover_v0_add_decoded_bit(instance, false);
} else if(instance->previous_bit && level && lr_is_short(duration)) {
instance->pending_short = false;
return land_rover_v0_add_decoded_bit(instance, true);
}
return false;
}
if(!instance->previous_bit) {
if(level && lr_is_long(duration)) {
instance->previous_bit = true;
return land_rover_v0_add_decoded_bit(instance, true);
} else if(level && lr_is_short(duration)) {
instance->pending_short = true;
return true;
}
return false;
}
if(!level && lr_is_long(duration)) {
instance->previous_bit = false;
return land_rover_v0_add_decoded_bit(instance, false);
} else if(!level && lr_is_short(duration)) {
instance->pending_short = true;
return true;
}
return false;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Encoder waveform helpers
* */
static bool land_rover_v0_encoder_add_level(
SubGhzProtocolEncoderLandRoverV0* instance,
size_t* index,
bool level,
uint32_t duration) {
if(*index >= LAND_ROVER_V0_UPLOAD_CAPACITY) return false;
instance->encoder.upload[(*index)++] = level_duration_make(level, duration);
return true;
}
static bool land_rover_v0_encoder_add_bit(
SubGhzProtocolEncoderLandRoverV0* instance,
size_t* index,
bool* previous_bit,
bool bit) {
const uint32_t te_short = subghz_protocol_land_rover_v0_const.te_short;
const uint32_t te_long = subghz_protocol_land_rover_v0_const.te_long;
if(!*previous_bit && !bit) {
/* 0→0: two short pulses low-high */
if(!land_rover_v0_encoder_add_level(instance, index, true, te_short) ||
!land_rover_v0_encoder_add_level(instance, index, false, te_short))
return false;
} else if(!*previous_bit && bit) {
/* 0→1: one long high */
if(!land_rover_v0_encoder_add_level(instance, index, true, te_long))
return false;
} else if(*previous_bit && !bit) {
/* 1→0: one long low */
if(!land_rover_v0_encoder_add_level(instance, index, false, te_long))
return false;
} else {
/* 1→1: two short pulses high-low */
if(!land_rover_v0_encoder_add_level(instance, index, false, te_short) ||
!land_rover_v0_encoder_add_level(instance, index, true, te_short))
return false;
}
*previous_bit = bit;
return true;
}
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance) {
furi_check(instance);
size_t index = 0;
const uint32_t te_short = subghz_protocol_land_rover_v0_const.te_short;
uint8_t key_bytes[8];
lr_u64_to_bytes_be(instance->key, key_bytes);
/* Preamble: alternating short high/low pairs */
for(uint16_t i = 0; i < LAND_ROVER_V0_PREAMBLE_PAIRS; i++) {
if(!land_rover_v0_encoder_add_level(instance, &index, true, te_short) ||
!land_rover_v0_encoder_add_level(instance, &index, false, te_short))
return false;
}
/* Sync: long high, long low, short high (boundary pulse) */
if(!land_rover_v0_encoder_add_level(instance, &index, true, LAND_ROVER_V0_SYNC_US) ||
!land_rover_v0_encoder_add_level(instance, &index, false, LAND_ROVER_V0_SYNC_US) ||
!land_rover_v0_encoder_add_level(instance, &index, true, te_short))
return false;
/* First encoded bit: always 0, previous state is 1 (the boundary pulse) */
bool previous_bit = true;
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, false))
return false;
/* Data bits 2..63 from the 64-bit key */
for(uint8_t bit_index = 2; bit_index < 64; bit_index++) {
const uint8_t byte_index = bit_index / 8U;
const uint8_t bit_in_byte = 7U - (bit_index % 8U);
const bool bit = (key_bytes[byte_index] >> bit_in_byte) & 1U;
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, bit))
return false;
}
/* 16-bit tail */
instance->tail = land_rover_v0_calculate_tail(instance->count);
for(uint8_t bit_index = 0; bit_index < 16; bit_index++) {
const bool bit = (instance->tail >> (15U - bit_index)) & 1U;
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, bit))
return false;
}
/* Extra bit (always 1) */
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, true))
return false;
/* Inter-frame gap */
if(!land_rover_v0_encoder_add_level(instance, &index, false, LAND_ROVER_V0_GAP_US))
return false;
instance->encoder.front = 0;
instance->encoder.size_upload = index;
return true;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Decoder public API
* */
void* subghz_protocol_decoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderLandRoverV0* instance =
calloc(1, sizeof(SubGhzProtocolDecoderLandRoverV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_land_rover_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_land_rover_v0_free(void* context) {
furi_check(context);
free(context);
}
void subghz_protocol_decoder_land_rover_v0_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.te_last = 0;
instance->preamble_count = 0;
memset(instance->raw, 0, sizeof(instance->raw));
instance->bit_count = 0;
instance->extra_bit = false;
instance->previous_bit = true;
instance->boundary_pad_skipped = false;
instance->pending_short = false;
}
void subghz_protocol_decoder_land_rover_v0_feed(
void* context,
bool level,
uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
switch(instance->decoder.parser_step) {
case LandRoverV0DecoderStepReset:
if(level && lr_is_short(duration)) {
instance->preamble_count = 0;
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
}
break;
case LandRoverV0DecoderStepPreambleLow:
if(!level && lr_is_short(duration)) {
instance->preamble_count++;
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleHigh;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
}
break;
case LandRoverV0DecoderStepPreambleHigh:
if(level && lr_is_short(duration)) {
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
} else if(level && land_rover_v0_is_sync(duration) &&
instance->preamble_count >= LAND_ROVER_V0_MIN_PREAMBLE_PAIRS) {
instance->decoder.parser_step = LandRoverV0DecoderStepSyncLow;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
}
break;
case LandRoverV0DecoderStepSyncLow:
if(!level && land_rover_v0_is_sync(duration)) {
memset(instance->raw, 0, sizeof(instance->raw));
instance->bit_count = 0;
instance->extra_bit = false;
instance->previous_bit = true;
instance->boundary_pad_skipped = false;
instance->pending_short = false;
land_rover_v0_add_decoded_bit(instance, true);
instance->decoder.parser_step = LandRoverV0DecoderStepData;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
}
break;
case LandRoverV0DecoderStepData:
if(!land_rover_v0_process_transition(instance, level, duration)) {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
break;
}
if(instance->bit_count ==
subghz_protocol_land_rover_v0_const.min_count_bit_for_found) {
if(land_rover_v0_finish_frame(instance) && instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
}
break;
}
instance->decoder.te_last = duration;
}
uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->key,
.decode_count_bit = 64,
};
uint8_t hash = subghz_protocol_blocks_get_hash_data(&decoder, 9);
hash ^= (uint8_t)(instance->tail >> 8);
hash ^= (uint8_t)instance->tail;
hash ^= instance->extra_bit ? 1U : 0U;
return hash;
}
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
uint8_t key_bytes[8];
lr_u64_to_bytes_be(instance->key, key_bytes);
flipper_format_rewind(flipper_format);
flipper_format_insert_or_update_hex(
flipper_format, LR_FF_KEY, key_bytes, sizeof(key_bytes));
lr_ff_write_u32(flipper_format, LR_FF_SERIAL, instance->serial);
lr_ff_write_u32(flipper_format, LR_FF_BTN, instance->button);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
lr_ff_write_u32(flipper_format, LR_FF_CNT, instance->count);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT,
instance->extra_bit ? 1U : 0U);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_land_rover_v0_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
uint8_t key_bytes[8] = {0};
bool have_key = false;
flipper_format_rewind(flipper_format);
if(flipper_format_read_hex(
flipper_format, LR_FF_KEY, key_bytes, sizeof(key_bytes))) {
instance->key = lr_bytes_to_u64_be(key_bytes);
have_key = true;
}
if(!have_key) {
instance->key = instance->generic.data;
lr_u64_to_bytes_be(instance->key, key_bytes);
}
uint32_t temp = 0;
flipper_format_rewind(flipper_format);
if(lr_ff_read_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, &temp)) {
instance->tail = (uint16_t)(temp & 0xFFFFU);
} else {
const uint32_t count =
((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
instance->tail = land_rover_v0_calculate_tail(count);
}
flipper_format_rewind(flipper_format);
if(lr_ff_read_u32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, &temp)) {
instance->extra_bit = (temp & 1U) != 0;
} else {
instance->extra_bit = true;
}
land_rover_v0_validate_frame(
instance->key, instance->tail, instance->extra_bit,
&instance->check_ok, &instance->tail_ok);
land_rover_v0_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
&instance->count,
&instance->button,
&instance->check);
instance->generic.data = instance->key;
instance->generic.data_count_bit =
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
}
return ret;
}
void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%06lX Btn:%02X - %s\r\n"
"BtnSig:%06lX\r\n"
"Cnt:%05lX Chk:%02X [%s] Tail:%05lX [%s]\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(unsigned long long)instance->key,
(unsigned long)instance->serial,
instance->button,
land_rover_v0_button_name(instance->button),
(unsigned long)instance->command_signature,
(unsigned long)instance->count,
instance->check,
instance->check_ok ? "OK" : "BAD",
(unsigned long)(((instance->tail >> 15) & 1U) ? 0x1FFFFUL : 0x0FFFFUL),
instance->tail_ok ? "OK" : "BAD");
}
/* ═══════════════════════════════════════════════════════════════════════════
* Encoder helpers
* */
static uint32_t land_rover_v0_signature_from_button(uint8_t button) {
switch(button) {
case LAND_ROVER_V0_BTN_LOCK: return LAND_ROVER_V0_SIG_LOCK;
case LAND_ROVER_V0_BTN_UNLOCK: return LAND_ROVER_V0_SIG_UNLOCK;
default: return 0;
}
}
static uint64_t land_rover_v0_build_key(
uint32_t signature,
uint32_t serial,
uint32_t count) {
uint8_t key_bytes[8] = {0};
key_bytes[0] = (uint8_t)((signature >> 16) & 0xFFU);
key_bytes[1] = (uint8_t)((signature >> 8) & 0xFFU);
key_bytes[2] = (uint8_t)( signature & 0xFFU);
key_bytes[3] = (uint8_t)((serial >> 16) & 0xFFU);
key_bytes[4] = (uint8_t)((serial >> 8) & 0xFFU);
key_bytes[5] = (uint8_t)( serial & 0xFFU);
key_bytes[6] = (uint8_t)((count >> 1) & 0xFFU);
const bool counter_lsb = (count & 1U) != 0;
const uint8_t check = land_rover_v0_calculate_check(count);
key_bytes[7] = (counter_lsb ? 0x80U : 0x00U) | check;
return lr_bytes_to_u64_be(key_bytes);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Encoder public API
* */
void* subghz_protocol_encoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderLandRoverV0* instance =
calloc(1, sizeof(SubGhzProtocolEncoderLandRoverV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_land_rover_v0;
instance->generic.protocol_name = instance->base.protocol->name;
/* Allocate the waveform upload buffer */
instance->encoder.upload =
malloc(LAND_ROVER_V0_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
instance->encoder.repeat = 10;
instance->encoder.size_upload = 0;
instance->encoder.front = 0;
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_land_rover_v0_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderLandRoverV0* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderLandRoverV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = 10;
do {
if(lr_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
SubGhzProtocolStatusOk)
break;
SubGhzProtocolStatus load_status =
subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_land_rover_v0_const.min_count_bit_for_found);
if(load_status != SubGhzProtocolStatusOk) break;
instance->serial = instance->generic.serial & 0xFFFFFFU;
instance->button = instance->generic.btn;
instance->count = instance->generic.cnt & 0x1FFU;
uint8_t key_bytes[8] = {0};
bool have_key = false;
flipper_format_rewind(flipper_format);
if(flipper_format_read_hex(
flipper_format, LR_FF_KEY, key_bytes, sizeof(key_bytes))) {
instance->key = lr_bytes_to_u64_be(key_bytes);
have_key = true;
}
if(have_key) {
land_rover_v0_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
&instance->count,
&instance->button,
&instance->check);
}
uint32_t u32 = 0;
bool have_button = false;
flipper_format_rewind(flipper_format);
if(lr_ff_read_u32(flipper_format, LR_FF_SERIAL, &u32))
instance->serial = u32 & 0xFFFFFFU;
flipper_format_rewind(flipper_format);
if(lr_ff_read_u32(flipper_format, LR_FF_CNT, &u32))
instance->count = u32 & 0x1FFU;
flipper_format_rewind(flipper_format);
if(lr_ff_read_u32(flipper_format, LR_FF_BTN, &u32)) {
instance->button = (uint8_t)u32;
have_button = true;
}
flipper_format_rewind(flipper_format);
if(lr_ff_read_u32(flipper_format, LAND_ROVER_V0_FF_BTNSIG, &u32))
instance->command_signature = u32 & 0xFFFFFFU;
if(have_button) {
const uint32_t sig = land_rover_v0_signature_from_button(instance->button);
if(sig != 0U) instance->command_signature = sig;
}
if(instance->command_signature == 0U) break;
instance->key = land_rover_v0_build_key(
instance->command_signature, instance->serial, instance->count);
lr_u64_to_bytes_be(instance->key, key_bytes);
instance->tail = land_rover_v0_calculate_tail(instance->count);
land_rover_v0_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
&instance->count,
&instance->button,
&instance->check);
instance->generic.data = instance->key;
instance->generic.data_count_bit =
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
flipper_format_rewind(flipper_format);
instance->encoder.repeat = lr_encoder_read_repeat(flipper_format, 10);
if(!land_rover_v0_build_upload(instance) ||
instance->encoder.size_upload == 0U)
break;
/* Update the file with all recalculated fields */
flipper_format_rewind(flipper_format);
flipper_format_insert_or_update_hex(
flipper_format, LR_FF_KEY, key_bytes, sizeof(key_bytes));
lr_ff_write_u32(flipper_format, LR_FF_SERIAL, instance->serial);
lr_ff_write_u32(flipper_format, LR_FF_BTN, instance->button);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
lr_ff_write_u32(flipper_format, LR_FF_CNT, instance->count);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
lr_ff_write_u32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, 1U);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_encoder_land_rover_v0_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderLandRoverV0* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_land_rover_v0_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderLandRoverV0* instance = context;
if(instance->encoder.front >= instance->encoder.size_upload) {
/* One full repetition done; count it down */
if(instance->encoder.repeat > 0) {
instance->encoder.repeat--;
}
instance->encoder.front = 0;
if(instance->encoder.repeat == 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
}
return instance->encoder.upload[instance->encoder.front++];
}
-36
View File
@@ -1,36 +0,0 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#include <lib/toolbox/level_duration.h>
#define LAND_ROVER_PROTOCOL_V0_NAME "Land Rover V0"
extern const SubGhzProtocol subghz_protocol_land_rover_v0;
void* subghz_protocol_decoder_land_rover_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_land_rover_v0_free(void* context);
void subghz_protocol_decoder_land_rover_v0_reset(void* context);
void subghz_protocol_decoder_land_rover_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_land_rover_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_land_rover_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_land_rover_v0_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_land_rover_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_land_rover_v0_stop(void* context);
LevelDuration subghz_protocol_encoder_land_rover_v0_yield(void* context);
+350
View File
@@ -0,0 +1,350 @@
#include "nord_ice.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#define TAG "SubGhzProtocolNord_Ice"
static const SubGhzBlockConst subghz_protocol_nord_ice_const = {
.te_short = 300,
.te_long = 800,
.te_delta = 150,
.min_count_bit_for_found = 33,
};
struct SubGhzProtocolDecoderNord_Ice {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderNord_Ice {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
Nord_IceDecoderStepReset = 0,
Nord_IceDecoderStepSaveDuration,
Nord_IceDecoderStepCheckDuration,
} Nord_IceDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_nord_ice_decoder = {
.alloc = subghz_protocol_decoder_nord_ice_alloc,
.free = subghz_protocol_decoder_nord_ice_free,
.feed = subghz_protocol_decoder_nord_ice_feed,
.reset = subghz_protocol_decoder_nord_ice_reset,
.get_hash_data = subghz_protocol_decoder_nord_ice_get_hash_data,
.serialize = subghz_protocol_decoder_nord_ice_serialize,
.deserialize = subghz_protocol_decoder_nord_ice_deserialize,
.get_string = subghz_protocol_decoder_nord_ice_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_nord_ice_encoder = {
.alloc = subghz_protocol_encoder_nord_ice_alloc,
.free = subghz_protocol_encoder_nord_ice_free,
.deserialize = subghz_protocol_encoder_nord_ice_deserialize,
.stop = subghz_protocol_encoder_nord_ice_stop,
.yield = subghz_protocol_encoder_nord_ice_yield,
};
const SubGhzProtocol subghz_protocol_nord_ice = {
.name = SUBGHZ_PROTOCOL_NORD_ICE_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_nord_ice_decoder,
.encoder = &subghz_protocol_nord_ice_encoder,
};
void* subghz_protocol_encoder_nord_ice_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderNord_Ice* instance = malloc(sizeof(SubGhzProtocolEncoderNord_Ice));
instance->base.protocol = &subghz_protocol_nord_ice;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_nord_ice_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderNord_Ice* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderNord_Ice instance
*/
static void subghz_protocol_encoder_nord_ice_get_upload(SubGhzProtocolEncoderNord_Ice* instance) {
furi_assert(instance);
size_t index = 0;
// Send key and GAP
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_nord_ice_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_nord_ice_const.te_short * 25);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_nord_ice_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_nord_ice_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_nord_ice_const.te_short * 25);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_nord_ice_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_nord_ice_check_remote_controller(SubGhzBlockGeneric* instance) {
instance->serial = (instance->data >> 15) << 9 |
(instance->data & 0x1FF); // 26 bits for serial
instance->btn = (instance->data >> 9) & 0x3F; // 6 bits for button
}
SubGhzProtocolStatus
subghz_protocol_encoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderNord_Ice* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_nord_ice_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_nord_ice_check_remote_controller(&instance->generic);
subghz_protocol_encoder_nord_ice_get_upload(instance);
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_nord_ice_stop(void* context) {
SubGhzProtocolEncoderNord_Ice* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_nord_ice_yield(void* context) {
SubGhzProtocolEncoderNord_Ice* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_nord_ice_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderNord_Ice* instance = malloc(sizeof(SubGhzProtocolDecoderNord_Ice));
instance->base.protocol = &subghz_protocol_nord_ice;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_nord_ice_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderNord_Ice* instance = context;
free(instance);
}
void subghz_protocol_decoder_nord_ice_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderNord_Ice* instance = context;
instance->decoder.parser_step = Nord_IceDecoderStepReset;
}
void subghz_protocol_decoder_nord_ice_feed(void* context, bool level, volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderNord_Ice* instance = context;
// Nord ICE Decoder
// 2026.03 - @xMasterX (MMX)
// Key samples
//
// Serial Btn Serial
// 0x9467688A btn 1 = 10010100011001110 110100 010001010
// 0x9467308A btn 2 = 10010100011001110 011000 010001010
// 0x9467628A btn 3 = 10010100011001110 110001 010001010
// 0x9467648A btn 4 = 10010100011001110 110010 010001010
switch(instance->decoder.parser_step) {
case Nord_IceDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short * 25) <
subghz_protocol_nord_ice_const.te_delta * 11)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration;
}
break;
case Nord_IceDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Nord_IceDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = Nord_IceDecoderStepReset;
}
break;
case Nord_IceDecoderStepCheckDuration:
if(!level) {
// Bit 0 is short and long timing = 300us HIGH (te_last) and 800us LOW
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nord_ice_const.te_short) <
subghz_protocol_nord_ice_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_long) <
subghz_protocol_nord_ice_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration;
// Bit 1 is long and short timing = 800us HIGH (te_last) and 300us LOW
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nord_ice_const.te_long) <
subghz_protocol_nord_ice_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short) <
subghz_protocol_nord_ice_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration;
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short * 25) <
subghz_protocol_nord_ice_const.te_delta * 11) {
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_nord_ice_const.te_short) <
subghz_protocol_nord_ice_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_nord_ice_const.te_long) <
subghz_protocol_nord_ice_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}
// If got 33 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_nord_ice_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = Nord_IceDecoderStepReset;
} else {
instance->decoder.parser_step = Nord_IceDecoderStepReset;
}
} else {
instance->decoder.parser_step = Nord_IceDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_nord_ice_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderNord_Ice* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_nord_ice_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderNord_Ice* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderNord_Ice* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_nord_ice_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_nord_ice_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderNord_Ice* instance = context;
subghz_protocol_nord_ice_check_remote_controller(&instance->generic);
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
// for future use
// // push protocol data to global variable
// subghz_block_generic_global.btn_is_available = false;
// subghz_block_generic_global.current_btn = instance->generic.btn;
// subghz_block_generic_global.btn_length_bit = 4;
// //
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key: 0x%08llX\r\n"
"Yek: 0x%08llX\r\n"
"Serial: 0x%07lX\r\n"
"Btn: %02X",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint64_t)(instance->generic.data & 0xFFFFFFFFF),
(code_found_reverse & 0xFFFFFFFFF),
instance->generic.serial,
instance->generic.btn);
}
+109
View File
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_NORD_ICE_NAME "Nord ICE"
typedef struct SubGhzProtocolDecoderNord_Ice SubGhzProtocolDecoderNord_Ice;
typedef struct SubGhzProtocolEncoderNord_Ice SubGhzProtocolEncoderNord_Ice;
extern const SubGhzProtocolDecoder subghz_protocol_nord_ice_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_nord_ice_encoder;
extern const SubGhzProtocol subghz_protocol_nord_ice;
/**
* Allocate SubGhzProtocolEncoderNord_Ice.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderNord_Ice* pointer to a SubGhzProtocolEncoderNord_Ice instance
*/
void* subghz_protocol_encoder_nord_ice_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderNord_Ice.
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
*/
void subghz_protocol_encoder_nord_ice_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
*/
void subghz_protocol_encoder_nord_ice_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_nord_ice_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderNord_Ice.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderNord_Ice* pointer to a SubGhzProtocolDecoderNord_Ice instance
*/
void* subghz_protocol_decoder_nord_ice_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderNord_Ice.
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
*/
void subghz_protocol_decoder_nord_ice_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderNord_Ice.
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
*/
void subghz_protocol_decoder_nord_ice_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_nord_ice_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_nord_ice_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderNord_Ice.
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_nord_ice_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderNord_Ice.
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
* @param output Resulting text
*/
void subghz_protocol_decoder_nord_ice_get_string(void* context, FuriString* output);
-1
View File
@@ -84,7 +84,6 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&ford_protocol_v1,
&ford_protocol_v2,
&ford_protocol_v3,
&subghz_protocol_land_rover_v0,
};
-1
View File
@@ -85,4 +85,3 @@
#include "ford_v1.h"
#include "ford_v2.h"
#include "ford_v3.h"
#include "land_rover_v0.h"