mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-06-09 23:51:37 +00:00
Compare commits
3 Commits
dev-86f5aae
..
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 5825020a20 | |||
| 91e9a4cbac | |||
| c92b1db8b7 |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -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 }}
|
||||
|
||||
@@ -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.
|
||||
@@ -36,8 +36,6 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| Keeloq Key Manager | Mod Hopping Config |
|
||||
|  |  |
|
||||
| PSA XTEA Decrypt | Counter BruteForce |
|
||||
|  |  |
|
||||
| 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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); \
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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++];
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -85,4 +85,3 @@
|
||||
#include "ford_v1.h"
|
||||
#include "ford_v2.h"
|
||||
#include "ford_v3.h"
|
||||
#include "land_rover_v0.h"
|
||||
|
||||
Reference in New Issue
Block a user