mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-06-10 00:21:38 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 86f5aae002 | |||
| 46f3a5c993 | |||
| 52015fb289 | |||
| 23ba62cd69 | |||
| cd1e9d6945 | |||
| c49b843096 | |||
| 0c35337bb7 | |||
| e419b9865a | |||
| a89cb55529 | |||
| efa653c7cf | |||
| 07957617e5 | |||
| 903104239b | |||
| 291c5320bb | |||
| edbc2f291e |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,48 +1,47 @@
|
||||
name: Build Dev Firmware
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build firmware
|
||||
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
|
||||
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 "FILE=$FILE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Read changelog
|
||||
id: changelog
|
||||
run: |
|
||||
{
|
||||
echo 'CHANGELOG<<EOF'
|
||||
cat CHANGELOG.md
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Create 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 }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
@@ -22,6 +22,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
- [Contribution Policy](#contribution-policy)
|
||||
- [Citations & References](#citations--references)
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [Special Thanks](#special-thanks-to-everyone-who-contributes-to-this-project)
|
||||
|
||||
---
|
||||
|
||||
@@ -35,6 +36,8 @@ 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -352,14 +355,32 @@ IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR
|
||||
|
||||
---
|
||||
|
||||
### Special thanks to everyone who contributes to this project (in alphabetical order):
|
||||
## Special thanks to everyone who contributes to this project:
|
||||
|
||||
- 47LeCoste
|
||||
- D4c1
|
||||
- D4rks1d3
|
||||
- LTX74
|
||||
- Leeroy
|
||||
- lupettohf
|
||||
- MMX
|
||||
- RalphWiggum
|
||||
- zero-mega
|
||||
## Contributors (GitHub)
|
||||
|
||||
<a href="https://github.com/d4c1-labs/Flipper-ARF/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=d4c1-labs/Flipper-ARF"/>
|
||||
</a>
|
||||
|
||||
## Special Thanks
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/whatthefxck">
|
||||
<img src="https://avatars.githubusercontent.com/whatthefxck?s=80" width="80" height="80" alt="whatthefxck"/>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/zero-mega">
|
||||
<img src="https://avatars.githubusercontent.com/zero-mega?s=80" width="80" height="80" alt="zero-mega"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p align="center">
|
||||
Special thanks to everyone who contributed code, testing, reversing,
|
||||
research, ideas, captures and documentation.
|
||||
</p>
|
||||
|
||||
@@ -64,6 +64,10 @@ typedef enum {
|
||||
SubGhzCustomEventViewFreqAnalOkLong,
|
||||
|
||||
SubGhzCustomEventByteInputDone,
|
||||
SubGhzCustomEventCarEmulateTransmit,
|
||||
SubGhzCustomEventCarEmulateStop,
|
||||
SubGhzCustomEventCarEmulateExit,
|
||||
|
||||
} SubGhzCustomEvent;
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -94,6 +94,7 @@ typedef enum {
|
||||
SubGhzViewIdReadRAW,
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
SubGhzViewIdCarEmulate,
|
||||
|
||||
} SubGhzViewId;
|
||||
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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,3 +34,5 @@ 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,53 +1,135 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/subghz_protocol_registry.h>
|
||||
|
||||
void subghz_scene_protocol_list_submenu_callback(void* context, uint32_t index) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
|
||||
}
|
||||
#define TAG "SubGhzSceneProtocolList"
|
||||
|
||||
void subghz_scene_protocol_list_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
/* ── helpers ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
submenu_reset(subghz->submenu);
|
||||
|
||||
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
|
||||
|
||||
char header_str[32];
|
||||
snprintf(header_str, sizeof(header_str), "Protocols: %zu", protocol_count);
|
||||
submenu_set_header(subghz->submenu, header_str);
|
||||
|
||||
for(size_t i = 0; i < protocol_count; i++) {
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
|
||||
if(protocol) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
protocol->name,
|
||||
i,
|
||||
subghz_scene_protocol_list_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
}
|
||||
|
||||
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneProtocolList, event.event);
|
||||
return true;
|
||||
static bool proto_filter_contains(const char* filter, const char* name) {
|
||||
const char* p = filter;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(len == strlen(name) && strncmp(p, name, len) == 0) return true;
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void proto_filter_toggle(char* filter, size_t filter_size, const char* name) {
|
||||
if(proto_filter_contains(filter, name)) {
|
||||
/* remove it */
|
||||
char tmp[256] = {0};
|
||||
const char* p = filter;
|
||||
bool first = true;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(!(len == strlen(name) && strncmp(p, name, len) == 0)) {
|
||||
if(!first) strncat(tmp, ",", sizeof(tmp) - strlen(tmp) - 1);
|
||||
strncat(tmp, p, len < sizeof(tmp) - strlen(tmp) - 1 ? len : 0);
|
||||
first = false;
|
||||
}
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
strncpy(filter, tmp, filter_size - 1);
|
||||
filter[filter_size - 1] = '\0';
|
||||
} else {
|
||||
/* add it */
|
||||
if(filter[0] != '\0') strncat(filter, ",", filter_size - strlen(filter) - 1);
|
||||
strncat(filter, name, filter_size - strlen(filter) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── callbacks ────────────────────────────────────────────────────────────── */
|
||||
|
||||
static void subghz_scene_protocol_list_item_changed(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t value_index = variable_item_get_current_value_index(item);
|
||||
|
||||
uint32_t proto_idx =
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList);
|
||||
size_t selected =
|
||||
variable_item_list_get_selected_item_index(subghz->variable_item_list);
|
||||
UNUSED(proto_idx);
|
||||
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, selected);
|
||||
if(!protocol) return;
|
||||
|
||||
const char* name = protocol->name;
|
||||
|
||||
bool currently_in =
|
||||
proto_filter_contains(subghz->last_settings->protocol_filter, name);
|
||||
|
||||
if((value_index == 1) != currently_in) {
|
||||
proto_filter_toggle(
|
||||
subghz->last_settings->protocol_filter,
|
||||
sizeof(subghz->last_settings->protocol_filter),
|
||||
name);
|
||||
}
|
||||
|
||||
variable_item_set_current_value_text(item, value_index ? "ONLY" : "---");
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
}
|
||||
|
||||
/* ── scene callbacks ──────────────────────────────────────────────────────── */
|
||||
|
||||
void subghz_scene_protocol_list_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
VariableItemList* list = subghz->variable_item_list;
|
||||
variable_item_list_reset(list);
|
||||
|
||||
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
|
||||
|
||||
for(size_t i = 0; i < protocol_count; i++) {
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
|
||||
if(!protocol) continue;
|
||||
|
||||
VariableItem* item = variable_item_list_add(
|
||||
list,
|
||||
protocol->name,
|
||||
2,
|
||||
subghz_scene_protocol_list_item_changed,
|
||||
subghz);
|
||||
|
||||
bool enabled = proto_filter_contains(
|
||||
subghz->last_settings->protocol_filter, protocol->name);
|
||||
variable_item_set_current_value_index(item, enabled ? 1 : 0);
|
||||
variable_item_set_current_value_text(item, enabled ? "ONLY" : "---");
|
||||
}
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
list,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||
}
|
||||
|
||||
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneProtocolList, event.event);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void subghz_scene_protocol_list_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
submenu_reset(subghz->submenu);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager,
|
||||
SubGhzSceneProtocolList,
|
||||
variable_item_list_get_selected_item_index(subghz->variable_item_list));
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
}
|
||||
|
||||
@@ -105,6 +105,29 @@ static void subghz_scene_add_to_history_callback(
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// The check can be moved to /lib/subghz/receiver.c, but may result in false positives
|
||||
/* Protocol name allowlist filter — if non-empty, drop anything not in the list */
|
||||
if(subghz->last_settings->protocol_filter[0] != '\0') {
|
||||
const char* proto_name = decoder_base->protocol->name;
|
||||
const char* filter = subghz->last_settings->protocol_filter;
|
||||
bool allowed = false;
|
||||
/* Walk the comma-separated list */
|
||||
const char* p = filter;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(len == strlen(proto_name) && strncmp(p, proto_name, len) == 0) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
if(!allowed) {
|
||||
FURI_LOG_D(TAG, "%s filtered by allowlist", proto_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if((decoder_base->protocol->flag & subghz->ignore_filter) == 0) {
|
||||
SubGhzHistory* history = subghz->history;
|
||||
FuriString* item_name = furi_string_alloc();
|
||||
|
||||
@@ -17,6 +17,7 @@ enum SubGhzSettingIndex {
|
||||
SubGhzSettingIndexIgnoreNiceFlorS,
|
||||
SubGhzSettingIndexDeleteOldSignals,
|
||||
SubGhzSettingIndexSound,
|
||||
SubGhzSettingIndexProtoFilter,
|
||||
SubGhzSettingIndexResetToDefault,
|
||||
SubGhzSettingIndexLock,
|
||||
SubGhzSettingIndexRAWThresholdRSSI,
|
||||
@@ -445,7 +446,9 @@ static void subghz_scene_receiver_config_set_delete_old_signals(VariableItem* it
|
||||
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
SubGhz* subghz = context;
|
||||
if(index == SubGhzSettingIndexLock) {
|
||||
if(index == SubGhzSettingIndexProtoFilter) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneProtocolList);
|
||||
} else if(index == SubGhzSettingIndexLock) {
|
||||
view_dispatcher_send_custom_event(
|
||||
subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock);
|
||||
} else if(index == SubGhzSettingIndexResetToDefault) {
|
||||
@@ -473,6 +476,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
|
||||
subghz->last_settings->filter = subghz->filter;
|
||||
subghz->last_settings->delete_old_signals = false;
|
||||
subghz->last_settings->tx_power = subghz->tx_power = 0;
|
||||
subghz->last_settings->protocol_filter[0] = '\0';
|
||||
subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[default_index]);
|
||||
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[default_index]);
|
||||
@@ -668,6 +672,25 @@ void subghz_scene_receiver_config_on_enter(void* context) {
|
||||
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
SubGhzCustomEventManagerSet) {
|
||||
/* Protocol filter */
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Proto Filter",
|
||||
1,
|
||||
NULL,
|
||||
subghz);
|
||||
if(subghz->last_settings->protocol_filter[0] == '\0') {
|
||||
variable_item_set_current_value_text(item, "All");
|
||||
} else {
|
||||
uint8_t count = 1;
|
||||
for(const char* p = subghz->last_settings->protocol_filter; *p; p++) {
|
||||
if(*p == ',') count++;
|
||||
}
|
||||
static char filter_count_str[8];
|
||||
snprintf(filter_count_str, sizeof(filter_count_str), "%u set", count);
|
||||
variable_item_set_current_value_text(item, filter_count_str);
|
||||
}
|
||||
|
||||
// Reset to default
|
||||
variable_item_list_add(subghz->variable_item_list, "Reset to default", 1, NULL, NULL);
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ enum SubmenuIndex {
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexSignalSettings,
|
||||
SubmenuIndexCounterBf
|
||||
SubmenuIndexCounterBf, /* <-- comma was missing here */
|
||||
SubmenuIndexCarEmulateSettings,
|
||||
};
|
||||
|
||||
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -77,6 +78,13 @@ 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,
|
||||
@@ -109,7 +117,22 @@ 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);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexPsaDecrypt) {
|
||||
scene_manager_set_scene_state(
|
||||
@@ -136,6 +159,14 @@ 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,6 +206,12 @@ 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();
|
||||
|
||||
@@ -321,6 +327,10 @@ 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,6 +43,8 @@
|
||||
#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)
|
||||
@@ -76,6 +78,7 @@ 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,6 +22,8 @@
|
||||
#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));
|
||||
@@ -50,6 +52,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
instance->enable_preset_hopping = false;
|
||||
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
instance->leds_and_amp = true;
|
||||
instance->protocol_filter[0] = '\0';
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
@@ -163,6 +166,27 @@ 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 {
|
||||
@@ -281,6 +305,19 @@ 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,6 +30,8 @@ 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);
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#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,3 +16,59 @@ 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,568 @@
|
||||
/**
|
||||
* auto_rke_protocols.c
|
||||
* Additional automotive RKE protocols — ported from Pandora DXL 5000 firmware
|
||||
* Target: Flipper Zero
|
||||
*
|
||||
* Protocols included (all found in firmware string table 0x0000ecc4-0x0000ee00):
|
||||
* - Subaru (ID 0x06, 433.92 MHz)
|
||||
* - Hyundai/KiaRIO (ID 0x11, 433.92 MHz)
|
||||
* - Mazda Siemens (ID 0x15, 433.92 MHz)
|
||||
* - VAG -2004 (ID 0x19, 433.92 MHz) [VW/Audi/Seat/Skoda pre-2004]
|
||||
* - SantaFe 13-16 (ID 0x1A, 433.92 MHz) [Hyundai Santa Fe 2013-2016]
|
||||
*
|
||||
* All use OOK AM modulation. Timing constants extracted from firmware
|
||||
* FUN_000007cc (period calculator) and FUN_00000840 (timer init).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
/* =========================================================================
|
||||
* Common helpers
|
||||
* ========================================================================= */
|
||||
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} RawBuf;
|
||||
|
||||
static void raw_push(RawBuf *b, int32_t v)
|
||||
{
|
||||
if (b->count < 512) b->pulses[b->count++] = v;
|
||||
}
|
||||
static void raw_pair(RawBuf *b, uint32_t hi, uint32_t lo)
|
||||
{
|
||||
raw_push(b, (int32_t)hi);
|
||||
raw_push(b, -(int32_t)lo);
|
||||
}
|
||||
static bool in_range(int32_t m, uint32_t ref, uint32_t tol_pct)
|
||||
{
|
||||
int32_t r = (int32_t)ref, d = m - r;
|
||||
if (d < 0) d = -d;
|
||||
return (d * 100) <= (r * (int32_t)tol_pct);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 1. SUBARU RKE (firmware case 0x06, protocol type 6, iVar7=0x18)
|
||||
*
|
||||
* Subaru legacy fob (Impreza/Forester/Legacy ~2000-2010):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 48 (LSB-first)
|
||||
* Layout: [47:16] 32-bit fixed ID [15:8] rolling counter [7:0] button+cksum
|
||||
* PWM : period 800 µs; 1 = 600 µs HI + 200 µs LO; 0 = 200 µs HI + 600 µs LO
|
||||
* Sync : 8000 µs LOW preamble, then 600 µs HI start-bit
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define SUBARU_FREQ_HZ 433920000ul
|
||||
#define SUBARU_BITS 48u
|
||||
#define SUBARU_REPEAT 3u
|
||||
#define SUBARU_SYNC_US 8000u
|
||||
#define SUBARU_TOL_PCT 15u
|
||||
#define SUBARU_BIT1_HI_US 600u
|
||||
#define SUBARU_BIT1_LO_US 200u
|
||||
#define SUBARU_BIT0_HI_US 200u
|
||||
#define SUBARU_BIT0_LO_US 600u
|
||||
|
||||
#define SUBARU_BTN_LOCK 0x1u
|
||||
#define SUBARU_BTN_UNLOCK 0x2u
|
||||
#define SUBARU_BTN_TRUNK 0x4u
|
||||
#define SUBARU_BTN_PANIC 0x8u
|
||||
|
||||
typedef struct {
|
||||
uint32_t fixed_id;
|
||||
uint8_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} SubaruFrame;
|
||||
|
||||
static uint8_t subaru_cksum(uint32_t id, uint8_t ctr, uint8_t btn)
|
||||
{
|
||||
/* Simple nibble-XOR checksum (derived from analysis of firmware data loop) */
|
||||
uint8_t c = 0;
|
||||
for (int i = 0; i < 4; i++) c ^= (id >> (i * 8)) & 0xFFu;
|
||||
c ^= ctr ^ (btn & 0xFu);
|
||||
return c & 0xFFu;
|
||||
}
|
||||
|
||||
void subaru_encode(const SubaruFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t ck = subaru_cksum(f->fixed_id, f->counter, f->button);
|
||||
|
||||
/* Pack 48 bits LSB-first: fixed_id[31:0] | counter[7:0] | (btn<<4)|ck */
|
||||
uint64_t word = (uint64_t)f->fixed_id |
|
||||
((uint64_t)f->counter << 32) |
|
||||
((uint64_t)((f->button << 4) | ck) << 40);
|
||||
|
||||
for (uint32_t rep = 0; rep < SUBARU_REPEAT; rep++) {
|
||||
/* Sync gap then start burst */
|
||||
raw_push(buf, -(int32_t)SUBARU_SYNC_US);
|
||||
raw_pair(buf, SUBARU_BIT1_HI_US, SUBARU_BIT1_LO_US); /* start bit=1 */
|
||||
|
||||
for (uint32_t b = 0; b < SUBARU_BITS; b++) {
|
||||
bool bit = (word >> b) & 1u;
|
||||
raw_pair(buf,
|
||||
bit ? SUBARU_BIT1_HI_US : SUBARU_BIT0_HI_US,
|
||||
bit ? SUBARU_BIT1_LO_US : SUBARU_BIT0_LO_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool subaru_decode(const RawBuf *buf, SubaruFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
/* Sync: long LOW */
|
||||
if (!in_range(-buf->pulses[i], SUBARU_SYNC_US, SUBARU_TOL_PCT)) continue;
|
||||
/* Start bit: long HI */
|
||||
if (i + 1 >= buf->count) continue;
|
||||
if (!in_range(buf->pulses[i + 1], SUBARU_BIT1_HI_US, SUBARU_TOL_PCT)) continue;
|
||||
|
||||
uint32_t j = i + 2; /* skip LOW half of start bit */
|
||||
if (j + 1 >= buf->count) continue;
|
||||
j++; /* skip the LOW portion already in pair */
|
||||
if (j + SUBARU_BITS * 2 > buf->count) continue;
|
||||
|
||||
uint64_t word = 0;
|
||||
bool ok = true;
|
||||
for (uint32_t b = 0; b < SUBARU_BITS; b++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
if (in_range(hi, SUBARU_BIT1_HI_US, SUBARU_TOL_PCT) &&
|
||||
in_range(lo, SUBARU_BIT1_LO_US, SUBARU_TOL_PCT)) {
|
||||
word |= (uint64_t)1 << b;
|
||||
} else if (in_range(hi, SUBARU_BIT0_HI_US, SUBARU_TOL_PCT) &&
|
||||
in_range(lo, SUBARU_BIT0_LO_US, SUBARU_TOL_PCT)) {
|
||||
/* bit 0 */
|
||||
} else { ok = false; break; }
|
||||
}
|
||||
if (!ok) continue;
|
||||
|
||||
frame->fixed_id = (uint32_t)(word & 0xFFFFFFFFu);
|
||||
frame->counter = (uint8_t)((word >> 32) & 0xFFu);
|
||||
frame->button = (uint8_t)((word >> 44) & 0xFu);
|
||||
uint8_t rx_ck = (uint8_t)((word >> 40) & 0xFu);
|
||||
frame->valid = (rx_ck == (subaru_cksum(frame->fixed_id, frame->counter, frame->button) & 0xFu));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 2. HYUNDAI / KIA RIO (firmware case 0x11, iVar7=1, 14-bit display)
|
||||
*
|
||||
* Early Hyundai/Kia fobs (Accent, Rio, Elantra ~2001-2008):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 64 (MSB-first), plain fixed-code (no rolling — very old fobs)
|
||||
* Layout: [63:32] 32-bit serial [31:16] 16-bit button mask repeated
|
||||
* [15:0] ~16-bit checksum (XOR block)
|
||||
* PWM : period 1040 µs; 1 = 728 µs HI + 312 µs LO; 0 = 312 µs HI + 728 µs LO
|
||||
* Sync : 312 µs HI + 10400 µs LO
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define HKR_BITS 64u
|
||||
#define HKR_REPEAT 3u
|
||||
#define HKR_SYNC_HI_US 312u
|
||||
#define HKR_SYNC_LO_US 10400u
|
||||
#define HKR_BIT1_HI_US 728u
|
||||
#define HKR_BIT1_LO_US 312u
|
||||
#define HKR_BIT0_HI_US 312u
|
||||
#define HKR_BIT0_LO_US 728u
|
||||
#define HKR_TOL_PCT 15u
|
||||
#define HKR_GAP_US 10000u
|
||||
|
||||
#define HKR_BTN_LOCK 0x0100u
|
||||
#define HKR_BTN_UNLOCK 0x0200u
|
||||
#define HKR_BTN_TRUNK 0x0400u
|
||||
#define HKR_BTN_PANIC 0x0800u
|
||||
|
||||
typedef struct {
|
||||
uint32_t serial;
|
||||
uint16_t button_mask;
|
||||
bool valid;
|
||||
} HKRFrame;
|
||||
|
||||
static uint16_t hkr_cksum(uint32_t serial, uint16_t btn)
|
||||
{
|
||||
uint16_t c = (uint16_t)(serial ^ (serial >> 16));
|
||||
c ^= btn;
|
||||
return ~c;
|
||||
}
|
||||
|
||||
void hkr_encode(const HKRFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint16_t ck = hkr_cksum(f->serial, f->button_mask);
|
||||
uint64_t word = ((uint64_t)f->serial << 32) |
|
||||
((uint64_t)f->button_mask << 16) |
|
||||
(uint64_t)ck;
|
||||
|
||||
for (uint32_t rep = 0; rep < HKR_REPEAT; rep++) {
|
||||
raw_pair(buf, HKR_SYNC_HI_US, HKR_SYNC_LO_US);
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
bool bit = (word >> b) & 1u;
|
||||
raw_pair(buf,
|
||||
bit ? HKR_BIT1_HI_US : HKR_BIT0_HI_US,
|
||||
bit ? HKR_BIT1_LO_US : HKR_BIT0_LO_US);
|
||||
}
|
||||
raw_push(buf, -(int32_t)HKR_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool hkr_decode(const RawBuf *buf, HKRFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], HKR_SYNC_HI_US, HKR_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], HKR_SYNC_LO_US, HKR_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + HKR_BITS * 2 > buf->count) continue;
|
||||
uint64_t word = 0;
|
||||
bool ok = true;
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
if (in_range(hi, HKR_BIT1_HI_US, HKR_TOL_PCT) && in_range(lo, HKR_BIT1_LO_US, HKR_TOL_PCT)) word |= (uint64_t)1 << b;
|
||||
else if (in_range(hi, HKR_BIT0_HI_US, HKR_TOL_PCT) && in_range(lo, HKR_BIT0_LO_US, HKR_TOL_PCT)) { /* 0 */ }
|
||||
else { ok = false; break; }
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->serial = (uint32_t)(word >> 32);
|
||||
frame->button_mask = (uint16_t)((word >> 16) & 0xFFFFu);
|
||||
uint16_t rx_ck = (uint16_t)(word & 0xFFFFu);
|
||||
frame->valid = (rx_ck == hkr_cksum(frame->serial, frame->button_mask));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 3. MAZDA SIEMENS RKE (firmware case 0x15, iVar7=5, 13-bit display)
|
||||
*
|
||||
* Mazda 3/6/CX-7 with Siemens VDO fob (~2003-2009):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 72 (MSB-first), Siemens rolling code
|
||||
* Layout: [71:40] 32-bit hop (Siemens proprietary cipher)
|
||||
* [39:16] 24-bit serial
|
||||
* [15:8] 8-bit counter (low byte)
|
||||
* [7:4] 4-bit button [3:0] 4-bit checksum
|
||||
* PWM : 1 = 450 µs HI + 1350 µs LO; 0 = 450 µs HI + 450 µs LO
|
||||
* Sync : 450 µs HI + 14400 µs LO
|
||||
* Repeat: 2×
|
||||
* ========================================================================= */
|
||||
|
||||
#define MAZ_BITS 72u
|
||||
#define MAZ_REPEAT 2u
|
||||
#define MAZ_SYNC_HI_US 450u
|
||||
#define MAZ_SYNC_LO_US 14400u
|
||||
#define MAZ_BIT1_HI_US 450u
|
||||
#define MAZ_BIT1_LO_US 1350u
|
||||
#define MAZ_BIT0_HI_US 450u
|
||||
#define MAZ_BIT0_LO_US 450u
|
||||
#define MAZ_GAP_US 20000u
|
||||
#define MAZ_TOL_PCT 15u
|
||||
|
||||
#define MAZ_BTN_LOCK 0x1u
|
||||
#define MAZ_BTN_UNLOCK 0x2u
|
||||
#define MAZ_BTN_TRUNK 0x4u
|
||||
|
||||
typedef struct {
|
||||
uint32_t hop; /* Siemens encrypted hopping word — decrypt separately */
|
||||
uint32_t serial; /* 24-bit */
|
||||
uint8_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} MazdaFrame;
|
||||
|
||||
static uint8_t maz_cksum(uint32_t hop, uint32_t serial, uint8_t ctr, uint8_t btn)
|
||||
{
|
||||
uint8_t c = 0;
|
||||
for (int i = 0; i < 4; i++) c ^= (hop >> (i * 8)) & 0xFFu;
|
||||
for (int i = 0; i < 3; i++) c ^= (serial >> (i * 8)) & 0xFFu;
|
||||
c ^= ctr ^ (btn & 0xFu);
|
||||
return c & 0xFu;
|
||||
}
|
||||
|
||||
void mazda_encode(const MazdaFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t ck = maz_cksum(f->hop, f->serial, f->counter, f->button);
|
||||
/* Pack 72 bits into 9 bytes, MSB of hop is bit 71 */
|
||||
uint8_t pkt[9];
|
||||
pkt[0] = (f->hop >> 24) & 0xFFu;
|
||||
pkt[1] = (f->hop >> 16) & 0xFFu;
|
||||
pkt[2] = (f->hop >> 8) & 0xFFu;
|
||||
pkt[3] = f->hop & 0xFFu;
|
||||
pkt[4] = (f->serial >> 16) & 0xFFu;
|
||||
pkt[5] = (f->serial >> 8) & 0xFFu;
|
||||
pkt[6] = f->serial & 0xFFu;
|
||||
pkt[7] = f->counter;
|
||||
pkt[8] = (uint8_t)((f->button << 4) | ck);
|
||||
|
||||
for (uint32_t rep = 0; rep < MAZ_REPEAT; rep++) {
|
||||
raw_pair(buf, MAZ_SYNC_HI_US, MAZ_SYNC_LO_US);
|
||||
for (int byte = 0; byte < 9; byte++) {
|
||||
for (int bit = 7; bit >= 0; bit--) {
|
||||
bool b = (pkt[byte] >> bit) & 1u;
|
||||
raw_pair(buf,
|
||||
b ? MAZ_BIT1_HI_US : MAZ_BIT0_HI_US,
|
||||
b ? MAZ_BIT1_LO_US : MAZ_BIT0_LO_US);
|
||||
}
|
||||
}
|
||||
raw_push(buf, -(int32_t)MAZ_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool mazda_decode(const RawBuf *buf, MazdaFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], MAZ_SYNC_HI_US, MAZ_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], MAZ_SYNC_LO_US, MAZ_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + MAZ_BITS * 2 > buf->count) continue;
|
||||
uint8_t pkt[9] = {0};
|
||||
bool ok = true;
|
||||
for (uint32_t bt = 0; bt < MAZ_BITS; bt++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
bool b;
|
||||
if (in_range(lo, MAZ_BIT1_LO_US, MAZ_TOL_PCT)) b = true;
|
||||
else if (in_range(lo, MAZ_BIT0_LO_US, MAZ_TOL_PCT)) b = false;
|
||||
else { ok = false; break; }
|
||||
(void)hi;
|
||||
if (b) pkt[bt / 8] |= (uint8_t)(1u << (7 - (bt % 8)));
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->hop = ((uint32_t)pkt[0]<<24)|((uint32_t)pkt[1]<<16)|((uint32_t)pkt[2]<<8)|pkt[3];
|
||||
frame->serial = ((uint32_t)pkt[4]<<16)|((uint32_t)pkt[5]<<8)|pkt[6];
|
||||
frame->counter = pkt[7];
|
||||
frame->button = pkt[8] >> 4;
|
||||
uint8_t rx_ck = pkt[8] & 0xFu;
|
||||
frame->valid = (rx_ck == maz_cksum(frame->hop, frame->serial, frame->counter, frame->button));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 4. VAG -2004 (firmware case 0x19, Princeton-style, ID 0x19)
|
||||
*
|
||||
* VW/Audi/Seat/Skoda fobs before 2004 (ID48 era, 3-button):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 64 (MSB-first), simple rolling code (16-bit counter)
|
||||
* Layout: [63:32] 32-bit fixed transponder ID
|
||||
* [31:16] 16-bit counter
|
||||
* [15:8] 8-bit button+flags
|
||||
* [7:0] 8-bit checksum (sum of all prior bytes mod 256, inverted)
|
||||
* PWM : period 800 µs; 1 = 550 µs HI + 250 µs LO; 0 = 250 µs HI + 550 µs LO
|
||||
* Sync : 550 µs HI + 11000 µs LO
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define VAG_BITS 64u
|
||||
#define VAG_REPEAT 3u
|
||||
#define VAG_SYNC_HI_US 550u
|
||||
#define VAG_SYNC_LO_US 11000u
|
||||
#define VAG_BIT1_HI_US 550u
|
||||
#define VAG_BIT1_LO_US 250u
|
||||
#define VAG_BIT0_HI_US 250u
|
||||
#define VAG_BIT0_LO_US 550u
|
||||
#define VAG_GAP_US 9000u
|
||||
#define VAG_TOL_PCT 15u
|
||||
|
||||
#define VAG_BTN_LOCK 0x01u
|
||||
#define VAG_BTN_UNLOCK 0x02u
|
||||
#define VAG_BTN_TRUNK 0x04u
|
||||
#define VAG_BTN_PANIC 0x08u
|
||||
|
||||
typedef struct {
|
||||
uint32_t transponder_id;
|
||||
uint16_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} VAGFrame;
|
||||
|
||||
static uint8_t vag_cksum(uint32_t tid, uint16_t ctr, uint8_t btn)
|
||||
{
|
||||
uint8_t s = 0;
|
||||
s += (tid >> 24) & 0xFFu;
|
||||
s += (tid >> 16) & 0xFFu;
|
||||
s += (tid >> 8) & 0xFFu;
|
||||
s += tid & 0xFFu;
|
||||
s += (ctr >> 8) & 0xFFu;
|
||||
s += ctr & 0xFFu;
|
||||
s += btn;
|
||||
return (uint8_t)(~s);
|
||||
}
|
||||
|
||||
void vag_encode(const VAGFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t ck = vag_cksum(f->transponder_id, f->counter, f->button);
|
||||
uint64_t word = ((uint64_t)f->transponder_id << 32) |
|
||||
((uint64_t)f->counter << 16) |
|
||||
((uint64_t)f->button << 8) |
|
||||
ck;
|
||||
|
||||
for (uint32_t rep = 0; rep < VAG_REPEAT; rep++) {
|
||||
raw_pair(buf, VAG_SYNC_HI_US, VAG_SYNC_LO_US);
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
bool bit = (word >> b) & 1u;
|
||||
raw_pair(buf,
|
||||
bit ? VAG_BIT1_HI_US : VAG_BIT0_HI_US,
|
||||
bit ? VAG_BIT1_LO_US : VAG_BIT0_LO_US);
|
||||
}
|
||||
raw_push(buf, -(int32_t)VAG_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool vag_decode(const RawBuf *buf, VAGFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], VAG_SYNC_HI_US, VAG_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], VAG_SYNC_LO_US, VAG_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + VAG_BITS * 2 > buf->count) continue;
|
||||
uint64_t word = 0;
|
||||
bool ok = true;
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
if (in_range(hi, VAG_BIT1_HI_US, VAG_TOL_PCT) && in_range(lo, VAG_BIT1_LO_US, VAG_TOL_PCT)) word |= (uint64_t)1 << b;
|
||||
else if (in_range(hi, VAG_BIT0_HI_US, VAG_TOL_PCT) && in_range(lo, VAG_BIT0_LO_US, VAG_TOL_PCT)) {}
|
||||
else { ok = false; break; }
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->transponder_id = (uint32_t)(word >> 32);
|
||||
frame->counter = (uint16_t)((word >> 16) & 0xFFFFu);
|
||||
frame->button = (uint8_t) ((word >> 8) & 0xFFu);
|
||||
uint8_t rx_ck = (uint8_t) (word & 0xFFu);
|
||||
frame->valid = (rx_ck == vag_cksum(frame->transponder_id, frame->counter, frame->button));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 5. HYUNDAI SANTA FE 2013-2016 (firmware case 0x1A, paired with HU Solaris)
|
||||
*
|
||||
* Hyundai Santa Fe / Solaris RKE (TRW fob variant):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 80 (MSB-first)
|
||||
* Layout: [79:48] 32-bit rolling code (Hitag2 derived)
|
||||
* [47:24] 24-bit serial
|
||||
* [23:16] 8-bit counter
|
||||
* [15:8] 8-bit button flags
|
||||
* [7:0] 8-bit CRC8 (poly 0x31, init 0xFF)
|
||||
* PWM : period 500 µs; 1 = 375 µs HI + 125 µs LO; 0 = 125 µs HI + 375 µs LO
|
||||
* Sync : 375 µs HI + 12000 µs LO
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define SFE_BITS 80u
|
||||
#define SFE_REPEAT 3u
|
||||
#define SFE_SYNC_HI_US 375u
|
||||
#define SFE_SYNC_LO_US 12000u
|
||||
#define SFE_BIT1_HI_US 375u
|
||||
#define SFE_BIT1_LO_US 125u
|
||||
#define SFE_BIT0_HI_US 125u
|
||||
#define SFE_BIT0_LO_US 375u
|
||||
#define SFE_GAP_US 15000u
|
||||
#define SFE_TOL_PCT 15u
|
||||
|
||||
#define SFE_BTN_LOCK 0x01u
|
||||
#define SFE_BTN_UNLOCK 0x02u
|
||||
#define SFE_BTN_TRUNK 0x04u
|
||||
#define SFE_BTN_PANIC 0x08u
|
||||
|
||||
typedef struct {
|
||||
uint32_t rolling; /* Hitag2-derived ciphertext — decrypt separately */
|
||||
uint32_t serial; /* 24-bit */
|
||||
uint8_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} SantaFeFrame;
|
||||
|
||||
static uint8_t sfe_crc8(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint8_t crc = 0xFFu;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for (int b = 0; b < 8; b++) {
|
||||
if (crc & 0x80u) crc = (uint8_t)((crc << 1) ^ 0x31u);
|
||||
else crc = (uint8_t) (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void santafe_encode(const SantaFeFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t pkt[10];
|
||||
pkt[0] = (f->rolling >> 24) & 0xFFu;
|
||||
pkt[1] = (f->rolling >> 16) & 0xFFu;
|
||||
pkt[2] = (f->rolling >> 8) & 0xFFu;
|
||||
pkt[3] = f->rolling & 0xFFu;
|
||||
pkt[4] = (f->serial >> 16) & 0xFFu;
|
||||
pkt[5] = (f->serial >> 8) & 0xFFu;
|
||||
pkt[6] = f->serial & 0xFFu;
|
||||
pkt[7] = f->counter;
|
||||
pkt[8] = f->button;
|
||||
pkt[9] = sfe_crc8(pkt, 9);
|
||||
|
||||
for (uint32_t rep = 0; rep < SFE_REPEAT; rep++) {
|
||||
raw_pair(buf, SFE_SYNC_HI_US, SFE_SYNC_LO_US);
|
||||
for (int byte = 0; byte < 10; byte++) {
|
||||
for (int bit = 7; bit >= 0; bit--) {
|
||||
bool b = (pkt[byte] >> bit) & 1u;
|
||||
raw_pair(buf,
|
||||
b ? SFE_BIT1_HI_US : SFE_BIT0_HI_US,
|
||||
b ? SFE_BIT1_LO_US : SFE_BIT0_LO_US);
|
||||
}
|
||||
}
|
||||
raw_push(buf, -(int32_t)SFE_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool santafe_decode(const RawBuf *buf, SantaFeFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], SFE_SYNC_HI_US, SFE_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], SFE_SYNC_LO_US, SFE_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + SFE_BITS * 2 > buf->count) continue;
|
||||
uint8_t pkt[10] = {0};
|
||||
bool ok = true;
|
||||
for (uint32_t bt = 0; bt < SFE_BITS; bt++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
bool b;
|
||||
if (in_range(lo, SFE_BIT1_LO_US, SFE_TOL_PCT)) b = true;
|
||||
else if (in_range(lo, SFE_BIT0_LO_US, SFE_TOL_PCT)) b = false;
|
||||
else { ok = false; break; }
|
||||
(void)hi;
|
||||
if (b) pkt[bt / 8] |= (uint8_t)(1u << (7 - (bt % 8)));
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->rolling = ((uint32_t)pkt[0]<<24)|((uint32_t)pkt[1]<<16)|((uint32_t)pkt[2]<<8)|pkt[3];
|
||||
frame->serial = ((uint32_t)pkt[4]<<16)|((uint32_t)pkt[5]<<8)|pkt[6];
|
||||
frame->counter = pkt[7];
|
||||
frame->button = pkt[8];
|
||||
uint8_t rx_crc = pkt[9];
|
||||
frame->valid = (rx_crc == sfe_crc8(pkt, 9));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
#pragma once
|
||||
/**
|
||||
* auto_rke_protocols.h
|
||||
* Additional automotive RKE protocols — Pandora DXL 5000 → Flipper Zero port
|
||||
*
|
||||
* Protocols:
|
||||
* - Subaru (ID 0x06) | 433.92 MHz | 48-bit | OOK PWM
|
||||
* - Hyundai/KiaRIO (ID 0x11) | 433.92 MHz | 64-bit | OOK PWM
|
||||
* - Mazda Siemens (ID 0x15) | 433.92 MHz | 72-bit | OOK PWM
|
||||
* - VAG -2004 (ID 0x19) | 433.92 MHz | 64-bit | OOK PWM
|
||||
* - SantaFe 13-16 (ID 0x1A) | 433.92 MHz | 80-bit | OOK PWM
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =========================================================================
|
||||
* Shared raw pulse buffer
|
||||
* All encode functions write into RawBuf.
|
||||
* All decode functions read from RawBuf.
|
||||
* positive value = HIGH duration in µs
|
||||
* negative value = LOW duration in µs
|
||||
* ========================================================================= */
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} RawBuf;
|
||||
|
||||
/* =========================================================================
|
||||
* 1. SUBARU (firmware ID 0x06)
|
||||
* ========================================================================= */
|
||||
|
||||
#define SUBARU_FREQ_HZ 433920000ul
|
||||
#define SUBARU_BITS 48u
|
||||
#define SUBARU_REPEAT 3u
|
||||
#define SUBARU_SYNC_US 8000u
|
||||
#define SUBARU_BIT1_HI_US 600u
|
||||
#define SUBARU_BIT1_LO_US 200u
|
||||
#define SUBARU_BIT0_HI_US 200u
|
||||
#define SUBARU_BIT0_LO_US 600u
|
||||
#define SUBARU_TOL_PCT 15u
|
||||
|
||||
#define SUBARU_BTN_LOCK 0x1u
|
||||
#define SUBARU_BTN_UNLOCK 0x2u
|
||||
#define SUBARU_BTN_TRUNK 0x4u
|
||||
#define SUBARU_BTN_PANIC 0x8u
|
||||
|
||||
/** Subaru RKE frame (Impreza/Forester/Legacy ~2000-2010) */
|
||||
typedef struct {
|
||||
uint32_t fixed_id; /**< 32-bit fixed fob ID */
|
||||
uint8_t counter; /**< 8-bit rolling counter */
|
||||
uint8_t button; /**< SUBARU_BTN_* */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} SubaruFrame;
|
||||
|
||||
void subaru_encode(const SubaruFrame *frame, RawBuf *buf);
|
||||
bool subaru_decode(const RawBuf *buf, SubaruFrame *frame);
|
||||
|
||||
/* =========================================================================
|
||||
* 2. HYUNDAI / KIA RIO (firmware ID 0x11)
|
||||
* ========================================================================= */
|
||||
|
||||
#define HKR_FREQ_HZ 433920000ul
|
||||
#define HKR_BITS 64u
|
||||
#define HKR_REPEAT 3u
|
||||
#define HKR_SYNC_HI_US 312u
|
||||
#define HKR_SYNC_LO_US 10400u
|
||||
#define HKR_BIT1_HI_US 728u
|
||||
#define HKR_BIT1_LO_US 312u
|
||||
#define HKR_BIT0_HI_US 312u
|
||||
#define HKR_BIT0_LO_US 728u
|
||||
#define HKR_GAP_US 10000u
|
||||
#define HKR_TOL_PCT 15u
|
||||
|
||||
#define HKR_BTN_LOCK 0x0100u
|
||||
#define HKR_BTN_UNLOCK 0x0200u
|
||||
#define HKR_BTN_TRUNK 0x0400u
|
||||
#define HKR_BTN_PANIC 0x0800u
|
||||
|
||||
/** Hyundai/Kia RIO RKE frame (Accent/Rio/Elantra ~2001-2008, fixed code) */
|
||||
typedef struct {
|
||||
uint32_t serial; /**< 32-bit fixed serial */
|
||||
uint16_t button_mask; /**< HKR_BTN_* bitmask */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} HKRFrame;
|
||||
|
||||
void hkr_encode(const HKRFrame *frame, RawBuf *buf);
|
||||
bool hkr_decode(const RawBuf *buf, HKRFrame *frame);
|
||||
|
||||
/* =========================================================================
|
||||
* 3. MAZDA SIEMENS (firmware ID 0x15)
|
||||
* ========================================================================= */
|
||||
|
||||
#define MAZ_FREQ_HZ 433920000ul
|
||||
#define MAZ_BITS 72u
|
||||
#define MAZ_REPEAT 2u
|
||||
#define MAZ_SYNC_HI_US 450u
|
||||
#define MAZ_SYNC_LO_US 14400u
|
||||
#define MAZ_BIT1_HI_US 450u
|
||||
#define MAZ_BIT1_LO_US 1350u
|
||||
#define MAZ_BIT0_HI_US 450u
|
||||
#define MAZ_BIT0_LO_US 450u
|
||||
#define MAZ_GAP_US 20000u
|
||||
#define MAZ_TOL_PCT 15u
|
||||
|
||||
#define MAZ_BTN_LOCK 0x1u
|
||||
#define MAZ_BTN_UNLOCK 0x2u
|
||||
#define MAZ_BTN_TRUNK 0x4u
|
||||
|
||||
/**
|
||||
* Mazda Siemens VDO RKE frame (Mazda 3/6/CX-7 ~2003-2009).
|
||||
* hop is the raw Siemens ciphertext — inner cipher is proprietary.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hop; /**< 32-bit Siemens encrypted hopping word */
|
||||
uint32_t serial; /**< 24-bit fixed serial */
|
||||
uint8_t counter; /**< 8-bit rolling counter */
|
||||
uint8_t button; /**< MAZ_BTN_* */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} MazdaFrame;
|
||||
|
||||
void mazda_encode(const MazdaFrame *frame, RawBuf *buf);
|
||||
bool mazda_decode(const RawBuf *buf, MazdaFrame *frame);
|
||||
|
||||
/* =========================================================================
|
||||
* 4. VAG -2004 (firmware ID 0x19)
|
||||
* ========================================================================= */
|
||||
|
||||
#define VAG_FREQ_HZ 433920000ul
|
||||
#define VAG_BITS 64u
|
||||
#define VAG_REPEAT 3u
|
||||
#define VAG_SYNC_HI_US 550u
|
||||
#define VAG_SYNC_LO_US 11000u
|
||||
#define VAG_BIT1_HI_US 550u
|
||||
#define VAG_BIT1_LO_US 250u
|
||||
#define VAG_BIT0_HI_US 250u
|
||||
#define VAG_BIT0_LO_US 550u
|
||||
#define VAG_GAP_US 9000u
|
||||
#define VAG_TOL_PCT 15u
|
||||
|
||||
#define VAG_BTN_LOCK 0x01u
|
||||
#define VAG_BTN_UNLOCK 0x02u
|
||||
#define VAG_BTN_TRUNK 0x04u
|
||||
#define VAG_BTN_PANIC 0x08u
|
||||
|
||||
/** VW/Audi/Seat/Skoda pre-2004 RKE frame */
|
||||
typedef struct {
|
||||
uint32_t transponder_id; /**< 32-bit fixed transponder ID */
|
||||
uint16_t counter; /**< 16-bit rolling counter */
|
||||
uint8_t button; /**< VAG_BTN_* */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} VAGFrame;
|
||||
|
||||
void vag_encode(const VAGFrame *frame, RawBuf *buf);
|
||||
bool vag_decode(const RawBuf *buf, VAGFrame *frame);
|
||||
|
||||
/** Counter window validation — VAG accepts [stored+1, stored+255] */
|
||||
static inline bool vag_counter_valid(uint16_t stored, uint16_t received) {
|
||||
uint16_t delta = (uint16_t)(received - stored);
|
||||
return (delta >= 1u && delta <= 255u);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 5. HYUNDAI SANTA FE 2013-2016 (firmware ID 0x1A)
|
||||
* ========================================================================= */
|
||||
|
||||
#define SFE_FREQ_HZ 433920000ul
|
||||
#define SFE_BITS 80u
|
||||
#define SFE_REPEAT 3u
|
||||
#define SFE_SYNC_HI_US 375u
|
||||
#define SFE_SYNC_LO_US 12000u
|
||||
#define SFE_BIT1_HI_US 375u
|
||||
#define SFE_BIT1_LO_US 125u
|
||||
#define SFE_BIT0_HI_US 125u
|
||||
#define SFE_BIT0_LO_US 375u
|
||||
#define SFE_GAP_US 15000u
|
||||
#define SFE_TOL_PCT 15u
|
||||
|
||||
#define SFE_BTN_LOCK 0x01u
|
||||
#define SFE_BTN_UNLOCK 0x02u
|
||||
#define SFE_BTN_TRUNK 0x04u
|
||||
#define SFE_BTN_PANIC 0x08u
|
||||
|
||||
/**
|
||||
* Hyundai Santa Fe / Solaris RKE frame (TRW fob ~2013-2016).
|
||||
* rolling is Hitag2-derived ciphertext.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t rolling; /**< 32-bit Hitag2-derived encrypted word */
|
||||
uint32_t serial; /**< 24-bit fixed serial */
|
||||
uint8_t counter; /**< 8-bit rolling counter */
|
||||
uint8_t button; /**< SFE_BTN_* */
|
||||
bool valid; /**< true after decode if CRC8 matched */
|
||||
} SantaFeFrame;
|
||||
|
||||
void santafe_encode(const SantaFeFrame *frame, RawBuf *buf);
|
||||
bool santafe_decode(const RawBuf *buf, SantaFeFrame *frame);
|
||||
|
||||
/** Counter window validation — SantaFe accepts [stored+1, stored+32] */
|
||||
static inline bool santafe_counter_valid(uint8_t stored, uint8_t received) {
|
||||
uint8_t delta = (uint8_t)(received - stored);
|
||||
return (delta >= 1u && delta <= 32u);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,890 @@
|
||||
#include "ford_v2.h"
|
||||
#include <furi.h>
|
||||
#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
|
||||
#define FORD_V2_TE_DELTA 260U
|
||||
#define FORD_V2_INTER_BURST_GAP_US 15000U
|
||||
#define FORD_V2_PREAMBLE_MIN 64U
|
||||
#define FORD_V2_DATA_BITS 104U
|
||||
#define FORD_V2_DATA_BYTES 13U
|
||||
#define FORD_V2_SYNC_0 0x7FU
|
||||
#define FORD_V2_SYNC_1 0xA7U
|
||||
#define FORD_V2_ENC_TE_SHORT 240U
|
||||
#define FORD_V2_ENC_PREAMBLE_PAIRS 70U
|
||||
#define FORD_V2_ENC_BURST_COUNT 6U
|
||||
#define FORD_V2_ENC_INTER_BURST_GAP_US 16000U
|
||||
#define FORD_V2_ENC_ALLOC_ELEMS 2600U
|
||||
#define FORD_V2_ENC_SEPARATOR_ELEMS 2U
|
||||
#define FORD_V2_ENC_PREAMBLE_ELEMS (FORD_V2_ENC_PREAMBLE_PAIRS * 2U)
|
||||
#define FORD_V2_ENC_DATA_ELEMS ((FORD_V2_DATA_BITS - 1U) * 2U)
|
||||
#define FORD_V2_ENC_BURST_ELEMS \
|
||||
(FORD_V2_ENC_PREAMBLE_ELEMS + FORD_V2_ENC_SEPARATOR_ELEMS + FORD_V2_ENC_DATA_ELEMS)
|
||||
#define FORD_V2_ENC_UPLOAD_ELEMS \
|
||||
(FORD_V2_ENC_BURST_COUNT * FORD_V2_ENC_BURST_ELEMS + (FORD_V2_ENC_BURST_COUNT - 1U))
|
||||
#define FORD_V2_ENC_SYNC_LO_US 476U
|
||||
|
||||
#define FORD_V2_SYNC_BITS 16U
|
||||
#define FORD_V2_POST_SYNC_DECODE_COUNT_BIT 16U
|
||||
#define FORD_V2_KEY_BYTE_COUNT 8U
|
||||
#define FORD_V2_TAIL_RAW_BYTE_COUNT 5U
|
||||
#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));
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v2_const = {
|
||||
.te_short = FORD_V2_TE_SHORT,
|
||||
.te_long = FORD_V2_TE_LONG,
|
||||
.te_delta = FORD_V2_TE_DELTA,
|
||||
.min_count_bit_for_found = FORD_V2_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FordV2DecoderStepReset = 0,
|
||||
FordV2DecoderStepPreamble = 1,
|
||||
FordV2DecoderStepSync = 2,
|
||||
FordV2DecoderStepData = 3,
|
||||
} FordV2DecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV2 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t raw_bytes[FORD_V2_DATA_BYTES];
|
||||
uint8_t byte_count;
|
||||
|
||||
uint16_t sync_shift;
|
||||
uint8_t sync_bit_count;
|
||||
|
||||
uint64_t extra_data;
|
||||
uint16_t counter16;
|
||||
uint32_t tail31;
|
||||
bool structure_ok;
|
||||
} SubGhzProtocolDecoderFordV2;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderFordV2 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint64_t extra_data;
|
||||
uint8_t raw_bytes[FORD_V2_DATA_BYTES];
|
||||
} SubGhzProtocolEncoderFordV2;
|
||||
|
||||
static void ford_v2_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event);
|
||||
|
||||
static void ford_v2_decoder_reset_state(SubGhzProtocolDecoderFordV2* instance) {
|
||||
instance->decoder.parser_step = FordV2DecoderStepReset;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->counter16 = 0;
|
||||
instance->tail31 = 0;
|
||||
instance->structure_ok = false;
|
||||
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
static bool ford_v2_duration_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V2_TE_SHORT) < (int32_t)FORD_V2_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v2_duration_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V2_TE_LONG) < (int32_t)FORD_V2_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v2_button_is_valid(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x13:
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t ford_v2_uint8_parity(uint8_t value) {
|
||||
uint8_t parity = 0U;
|
||||
while(value) {
|
||||
parity ^= (value & 1U);
|
||||
value >>= 1U;
|
||||
}
|
||||
return parity;
|
||||
}
|
||||
|
||||
static const char* ford_v2_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
return "Lock";
|
||||
case 0x11:
|
||||
return "Unlock";
|
||||
case 0x13:
|
||||
return "Trunk";
|
||||
case 0x14:
|
||||
return "Panic";
|
||||
case 0x15:
|
||||
return "RemoteStart";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_extract_from_raw(SubGhzProtocolDecoderFordV2* instance) {
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
instance->generic.serial = ((uint32_t)k[2] << 24) | ((uint32_t)k[3] << 16) |
|
||||
((uint32_t)k[4] << 8) | (uint32_t)k[5];
|
||||
|
||||
instance->generic.btn = k[6];
|
||||
|
||||
instance->counter16 = (uint16_t)((((uint16_t)(k[7] & 0x7FU)) << 9) |
|
||||
(((uint16_t)k[8]) << 1) |
|
||||
((uint16_t)(k[9] >> 7)));
|
||||
|
||||
instance->generic.cnt = instance->counter16;
|
||||
|
||||
instance->tail31 = (((uint32_t)(k[9] & 0x7FU)) << 24) | ((uint32_t)k[10] << 16) |
|
||||
((uint32_t)k[11] << 8) | (uint32_t)k[12];
|
||||
|
||||
instance->structure_ok = true;
|
||||
|
||||
if(k[0] != FORD_V2_SYNC_0) instance->structure_ok = false;
|
||||
if(k[1] != FORD_V2_SYNC_1) instance->structure_ok = false;
|
||||
if(!ford_v2_button_is_valid(k[6])) instance->structure_ok = false;
|
||||
|
||||
if((k[7] & 0x7FU) != (uint8_t)((instance->counter16 >> 9) & 0x7FU)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
if(k[8] != (uint8_t)((instance->counter16 >> 1) & 0xFFU)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
if(((k[9] >> 7) & 1U) != (uint8_t)(instance->counter16 & 1U)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
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)k[i];
|
||||
}
|
||||
|
||||
instance->generic.data_count_bit = FORD_V2_DATA_BITS;
|
||||
|
||||
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)k[8U + i];
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_commit_frame(SubGhzProtocolDecoderFordV2* instance) {
|
||||
if(instance->raw_bytes[0] != FORD_V2_SYNC_0 || instance->raw_bytes[1] != FORD_V2_SYNC_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ford_v2_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_sync_enter_data(SubGhzProtocolDecoderFordV2* instance) {
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
instance->raw_bytes[0] = FORD_V2_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V2_SYNC_1;
|
||||
instance->byte_count = 2U;
|
||||
instance->decoder.parser_step = FordV2DecoderStepData;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = FORD_V2_POST_SYNC_DECODE_COUNT_BIT;
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_sync_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->sync_shift = (uint16_t)((instance->sync_shift << 1) | (data_bit ? 1U : 0U));
|
||||
if(instance->sync_bit_count < FORD_V2_SYNC_BITS) {
|
||||
instance->sync_bit_count++;
|
||||
}
|
||||
|
||||
return instance->sync_bit_count >= FORD_V2_SYNC_BITS && instance->sync_shift == ford_v2_sync_shift16_inv;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync) {
|
||||
if(ford_v2_decoder_sync_feed_event(instance, event)) {
|
||||
ford_v2_decoder_sync_enter_data(instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->decoder.parser_step != FordV2DecoderStepData) {
|
||||
return;
|
||||
}
|
||||
|
||||
data_bit = !data_bit;
|
||||
|
||||
instance->decoder.decode_data =
|
||||
(instance->decoder.decode_data << 1) | (data_bit ? 1U : 0U);
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if((instance->decoder.decode_count_bit & 7U) == 0U) {
|
||||
uint8_t byte_val = (uint8_t)(instance->decoder.decode_data & 0xFFU);
|
||||
|
||||
if(instance->byte_count < FORD_V2_DATA_BYTES) {
|
||||
instance->raw_bytes[instance->byte_count] = byte_val;
|
||||
instance->byte_count++;
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
|
||||
if(instance->byte_count == FORD_V2_DATA_BYTES) {
|
||||
(void)ford_v2_decoder_commit_frame(instance);
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_manchester_feed_pulse(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ford_v2_duration_is_long(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_enter_sync_from_preamble(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
instance->decoder.parser_step = FordV2DecoderStepSync;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
if(ev == ManchesterEventShortLow || ev == ManchesterEventLongLow) {
|
||||
instance->manchester_state = ManchesterStateMid0;
|
||||
}
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
} else if(ford_v2_duration_is_long(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
if(ev == ManchesterEventShortLow || ev == ManchesterEventLongLow) {
|
||||
instance->manchester_state = ManchesterStateMid0;
|
||||
}
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_rebuild_raw_buffer(SubGhzProtocolDecoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[i] = (uint8_t)(instance->generic.data >> (56U - i * 8U));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[8U + i] = (uint8_t)(instance->extra_data >> (32U - i * 8U));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v2_encoder_add_level(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
size_t idx = instance->encoder.size_upload;
|
||||
if(idx > 0 && level_duration_get_level(instance->encoder.upload[idx - 1]) == level) {
|
||||
uint32_t prev = level_duration_get_duration(instance->encoder.upload[idx - 1]);
|
||||
instance->encoder.upload[idx - 1] = level_duration_make(level, prev + duration);
|
||||
} else {
|
||||
furi_check(idx < FORD_V2_ENC_ALLOC_ELEMS);
|
||||
instance->encoder.upload[idx] = level_duration_make(level, duration);
|
||||
instance->encoder.size_upload++;
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_rebuild_raw_from_payload(SubGhzProtocolEncoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[i] = (uint8_t)(instance->generic.data >> (56U - i * 8U));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[8U + i] = (uint8_t)(instance->extra_data >> (32U - i * 8U));
|
||||
}
|
||||
|
||||
const uint8_t btn = instance->raw_bytes[6];
|
||||
const uint8_t k7_msb = (uint8_t)(ford_v2_uint8_parity(btn) << 7);
|
||||
instance->raw_bytes[7] = (instance->raw_bytes[7] & 0x7FU) | k7_msb;
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_refresh_data_from_raw(SubGhzProtocolEncoderFordV2* instance) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v2_encoder_emit_manchester_bit(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
bool bit) {
|
||||
if(bit) {
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
} else {
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_emit_burst(SubGhzProtocolEncoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_ENC_PREAMBLE_PAIRS; i++) {
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
}
|
||||
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_SYNC_LO_US);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
|
||||
for(uint16_t bit_pos = 1U; bit_pos < FORD_V2_DATA_BITS; bit_pos++) {
|
||||
const uint8_t byte_idx = (uint8_t)(bit_pos / 8U);
|
||||
const uint8_t bit_idx = (uint8_t)(7U - (bit_pos % 8U));
|
||||
ford_v2_encoder_emit_manchester_bit(
|
||||
instance, ((instance->raw_bytes[byte_idx] >> bit_idx) & 1U) != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_build_upload(SubGhzProtocolEncoderFordV2* instance) {
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
for(uint8_t burst = 0; burst < FORD_V2_ENC_BURST_COUNT; burst++) {
|
||||
ford_v2_encoder_emit_burst(instance);
|
||||
|
||||
if(burst + 1U < FORD_V2_ENC_BURST_COUNT) {
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_INTER_BURST_GAP_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_read_optional_tail_raw(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
instance->extra_data = 0U;
|
||||
uint8_t tail_raw[FORD_V2_TAIL_RAW_BYTE_COUNT] = {0};
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(flipper_format, "TailRaw", tail_raw, sizeof(tail_raw))) {
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->extra_data = (instance->extra_data << 8) | (uint64_t)tail_raw[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus ford_v2_encoder_deserialize_read_header(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format,
|
||||
FuriString* temp_str) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus g = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, FORD_V2_DATA_BITS);
|
||||
if(g != SubGhzProtocolStatusOk) {
|
||||
return g;
|
||||
}
|
||||
|
||||
ford_v2_encoder_read_optional_tail_raw(instance, flipper_format);
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1)) {
|
||||
instance->encoder.repeat = repeat;
|
||||
}
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFordV2* instance = calloc(1, sizeof(SubGhzProtocolEncoderFordV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
instance->encoder.upload = calloc(FORD_V2_ENC_ALLOC_ELEMS, sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
instance->generic.data_count_bit = FORD_V2_DATA_BITS;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_check(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;
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v2_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0U) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_reset(void* context) {
|
||||
furi_check(context);
|
||||
ford_v2_decoder_reset_state((SubGhzProtocolDecoderFordV2*)context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV2DecoderStepReset:
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV2DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV2DecoderStepPreamble:
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
if(instance->preamble_count < FORD_V2_PREAMBLE_COUNT_MAX) {
|
||||
instance->preamble_count++;
|
||||
}
|
||||
} else if(!level && ford_v2_duration_is_long(duration)) {
|
||||
if(instance->preamble_count >= FORD_V2_PREAMBLE_MIN) {
|
||||
ford_v2_decoder_enter_sync_from_preamble(instance, level, duration);
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV2DecoderStepSync:
|
||||
case FordV2DecoderStepData:
|
||||
if(ford_v2_decoder_manchester_feed_pulse(instance, level, duration)) {
|
||||
} else {
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync &&
|
||||
duration >= FORD_V2_INTER_BURST_GAP_US) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepData) {
|
||||
if(duration >= FORD_V2_INTER_BURST_GAP_US) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v2_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
const uint16_t cnt = (uint16_t)((((uint16_t)(k[7] & 0x7FU)) << 9) | (((uint16_t)k[8]) << 1) |
|
||||
((uint16_t)(k[9] >> 7)));
|
||||
const uint32_t tail = (((uint32_t)(k[9] & 0x7FU)) << 24) | ((uint32_t)k[10] << 16) |
|
||||
((uint32_t)k[11] << 8) | (uint32_t)k[12];
|
||||
|
||||
uint32_t mix = ((uint32_t)k[2] << 24) | ((uint32_t)k[3] << 16) | ((uint32_t)k[4] << 8) |
|
||||
(uint32_t)k[5];
|
||||
mix ^= (uint32_t)k[6] << 16;
|
||||
mix ^= (uint32_t)cnt << 8;
|
||||
mix ^= tail;
|
||||
|
||||
return (uint8_t)((mix >> 0) ^ (mix >> 8) ^ (mix >> 16) ^ (mix >> 24) ^ (uint8_t)(cnt >> 8) ^
|
||||
(uint8_t)(tail >> 16));
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &btn, 1);
|
||||
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &cnt, 1);
|
||||
|
||||
uint32_t tail31 = instance->tail31;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Tail31", &tail31, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(flipper_format, "TailRaw", &instance->raw_bytes[8], 5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_read_tail_raw_if_present(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
uint8_t tail_raw[FORD_V2_TAIL_RAW_BYTE_COUNT] = {0};
|
||||
if(flipper_format_read_hex(flipper_format, "TailRaw", tail_raw, sizeof(tail_raw))) {
|
||||
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)tail_raw[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_ford_v2_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if(instance->generic.data_count_bit != FORD_V2_DATA_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
ford_v2_decoder_read_tail_raw_if_present(instance, flipper_format);
|
||||
|
||||
ford_v2_decoder_rebuild_raw_buffer(instance);
|
||||
ford_v2_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
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;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
|
||||
"Sn:%08lX Btn:%02X [%s]\r\n"
|
||||
"Cnt:%u Struct:%s\r\n"
|
||||
"Tail31:%08lX\r\n"
|
||||
"TailRaw:%02X%02X%02X%02X%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
k[2],
|
||||
k[3],
|
||||
k[4],
|
||||
k[5],
|
||||
k[6],
|
||||
k[7],
|
||||
k[8],
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12],
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
ford_v2_button_name(instance->generic.btn),
|
||||
(unsigned)instance->counter16,
|
||||
instance->structure_ok ? "OK" : "BAD",
|
||||
(unsigned long)instance->tail31,
|
||||
k[8],
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12]);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v2_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v2_alloc,
|
||||
.free = subghz_protocol_decoder_ford_v2_free,
|
||||
.feed = subghz_protocol_decoder_ford_v2_feed,
|
||||
.reset = subghz_protocol_decoder_ford_v2_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_ford_v2_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ford_v2_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ford_v2_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ford_v2_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v2_alloc,
|
||||
.free = subghz_protocol_encoder_ford_v2_free,
|
||||
.deserialize = subghz_protocol_encoder_ford_v2_deserialize,
|
||||
.stop = subghz_protocol_encoder_ford_v2_stop,
|
||||
.yield = subghz_protocol_encoder_ford_v2_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ford_protocol_v2 = {
|
||||
.name = FORD_PROTOCOL_V2_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
|
||||
| SubGhzProtocolFlag_Send
|
||||
,
|
||||
.decoder = &subghz_protocol_ford_v2_decoder,
|
||||
.encoder = &subghz_protocol_ford_v2_encoder,
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define FORD_PROTOCOL_V2_NAME "Ford V2"
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v2;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_ford_v2_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v2_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v2_reset(void* context);
|
||||
void subghz_protocol_decoder_ford_v2_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_ford_v2_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output);
|
||||
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_ford_v2_free(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_ford_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_stop(void* context);
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v2_yield(void* context);
|
||||
@@ -0,0 +1,868 @@
|
||||
#include "ford_v3.h"
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
|
||||
#define FORD_V3_TE_SHORT 200U
|
||||
#define FORD_V3_TE_LONG 400U
|
||||
#define FORD_V3_TE_DELTA 260U
|
||||
#define FORD_V3_INTER_BURST_GAP_US 15000U
|
||||
#define FORD_V3_PREAMBLE_MIN 64U
|
||||
|
||||
#define FORD_V3_DATA_BYTES 17U
|
||||
#define FORD_V3_DATA_BITS 136U
|
||||
#define FORD_V3_SYNC_0 0x7FU
|
||||
#define FORD_V3_SYNC_1 0xA7U
|
||||
#define FORD_V3_CRC_LEN 12U
|
||||
#define FORD_V3_CRC_OFFSET 3U
|
||||
#define FORD_V3_CRC_POLY 0x1021U
|
||||
#define FORD_V3_CRC_INIT 0x0000U
|
||||
#define FORD_V3_CRYPT_OFFSET 7U
|
||||
#define FORD_V3_CRYPT_LEN 8U
|
||||
|
||||
#define FORD_V3_ENC_TE_SHORT 240U
|
||||
#define FORD_V3_ENC_PREAMBLE_PAIRS 70U
|
||||
#define FORD_V3_ENC_BURST_COUNT 6U
|
||||
#define FORD_V3_ENC_INTER_BURST_GAP_US 16000U
|
||||
#define FORD_V3_ENC_ALLOC_ELEMS 2600U
|
||||
#define FORD_V3_ENC_SYNC_LO_US 476U
|
||||
#define FORD_V3_ENC_SEPARATOR_ELEMS 2U
|
||||
#define FORD_V3_ENC_PREAMBLE_ELEMS (FORD_V3_ENC_PREAMBLE_PAIRS * 2U)
|
||||
#define FORD_V3_ENC_DATA_ELEMS ((FORD_V3_DATA_BITS - 1U) * 2U)
|
||||
#define FORD_V3_ENC_BURST_ELEMS \
|
||||
(FORD_V3_ENC_PREAMBLE_ELEMS + FORD_V3_ENC_SEPARATOR_ELEMS + FORD_V3_ENC_DATA_ELEMS)
|
||||
#define FORD_V3_ENC_UPLOAD_ELEMS \
|
||||
(FORD_V3_ENC_BURST_COUNT * FORD_V3_ENC_BURST_ELEMS + (FORD_V3_ENC_BURST_COUNT - 1U))
|
||||
#define FORD_V3_ENCODER_DEFAULT_REPEAT 10U
|
||||
|
||||
#define FORD_V3_SYNC_BITS 16U
|
||||
#define FORD_V3_POST_SYNC_DECODE_COUNT_BIT 16U
|
||||
#define FORD_V3_PREAMBLE_COUNT_MAX 0xFFFFU
|
||||
|
||||
static const uint16_t ford_v3_sync_shift16_inv =
|
||||
(uint16_t)(~(((uint16_t)FORD_V3_SYNC_0 << 8) | (uint16_t)FORD_V3_SYNC_1));
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
|
||||
.te_short = FORD_V3_TE_SHORT,
|
||||
.te_long = FORD_V3_TE_LONG,
|
||||
.te_delta = FORD_V3_TE_DELTA,
|
||||
.min_count_bit_for_found = FORD_V3_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FordV3DecoderStepReset = 0,
|
||||
FordV3DecoderStepPreamble = 1,
|
||||
FordV3DecoderStepSync = 2,
|
||||
FordV3DecoderStepData = 3,
|
||||
} FordV3DecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV3 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
|
||||
uint8_t byte_count;
|
||||
|
||||
uint16_t sync_shift;
|
||||
uint8_t sync_bit_count;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t counter16;
|
||||
uint16_t crc_received;
|
||||
uint16_t crc_computed;
|
||||
bool crc_ok;
|
||||
bool structure_ok;
|
||||
|
||||
uint8_t crypt_buf[FORD_V3_CRYPT_LEN];
|
||||
} SubGhzProtocolDecoderFordV3;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderFordV3 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
|
||||
|
||||
uint8_t raw_freq0[FORD_V3_DATA_BYTES];
|
||||
uint8_t raw_freq1[FORD_V3_DATA_BYTES];
|
||||
uint8_t raw_freq2[FORD_V3_DATA_BYTES];
|
||||
} SubGhzProtocolEncoderFordV3;
|
||||
|
||||
static void ford_v3_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
ManchesterEvent event);
|
||||
|
||||
static uint16_t ford_v3_crc16(const uint8_t* data, uint8_t len) {
|
||||
uint16_t crc = FORD_V3_CRC_INIT;
|
||||
while(len--) {
|
||||
crc ^= (uint16_t)(*data++) << 8;
|
||||
for(uint8_t i = 0; i < 8U; i++) {
|
||||
crc = (crc & 0x8000U) ? (uint16_t)((crc << 1) ^ FORD_V3_CRC_POLY)
|
||||
: (uint16_t)(crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void ford_v3_crc_process(uint8_t* buf) {
|
||||
uint16_t crc = ford_v3_crc16(&buf[FORD_V3_CRC_OFFSET], FORD_V3_CRC_LEN);
|
||||
buf[15] = (uint8_t)((crc >> 8) & 0xFFU);
|
||||
buf[16] = (uint8_t)(crc & 0xFFU);
|
||||
}
|
||||
|
||||
static uint8_t ford_v3_uint8_parity(uint8_t value) {
|
||||
uint8_t p = 0U;
|
||||
while(value) {
|
||||
p ^= (value & 1U);
|
||||
value >>= 1U;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void ford_v3_encrypt_buffer_process(uint8_t* crypt) {
|
||||
uint8_t t0 = (0xAAU & crypt[5]) | (0x55U & crypt[6]);
|
||||
uint8_t t1 = (0x55U & crypt[5]) | (0xAAU & crypt[6]);
|
||||
crypt[5] = t0;
|
||||
crypt[6] = t1;
|
||||
|
||||
uint8_t par = ford_v3_uint8_parity(crypt[7]);
|
||||
|
||||
if(par) {
|
||||
uint8_t mask = crypt[6];
|
||||
crypt[0] ^= mask;
|
||||
crypt[1] ^= mask;
|
||||
crypt[2] ^= mask;
|
||||
crypt[3] ^= mask;
|
||||
crypt[4] ^= mask;
|
||||
crypt[5] ^= mask;
|
||||
} else {
|
||||
uint8_t mask = crypt[5];
|
||||
crypt[0] ^= mask;
|
||||
crypt[1] ^= mask;
|
||||
crypt[2] ^= mask;
|
||||
crypt[3] ^= mask;
|
||||
crypt[4] ^= mask;
|
||||
crypt[6] ^= mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_encrypt(uint8_t* buf) {
|
||||
uint8_t crypt[FORD_V3_CRYPT_LEN];
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
crypt[i] = buf[FORD_V3_CRYPT_OFFSET + i];
|
||||
}
|
||||
|
||||
uint8_t sum = 0U;
|
||||
for(uint8_t i = 0; i < 7U; i++) sum += crypt[i];
|
||||
crypt[7] = sum;
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
buf[FORD_V3_CRYPT_OFFSET + i] = crypt[i];
|
||||
}
|
||||
|
||||
ford_v3_encrypt_buffer_process(crypt);
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
buf[FORD_V3_CRYPT_OFFSET + i] = crypt[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_decrypt(const uint8_t* enc_block, uint8_t* crypt_out) {
|
||||
uint8_t crypt[20] = {0};
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) crypt[i] = enc_block[i];
|
||||
|
||||
crypt[17] = crypt[1];
|
||||
crypt[18] = 0x00U;
|
||||
{
|
||||
uint8_t tmp = crypt[17];
|
||||
while(tmp) {
|
||||
if(tmp & 1U) crypt[18] ^= 1U;
|
||||
tmp >>= 1U;
|
||||
}
|
||||
}
|
||||
|
||||
if(crypt[18] & 0xFFU) {
|
||||
crypt[17] = enc_block[6];
|
||||
for(uint8_t i = 1; i < 7U; i++) {
|
||||
crypt[i] = (crypt[i] ^ crypt[17]) & 0xFFU;
|
||||
}
|
||||
} else {
|
||||
crypt[17] = enc_block[5];
|
||||
for(uint8_t i = 1; i < 6U; i++) {
|
||||
crypt[i] = (crypt[i] ^ crypt[17]) & 0xFFU;
|
||||
}
|
||||
crypt[7] ^= crypt[17];
|
||||
}
|
||||
|
||||
crypt[19] = (crypt[6] & 0xAAU) | (crypt[7] & 0x55U);
|
||||
crypt[7] = (crypt[7] & 0xAAU) | (crypt[6] & 0x55U);
|
||||
crypt[6] = crypt[19] & 0xFFU;
|
||||
|
||||
crypt[20 - 1] = 7U;
|
||||
crypt[17] = 0x00U;
|
||||
while(crypt[19]) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
uint8_t cnt = 7U;
|
||||
uint8_t sum = 0U;
|
||||
while(cnt) {
|
||||
--cnt;
|
||||
sum += crypt[cnt];
|
||||
}
|
||||
crypt[17] = sum;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) crypt_out[i] = crypt[i];
|
||||
}
|
||||
|
||||
static bool ford_v3_button_is_valid(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
case 0x20:
|
||||
case 0x40:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ford_v3_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10: return "Lock";
|
||||
case 0x20: return "Unlock";
|
||||
case 0x40: return "Trunk";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_reset_state(SubGhzProtocolDecoderFordV3* instance) {
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->counter16 = 0;
|
||||
instance->crc_ok = false;
|
||||
instance->structure_ok = false;
|
||||
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
memset(instance->crypt_buf, 0, sizeof(instance->crypt_buf));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
static bool ford_v3_duration_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v3_duration_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V3_TE_LONG) < (int32_t)FORD_V3_TE_DELTA;
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_extract_from_raw(SubGhzProtocolDecoderFordV3* instance) {
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
instance->structure_ok = false;
|
||||
|
||||
if(k[0] != FORD_V3_SYNC_0 || k[1] != FORD_V3_SYNC_1) return;
|
||||
|
||||
instance->serial =
|
||||
((uint32_t)k[4] << 16) | ((uint32_t)k[5] << 8) | (uint32_t)k[6];
|
||||
instance->generic.serial = instance->serial;
|
||||
|
||||
ford_v3_decrypt(&k[FORD_V3_CRYPT_OFFSET], instance->crypt_buf);
|
||||
|
||||
instance->btn = instance->crypt_buf[4];
|
||||
instance->generic.btn = instance->btn;
|
||||
|
||||
instance->counter16 = ((uint16_t)instance->crypt_buf[5] << 8) |
|
||||
(uint16_t)instance->crypt_buf[6];
|
||||
instance->generic.cnt = instance->counter16;
|
||||
|
||||
instance->crc_received =
|
||||
((uint16_t)k[15] << 8) | (uint16_t)k[16];
|
||||
instance->crc_computed = ford_v3_crc16(&k[FORD_V3_CRC_OFFSET], FORD_V3_CRC_LEN);
|
||||
instance->crc_ok = (instance->crc_received == instance->crc_computed);
|
||||
|
||||
if(!instance->crc_ok) return;
|
||||
if(!ford_v3_button_is_valid(instance->btn)) return;
|
||||
|
||||
instance->generic.data = 0;
|
||||
for(uint8_t i = 0; i < 8U; i++) {
|
||||
instance->generic.data = (instance->generic.data << 8) | (uint64_t)k[i];
|
||||
}
|
||||
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
|
||||
|
||||
instance->structure_ok = true;
|
||||
}
|
||||
|
||||
static bool ford_v3_decoder_commit_frame(SubGhzProtocolDecoderFordV3* instance) {
|
||||
if(instance->raw_bytes[0] != FORD_V3_SYNC_0 ||
|
||||
instance->raw_bytes[1] != FORD_V3_SYNC_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ford_v3_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) return false;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_sync_enter_data(SubGhzProtocolDecoderFordV3* instance) {
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
instance->raw_bytes[0] = FORD_V3_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V3_SYNC_1;
|
||||
instance->byte_count = 2U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepData;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = FORD_V3_POST_SYNC_DECODE_COUNT_BIT;
|
||||
}
|
||||
|
||||
static bool ford_v3_decoder_sync_feed_event(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event,
|
||||
&instance->manchester_state, &data_bit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->sync_shift =
|
||||
(uint16_t)((instance->sync_shift << 1) | (data_bit ? 1U : 0U));
|
||||
if(instance->sync_bit_count < FORD_V3_SYNC_BITS) {
|
||||
instance->sync_bit_count++;
|
||||
}
|
||||
|
||||
return (instance->sync_bit_count >= FORD_V3_SYNC_BITS) &&
|
||||
(instance->sync_shift == ford_v3_sync_shift16_inv);
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(instance->decoder.parser_step == FordV3DecoderStepSync) {
|
||||
if(ford_v3_decoder_sync_feed_event(instance, event)) {
|
||||
ford_v3_decoder_sync_enter_data(instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event,
|
||||
&instance->manchester_state, &data_bit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->decoder.parser_step != FordV3DecoderStepData) return;
|
||||
|
||||
data_bit = !data_bit;
|
||||
|
||||
instance->decoder.decode_data =
|
||||
(instance->decoder.decode_data << 1) | (data_bit ? 1U : 0U);
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if((instance->decoder.decode_count_bit & 7U) == 0U) {
|
||||
uint8_t byte_val = (uint8_t)(instance->decoder.decode_data & 0xFFU);
|
||||
|
||||
if(instance->byte_count < FORD_V3_DATA_BYTES) {
|
||||
instance->raw_bytes[instance->byte_count] = byte_val;
|
||||
instance->byte_count++;
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
|
||||
if(instance->byte_count == FORD_V3_DATA_BYTES) {
|
||||
(void)ford_v3_decoder_commit_frame(instance);
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v3_decoder_manchester_feed_pulse(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
if(ford_v3_duration_is_long(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_enter_sync_from_preamble(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
instance->decoder.parser_step = FordV3DecoderStepSync;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
} else if(ford_v3_duration_is_long(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v3_encoder_add_level(
|
||||
SubGhzProtocolEncoderFordV3* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
size_t idx = instance->encoder.size_upload;
|
||||
if(idx > 0 &&
|
||||
level_duration_get_level(instance->encoder.upload[idx - 1]) == level) {
|
||||
uint32_t prev =
|
||||
level_duration_get_duration(instance->encoder.upload[idx - 1]);
|
||||
instance->encoder.upload[idx - 1] =
|
||||
level_duration_make(level, prev + duration);
|
||||
} else {
|
||||
furi_check(idx < FORD_V3_ENC_ALLOC_ELEMS);
|
||||
instance->encoder.upload[idx] = level_duration_make(level, duration);
|
||||
instance->encoder.size_upload++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v3_encoder_emit_manchester_bit(
|
||||
SubGhzProtocolEncoderFordV3* instance,
|
||||
bool bit) {
|
||||
if(bit) {
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_TE_SHORT);
|
||||
} else {
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_TE_SHORT);
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_encoder_emit_burst(
|
||||
SubGhzProtocolEncoderFordV3* instance,
|
||||
const uint8_t* raw) {
|
||||
for(uint8_t i = 0; i < FORD_V3_ENC_PREAMBLE_PAIRS; i++) {
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_TE_SHORT);
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
}
|
||||
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_SYNC_LO_US);
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
|
||||
for(uint16_t bit_pos = 1U; bit_pos < FORD_V3_DATA_BITS; bit_pos++) {
|
||||
const uint8_t byte_idx = (uint8_t)(bit_pos / 8U);
|
||||
const uint8_t bit_idx = (uint8_t)(7U - (bit_pos % 8U));
|
||||
ford_v3_encoder_emit_manchester_bit(
|
||||
instance,
|
||||
((raw[byte_idx] >> bit_idx) & 1U) != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ford V3 transmits three frequency variants (freq-id 0x00, 0x08, 0x10)
|
||||
* per burst cycle, each followed by inter-burst gap, cycling
|
||||
* FORD_V3_ENC_BURST_COUNT times total.
|
||||
*
|
||||
* For simplicity on Flipper (single-frequency TX) we encode only the
|
||||
* freq-id 0x00 variant repeated BURST_COUNT times, matching the V2 pattern.
|
||||
* Can replay the other variants by editing the .sub file.
|
||||
*/
|
||||
static void ford_v3_encoder_build_upload(SubGhzProtocolEncoderFordV3* instance) {
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
for(uint8_t burst = 0; burst < FORD_V3_ENC_BURST_COUNT; burst++) {
|
||||
ford_v3_encoder_emit_burst(instance, instance->raw_bytes);
|
||||
|
||||
if(burst + 1U < FORD_V3_ENC_BURST_COUNT) {
|
||||
ford_v3_encoder_add_level(
|
||||
instance, true, FORD_V3_ENC_INTER_BURST_GAP_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_encoder_rebuild_raw_from_payload(
|
||||
SubGhzProtocolEncoderFordV3* instance) {
|
||||
instance->raw_bytes[0] = FORD_V3_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V3_SYNC_1;
|
||||
instance->raw_bytes[2] = 0x00U;
|
||||
/* freq-id – use default (0x00 = 433.6 MHz channel) */
|
||||
instance->raw_bytes[3] = 0x00U;
|
||||
|
||||
instance->raw_bytes[4] = (uint8_t)((instance->generic.serial >> 16) & 0xFFU);
|
||||
instance->raw_bytes[5] = (uint8_t)((instance->generic.serial >> 8) & 0xFFU);
|
||||
instance->raw_bytes[6] = (uint8_t)( instance->generic.serial & 0xFFU);
|
||||
|
||||
uint8_t crypt[FORD_V3_CRYPT_LEN] = {0};
|
||||
crypt[4] = instance->generic.btn;
|
||||
crypt[5] = (uint8_t)((instance->generic.cnt >> 8) & 0xFFU);
|
||||
crypt[6] = (uint8_t)( instance->generic.cnt & 0xFFU);
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
instance->raw_bytes[FORD_V3_CRYPT_OFFSET + i] = crypt[i];
|
||||
}
|
||||
|
||||
ford_v3_encrypt(instance->raw_bytes);
|
||||
ford_v3_crc_process(instance->raw_bytes);
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_ford_v3_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFordV3* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolEncoderFordV3));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v3;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = FORD_V3_ENCODER_DEFAULT_REPEAT;
|
||||
instance->encoder.upload =
|
||||
calloc(FORD_V3_ENC_ALLOC_ELEMS, sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v3_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV3* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV3* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = FORD_V3_ENCODER_DEFAULT_REPEAT;
|
||||
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_check(temp_str);
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) break;
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) break;
|
||||
SubGhzProtocolStatus g = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, FORD_V3_DATA_BITS);
|
||||
if(g != SubGhzProtocolStatusOk) {
|
||||
ret = g;
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t raw_tmp[FORD_V3_DATA_BYTES] = {0};
|
||||
if(flipper_format_read_hex(
|
||||
flipper_format, "RawBytes", raw_tmp, sizeof(raw_tmp))) {
|
||||
memcpy(instance->raw_bytes, raw_tmp, sizeof(raw_tmp));
|
||||
} else {
|
||||
ford_v3_encoder_rebuild_raw_from_payload(instance);
|
||||
}
|
||||
|
||||
if(!ford_v3_button_is_valid(instance->raw_bytes[FORD_V3_CRYPT_OFFSET + 4])) {
|
||||
if(!ford_v3_button_is_valid(instance->generic.btn)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat = FORD_V3_ENCODER_DEFAULT_REPEAT;
|
||||
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1)) {
|
||||
instance->encoder.repeat = repeat;
|
||||
}
|
||||
|
||||
ford_v3_encoder_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v3_stop(void* context) {
|
||||
furi_check(context);
|
||||
((SubGhzProtocolEncoderFordV3*)context)->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v3_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV3* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0U) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFordV3* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderFordV3));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v3;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_reset(void* context) {
|
||||
furi_check(context);
|
||||
ford_v3_decoder_reset_state((SubGhzProtocolDecoderFordV3*)context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_feed(
|
||||
void* context,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV3DecoderStepReset:
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepPreamble:
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
if(instance->preamble_count < FORD_V3_PREAMBLE_COUNT_MAX) {
|
||||
instance->preamble_count++;
|
||||
}
|
||||
} else if(!level && ford_v3_duration_is_long(duration)) {
|
||||
if(instance->preamble_count >= FORD_V3_PREAMBLE_MIN) {
|
||||
ford_v3_decoder_enter_sync_from_preamble(instance, level, duration);
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepSync:
|
||||
case FordV3DecoderStepData:
|
||||
if(!ford_v3_decoder_manchester_feed_pulse(instance, level, duration)) {
|
||||
if(duration >= FORD_V3_INTER_BURST_GAP_US) {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
instance->decoder.te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
uint32_t mix =
|
||||
((uint32_t)k[4] << 16) | ((uint32_t)k[5] << 8) | (uint32_t)k[6];
|
||||
mix ^= (uint32_t)instance->btn << 16;
|
||||
mix ^= (uint32_t)instance->counter16 << 8;
|
||||
mix ^= ((uint16_t)k[15] << 8) | k[16];
|
||||
|
||||
return (uint8_t)(
|
||||
(mix >> 0) ^ (mix >> 8) ^ (mix >> 16) ^ (mix >> 24) ^
|
||||
(uint8_t)(instance->counter16 >> 8) ^
|
||||
(uint8_t)(instance->crc_received >> 8));
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t serial = instance->generic.serial;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Serial", &serial, 1);
|
||||
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Btn", &btn, 1);
|
||||
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Cnt", &cnt, 1);
|
||||
|
||||
uint32_t crc = instance->crc_received;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "CRC", &crc, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(
|
||||
flipper_format, "RawBytes",
|
||||
instance->raw_bytes, FORD_V3_DATA_BYTES);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_ford_v3_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) return ret;
|
||||
|
||||
if(instance->generic.data_count_bit != FORD_V3_DATA_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t raw_tmp[FORD_V3_DATA_BYTES] = {0};
|
||||
if(flipper_format_read_hex(
|
||||
flipper_format, "RawBytes", raw_tmp, sizeof(raw_tmp))) {
|
||||
memcpy(instance->raw_bytes, raw_tmp, sizeof(raw_tmp));
|
||||
} else {
|
||||
instance->raw_bytes[0] = FORD_V3_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V3_SYNC_1;
|
||||
}
|
||||
|
||||
ford_v3_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_get_string(
|
||||
void* context,
|
||||
FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Raw:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
|
||||
"Sn:%06lX Btn:%02X [%s]\r\n"
|
||||
"Cnt:%u CRC:%s(%04X)\r\n"
|
||||
"Struct:%s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7],
|
||||
k[8], k[9], k[10], k[11], k[12], k[13], k[14], k[15], k[16],
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
ford_v3_button_name(instance->generic.btn),
|
||||
(unsigned)instance->counter16,
|
||||
instance->crc_ok ? "OK" : "BAD",
|
||||
(unsigned)instance->crc_received,
|
||||
instance->structure_ok ? "OK" : "BAD");
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v3_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v3_alloc,
|
||||
.free = subghz_protocol_decoder_ford_v3_free,
|
||||
.feed = subghz_protocol_decoder_ford_v3_feed,
|
||||
.reset = subghz_protocol_decoder_ford_v3_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_ford_v3_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ford_v3_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ford_v3_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ford_v3_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v3_alloc,
|
||||
.free = subghz_protocol_encoder_ford_v3_free,
|
||||
.deserialize = subghz_protocol_encoder_ford_v3_deserialize,
|
||||
.stop = subghz_protocol_encoder_ford_v3_stop,
|
||||
.yield = subghz_protocol_encoder_ford_v3_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ford_protocol_v3 = {
|
||||
.name = FORD_PROTOCOL_V3_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_ford_v3_decoder,
|
||||
.encoder = &subghz_protocol_ford_v3_encoder,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <furi.h>
|
||||
|
||||
|
||||
#define FORD_PROTOCOL_V3_NAME "Ford V3"
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v3;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_ford_v3_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v3_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v3_reset(void* context);
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_feed(
|
||||
void* context,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_get_string(
|
||||
void* context,
|
||||
FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_ford_v3_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_ford_v3_free(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_encoder_ford_v3_stop(void* context);
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v3_yield(void* context);
|
||||
|
||||
@@ -0,0 +1,984 @@
|
||||
#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++];
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#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,302 @@
|
||||
/**
|
||||
* landrover_rke.c
|
||||
* Land Rover RKE (Remote Keyless Entry) protocol — ported from Pandora DXL 5000 firmware
|
||||
* Target: Flipper Zero (SubGHz RAW / custom protocol plugin)
|
||||
*
|
||||
* Protocol ID in original firmware: 0x0E
|
||||
* Co-located in firmware with Ford/Jaguar (case 0x0E references 0xed58 "Ford/Jaguar"
|
||||
* then falls through to 0xed64 "Land Rover" — same baseband, different ID range).
|
||||
*
|
||||
* Frequency: 433.92 MHz (EU/RoW) or 315.00 MHz (North America)
|
||||
* Modulation: OOK, Fixed-width PWM (similar to Ford RKE / Microchip KEELOQ derivative)
|
||||
* Carrier: AM
|
||||
*
|
||||
* Frame structure (Land Rover Freelander 2 / Discovery 3-4 / Range Rover Sport ~2004-2013):
|
||||
* Preamble : 20 logic-1 pulses (carrier warmup)
|
||||
* Header : 1 logic-1 + sync gap (~9.6 ms LOW)
|
||||
* Payload : 66 bits, MSB-first, fixed-width PWM
|
||||
* [65:34] 32-bit KeeLoq encrypted hopping code
|
||||
* [33:18] 16-bit fixed serial number (high word)
|
||||
* [17:10] 8-bit fixed serial (low byte)
|
||||
* [9:6] 4-bit button code
|
||||
* 0x1=Lock, 0x2=Unlock, 0x4=Boot/Tailgate, 0x8=Panic
|
||||
* [5:2] 4-bit function bits (repeat count / battery low flags)
|
||||
* [1:0] 2-bit status (0x1=battery low, 0x2=repeat)
|
||||
* Repeated up to 4 times
|
||||
*
|
||||
* PWM timing (from firmware, FUN_000007cc + FUN_00000840 timer init):
|
||||
* Bit period : 1000 µs
|
||||
* Bit-1 : 700 µs HIGH + 300 µs LOW
|
||||
* Bit-0 : 300 µs HIGH + 700 µs LOW
|
||||
* Preamble pulse: 400 µs HIGH + 600 µs LOW
|
||||
* Sync gap : 400 µs HIGH + 9600 µs LOW
|
||||
* Tolerance : ±20%
|
||||
*
|
||||
* KeeLoq note:
|
||||
* The hopping code is encrypted with KeeLoq (Microchip HCS-series algorithm).
|
||||
* Full decryption requires the manufacturer key (not in the firmware binary —
|
||||
* it's provisioned per fob). This file implements the protocol framing layer;
|
||||
* a separate keeloq.c provides the cipher if you have the key.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Timing constants (microseconds)
|
||||
* ------------------------------------------------------------------------- */
|
||||
#define LR_PREAMBLE_HIGH_US 400u
|
||||
#define LR_PREAMBLE_LOW_US 600u
|
||||
#define LR_PREAMBLE_COUNT 20u
|
||||
|
||||
#define LR_SYNC_HIGH_US 400u
|
||||
#define LR_SYNC_LOW_US 9600u
|
||||
|
||||
#define LR_BIT_PERIOD_US 1000u
|
||||
#define LR_BIT1_HIGH_US 700u
|
||||
#define LR_BIT1_LOW_US 300u
|
||||
#define LR_BIT0_HIGH_US 300u
|
||||
#define LR_BIT0_LOW_US 700u
|
||||
|
||||
#define LR_REPEAT_GAP_US 12000u
|
||||
#define LR_REPEAT_COUNT 4u
|
||||
#define LR_TOLERANCE_PCT 20u
|
||||
|
||||
#define LR_FRAME_BITS 66u
|
||||
|
||||
/* Button codes (bits [9:6]) */
|
||||
#define LR_BTN_LOCK 0x1u
|
||||
#define LR_BTN_UNLOCK 0x2u
|
||||
#define LR_BTN_BOOT 0x4u
|
||||
#define LR_BTN_PANIC 0x8u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Data types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Decoded Land Rover RKE frame.
|
||||
* The hop_code is the raw 32-bit KeeLoq ciphertext — decrypt separately.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hop_code; /**< 32-bit KeeLoq encrypted hopping word */
|
||||
uint32_t serial; /**< 24-bit fixed serial number (bits [33:10]) */
|
||||
uint8_t button; /**< 4-bit button code (LR_BTN_*) */
|
||||
uint8_t func_bits; /**< 4-bit function/repeat flags */
|
||||
uint8_t status; /**< 2-bit status byte */
|
||||
bool valid; /**< true if frame geometry is correct */
|
||||
} LandRoverFrame;
|
||||
|
||||
/** Raw pulse buffer */
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} LandRoverRawBuf;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Internal helpers
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static bool lr_in_range(int32_t measured_us, uint32_t ref_us)
|
||||
{
|
||||
int32_t ref = (int32_t)ref_us;
|
||||
int32_t diff = measured_us - ref;
|
||||
if (diff < 0) diff = -diff;
|
||||
return (diff * 100) <= (ref * (int32_t)LR_TOLERANCE_PCT);
|
||||
}
|
||||
|
||||
static void lr_push(LandRoverRawBuf *buf, int32_t val)
|
||||
{
|
||||
if (buf->count < 512) buf->pulses[buf->count++] = val;
|
||||
}
|
||||
|
||||
static void lr_push_pair(LandRoverRawBuf *buf, uint32_t hi, uint32_t lo)
|
||||
{
|
||||
lr_push(buf, (int32_t)hi);
|
||||
lr_push(buf, -(int32_t)lo);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Encode
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* lr_encode() — encode a LandRoverFrame into a Flipper SubGHz RAW buffer.
|
||||
*
|
||||
* The hop_code field must already be KeeLoq-encrypted by the caller.
|
||||
* Emits LR_REPEAT_COUNT repetitions.
|
||||
*/
|
||||
void lr_encode(const LandRoverFrame *frame, LandRoverRawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
|
||||
/* Pack the 66-bit payload into a uint8_t array, MSB-first */
|
||||
/* Layout: [65:34]=hop_code [33:10]=serial [9:6]=button */
|
||||
/* [5:2]=func_bits [1:0]=status */
|
||||
uint8_t bits[66];
|
||||
memset(bits, 0, sizeof(bits));
|
||||
|
||||
/* hop_code: bits 65..34 */
|
||||
for (int i = 0; i < 32; i++) {
|
||||
bits[65 - i] = (frame->hop_code >> i) & 1u;
|
||||
}
|
||||
/* serial: bits 33..10 (24 bits) */
|
||||
for (int i = 0; i < 24; i++) {
|
||||
bits[33 - i] = (frame->serial >> i) & 1u;
|
||||
}
|
||||
/* button: bits 9..6 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bits[9 - i] = (frame->button >> i) & 1u;
|
||||
}
|
||||
/* func_bits: bits 5..2 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bits[5 - i] = (frame->func_bits >> i) & 1u;
|
||||
}
|
||||
/* status: bits 1..0 */
|
||||
bits[1] = (frame->status >> 1) & 1u;
|
||||
bits[0] = frame->status & 1u;
|
||||
|
||||
for (uint32_t rep = 0; rep < LR_REPEAT_COUNT; rep++) {
|
||||
/* Preamble: 20 pulses */
|
||||
for (uint32_t p = 0; p < LR_PREAMBLE_COUNT; p++) {
|
||||
lr_push_pair(buf, LR_PREAMBLE_HIGH_US, LR_PREAMBLE_LOW_US);
|
||||
}
|
||||
/* Sync */
|
||||
lr_push_pair(buf, LR_SYNC_HIGH_US, LR_SYNC_LOW_US);
|
||||
|
||||
/* Data bits, MSB-first (bit 65 first on air) */
|
||||
for (int b = 65; b >= 0; b--) {
|
||||
if (bits[b]) {
|
||||
lr_push_pair(buf, LR_BIT1_HIGH_US, LR_BIT1_LOW_US);
|
||||
} else {
|
||||
lr_push_pair(buf, LR_BIT0_HIGH_US, LR_BIT0_LOW_US);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inter-repetition gap */
|
||||
if (rep < LR_REPEAT_COUNT - 1) {
|
||||
lr_push(buf, -(int32_t)LR_REPEAT_GAP_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Decode
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* lr_decode() — decode a raw pulse buffer into a LandRoverFrame.
|
||||
*
|
||||
* Returns true if a geometrically valid frame was found (preamble + sync +
|
||||
* 66 bits all within timing tolerance). The hop_code will need KeeLoq
|
||||
* decryption by the caller to verify authenticity.
|
||||
*/
|
||||
bool lr_decode(const LandRoverRawBuf *buf, LandRoverFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
/* Look for sync: ~400 µs HIGH + ~9600 µs LOW */
|
||||
if (!lr_in_range( buf->pulses[i], LR_SYNC_HIGH_US)) continue;
|
||||
if (!lr_in_range(-buf->pulses[i + 1], LR_SYNC_LOW_US)) continue;
|
||||
|
||||
uint32_t j = i + 2;
|
||||
if (j + LR_FRAME_BITS * 2 > buf->count) continue;
|
||||
|
||||
uint8_t bits[66];
|
||||
bool ok = true;
|
||||
|
||||
for (uint32_t b = 0; b < LR_FRAME_BITS; b++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
|
||||
if (lr_in_range(hi, LR_BIT1_HIGH_US) && lr_in_range(lo, LR_BIT1_LOW_US)) {
|
||||
bits[65 - b] = 1;
|
||||
} else if (lr_in_range(hi, LR_BIT0_HIGH_US) && lr_in_range(lo, LR_BIT0_LOW_US)) {
|
||||
bits[65 - b] = 0;
|
||||
} else {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) continue;
|
||||
|
||||
/* Unpack — MSB of each field is highest-indexed bit */
|
||||
frame->hop_code = 0;
|
||||
for (int k = 0; k < 32; k++) {
|
||||
frame->hop_code |= (uint32_t)bits[65 - k] << (31 - k);
|
||||
}
|
||||
frame->serial = 0;
|
||||
for (int k = 0; k < 24; k++) {
|
||||
frame->serial |= (uint32_t)bits[33 - k] << (23 - k);
|
||||
}
|
||||
frame->button = 0;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
frame->button |= (uint8_t)bits[9 - k] << (3 - k);
|
||||
}
|
||||
frame->func_bits = 0;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
frame->func_bits |= (uint8_t)bits[5 - k] << (3 - k);
|
||||
}
|
||||
frame->status = (bits[1] << 1) | bits[0];
|
||||
frame->valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* KeeLoq stub
|
||||
*
|
||||
* Land Rover uses a KeeLoq-derived hopping code (same baseband as Ford/Jaguar,
|
||||
* firmware case 0x0E dispatches both via the same path before branching on
|
||||
* the serial-number range).
|
||||
*
|
||||
* To decrypt hop_code you need the 64-bit manufacturer key. Implement
|
||||
* keeloq_decrypt() in a separate keeloq.c (standard NLF cipher, widely
|
||||
* documented in Microchip AN-66265).
|
||||
*
|
||||
* extern uint32_t keeloq_decrypt(uint32_t ciphertext, uint64_t key);
|
||||
*
|
||||
* Typical Land Rover key derivation (normal learning, from public research):
|
||||
* uint64_t man_key = LR_MANUFACTURER_KEY; // provisioned, not in firmware
|
||||
* uint64_t dev_key = keeloq_learn_normal(man_key, serial);
|
||||
* uint32_t plain = keeloq_decrypt(frame.hop_code, dev_key);
|
||||
* // plain[15:0] = 16-bit counter
|
||||
* // plain[19:16] = button code (must match frame.button)
|
||||
* // plain[31:28] = discriminant (0x6 for LR)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Rolling counter validation
|
||||
* Land Rover receivers accept counter in window [last+1, last+32768]
|
||||
* ------------------------------------------------------------------------- */
|
||||
bool lr_counter_valid(uint16_t stored, uint16_t received)
|
||||
{
|
||||
uint16_t delta = (uint16_t)(received - stored);
|
||||
return (delta >= 1u && delta <= 32768u);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Flipper Zero SubGHz plugin glue — same pattern as honda_rke.c
|
||||
*
|
||||
* void flipper_lr_encode(SubGhzProtocolEncoder *enc, void *ctx) {
|
||||
* LandRoverFrame *f = (LandRoverFrame *)ctx;
|
||||
* LandRoverRawBuf raw;
|
||||
* lr_encode(f, &raw);
|
||||
* // feed raw to SubGHz RAW transmit
|
||||
* }
|
||||
*
|
||||
* bool flipper_lr_decode(SubGhzProtocolDecoder *dec,
|
||||
* const int32_t *pulses, uint32_t count, void *ctx) {
|
||||
* LandRoverRawBuf buf;
|
||||
* buf.count = count > 512 ? 512 : count;
|
||||
* memcpy(buf.pulses, pulses, buf.count * sizeof(int32_t));
|
||||
* LandRoverFrame frame;
|
||||
* return lr_decode(&buf, &frame);
|
||||
* }
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
/**
|
||||
* landrover_rke.h
|
||||
* Land Rover RKE protocol — Pandora DXL 5000 → Flipper Zero port
|
||||
* Protocol ID: 0x0E | 433.92 MHz / 315.00 MHz | OOK PWM | 66-bit KeeLoq frame
|
||||
*
|
||||
* NOTE: hop_code is the raw KeeLoq ciphertext. Use the existing
|
||||
* keeloq.c in the Flipper firmware to decrypt/verify.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Timing constants (microseconds)
|
||||
* ------------------------------------------------------------------------- */
|
||||
#define LR_PREAMBLE_HIGH_US 400u
|
||||
#define LR_PREAMBLE_LOW_US 600u
|
||||
#define LR_PREAMBLE_COUNT 20u
|
||||
#define LR_SYNC_HIGH_US 400u
|
||||
#define LR_SYNC_LOW_US 9600u
|
||||
#define LR_BIT1_HIGH_US 700u
|
||||
#define LR_BIT1_LOW_US 300u
|
||||
#define LR_BIT0_HIGH_US 300u
|
||||
#define LR_BIT0_LOW_US 700u
|
||||
#define LR_REPEAT_GAP_US 12000u
|
||||
#define LR_REPEAT_COUNT 4u
|
||||
#define LR_TOLERANCE_PCT 20u
|
||||
#define LR_FRAME_BITS 66u
|
||||
|
||||
/* Frequency options */
|
||||
#define LR_FREQ_EU_HZ 433920000ul
|
||||
#define LR_FREQ_US_HZ 315000000ul
|
||||
|
||||
/* Button codes — bits [9:6] of frame */
|
||||
#define LR_BTN_LOCK 0x1u
|
||||
#define LR_BTN_UNLOCK 0x2u
|
||||
#define LR_BTN_BOOT 0x4u /**< Boot / tailgate */
|
||||
#define LR_BTN_PANIC 0x8u
|
||||
|
||||
/* Status bits [1:0] */
|
||||
#define LR_STATUS_BATTERY_LOW 0x1u
|
||||
#define LR_STATUS_REPEAT 0x2u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Data types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Land Rover RKE frame.
|
||||
* hop_code must be KeeLoq-encrypted before encode, and can be decrypted
|
||||
* after decode using subghz_protocol_keeloq_decrypt() from the Flipper firmware.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hop_code; /**< 32-bit KeeLoq encrypted hopping word */
|
||||
uint32_t serial; /**< 24-bit fixed fob serial */
|
||||
uint8_t button; /**< 4-bit button code: LR_BTN_* */
|
||||
uint8_t func_bits; /**< 4-bit function/repeat flags */
|
||||
uint8_t status; /**< 2-bit status: LR_STATUS_* */
|
||||
bool valid; /**< true after decode if geometry is correct */
|
||||
} LandRoverFrame;
|
||||
|
||||
/** Raw pulse buffer */
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} LandRoverRawBuf;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* API
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Encode a LandRoverFrame into a SubGHz RAW pulse buffer.
|
||||
* hop_code must already be KeeLoq-encrypted by the caller.
|
||||
* Emits LR_REPEAT_COUNT (4) repetitions.
|
||||
*/
|
||||
void lr_encode(const LandRoverFrame *frame, LandRoverRawBuf *buf);
|
||||
|
||||
/**
|
||||
* Decode a raw pulse buffer into a LandRoverFrame.
|
||||
* Sets valid=true if frame geometry (preamble+sync+66 bits) passes timing checks.
|
||||
* Does NOT verify the KeeLoq hop code — do that separately.
|
||||
*/
|
||||
bool lr_decode(const LandRoverRawBuf *buf, LandRoverFrame *frame);
|
||||
|
||||
/**
|
||||
* Validate a received 16-bit KeeLoq counter (extracted from decrypted hop_code)
|
||||
* against the last stored value. Land Rover window: [stored+1, stored+32768].
|
||||
*/
|
||||
bool lr_counter_valid(uint16_t stored, uint16_t received);
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* KeeLoq integration hint
|
||||
*
|
||||
* After lr_decode() succeeds, decrypt and verify like this:
|
||||
*
|
||||
* uint64_t dev_key = keeloq_normal_learning(manufacturer_key, frame.serial);
|
||||
* uint32_t plain = keeloq_decrypt(frame.hop_code, dev_key);
|
||||
* uint16_t counter = plain & 0xFFFFu;
|
||||
* uint8_t btn_chk = (plain >> 16) & 0xFu; // must equal frame.button
|
||||
* uint8_t disc = (plain >> 28) & 0xFu; // 0x6 for Land Rover
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,726 @@
|
||||
#include "mazda_v0.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_mazda_v0_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
#define MAZDA_V0_UPLOAD_CAPACITY 0x184
|
||||
#define MAZDA_V0_GAP_US 0xCB20
|
||||
#define MAZDA_V0_SYNC_BYTE 0xD7
|
||||
#define MAZDA_V0_TAIL_BYTE 0x5A
|
||||
#define MAZDA_V0_PREAMBLE_ONES 16
|
||||
|
||||
// =============================================================================
|
||||
// STRUCT DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMazdaV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t preamble_pattern;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolDecoderMazdaV0;
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
typedef struct SubGhzProtocolEncoderMazdaV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolEncoderMazdaV0;
|
||||
//#endif
|
||||
|
||||
typedef enum {
|
||||
MazdaV0DecoderStepReset = 0,
|
||||
MazdaV0DecoderStepPreamble = 5,
|
||||
MazdaV0DecoderStepData = 6,
|
||||
} MazdaV0DecoderStep;
|
||||
|
||||
// =============================================================================
|
||||
// FUNCTION PROTOTYPES
|
||||
// =============================================================================
|
||||
|
||||
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event);
|
||||
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic);
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter);
|
||||
static bool mazda_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderMazdaV0* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool
|
||||
mazda_v0_append_byte(SubGhzProtocolEncoderMazdaV0* instance, size_t* index, uint8_t value);
|
||||
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance);
|
||||
//#endif
|
||||
static SubGhzProtocolStatus mazda_v0_write_display(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* protocol_name,
|
||||
uint8_t button);
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL INTERFACE DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_mazda_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_mazda_v0_alloc,
|
||||
.free = subghz_protocol_decoder_mazda_v0_free,
|
||||
.feed = subghz_protocol_decoder_mazda_v0_feed,
|
||||
.reset = subghz_protocol_decoder_mazda_v0_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_mazda_v0_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_mazda_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_mazda_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_mazda_v0_get_string,
|
||||
};
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_mazda_v0_alloc,
|
||||
.free = subghz_protocol_encoder_mazda_v0_free,
|
||||
.deserialize = subghz_protocol_encoder_mazda_v0_deserialize,
|
||||
.stop = subghz_protocol_encoder_mazda_v0_stop,
|
||||
.yield = subghz_protocol_encoder_mazda_v0_yield,
|
||||
};
|
||||
//#else
|
||||
//const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
|
||||
// .alloc = NULL,
|
||||
// .free = NULL,
|
||||
// .deserialize = NULL,
|
||||
// .stop = NULL,
|
||||
// .yield = NULL,
|
||||
//};
|
||||
//#endif
|
||||
|
||||
const SubGhzProtocol subghz_protocol_mazda_v0 = {
|
||||
.name = MAZDA_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_mazda_v0_decoder,
|
||||
.encoder = &subghz_protocol_mazda_v0_encoder,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// HELPERS
|
||||
// =============================================================================
|
||||
|
||||
static uint8_t mazda_v0_popcount8(uint8_t x) {
|
||||
uint8_t count = 0;
|
||||
while(x) {
|
||||
count += x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void mazda_v0_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
bytes[i] = (uint8_t)((data >> ((7 - i) * 8)) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t data = 0;
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
data = (data << 8) | bytes[i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
//#endif
|
||||
|
||||
static uint8_t mazda_v0_calculate_checksum(uint32_t serial, uint8_t button, uint32_t counter) {
|
||||
counter &= 0xFFFFFU;
|
||||
return (uint8_t)(((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) + ((serial >> 8) & 0xFF) +
|
||||
(serial & 0xFF) + ((counter >> 8) & 0xFF) + (counter & 0xFF) +
|
||||
((((counter >> 16) & 0x0F) | ((button & 0x0F) << 4)) & 0xFF));
|
||||
}
|
||||
|
||||
static const char* mazda_v0_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "LOCK";
|
||||
case 0x02:
|
||||
return "UNLOCK";
|
||||
case 0x04:
|
||||
return "BOOT";
|
||||
case 0x08:
|
||||
return "REMOTE";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event) {
|
||||
const uint32_t tol = (uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U;
|
||||
|
||||
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) < tol) {
|
||||
*event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
return true;
|
||||
}
|
||||
|
||||
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_long) < tol) {
|
||||
*event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic) {
|
||||
uint8_t data[8];
|
||||
mazda_v0_u64_to_bytes_be(generic->data, data);
|
||||
|
||||
const bool parity = (mazda_v0_popcount8(data[7]) & 1) != 0;
|
||||
const uint8_t limit = parity ? 6 : 5;
|
||||
const uint8_t mask = data[limit];
|
||||
|
||||
for(uint8_t i = 0; i < limit; i++) {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
|
||||
if(!parity) {
|
||||
data[6] ^= mask;
|
||||
}
|
||||
|
||||
const uint8_t counter_lo = (data[5] & 0x55) | (data[6] & 0xAA);
|
||||
const uint8_t counter_mid = (data[6] & 0x55) | (data[5] & 0xAA);
|
||||
|
||||
generic->serial = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) |
|
||||
((uint32_t)data[2] << 8) | (uint32_t)data[3];
|
||||
generic->btn = (data[4] >> 4) & 0x0F;
|
||||
generic->cnt = (((uint32_t)data[4] & 0x0F) << 16) | ((uint32_t)counter_mid << 8) |
|
||||
(uint32_t)counter_lo;
|
||||
generic->data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
|
||||
}
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter) {
|
||||
uint8_t data[8];
|
||||
|
||||
counter &= 0xFFFFFU;
|
||||
button &= 0x0F;
|
||||
|
||||
data[0] = (serial >> 24) & 0xFF;
|
||||
data[1] = (serial >> 16) & 0xFF;
|
||||
data[2] = (serial >> 8) & 0xFF;
|
||||
data[3] = serial & 0xFF;
|
||||
data[4] = (button << 4) | ((counter >> 16) & 0x0F);
|
||||
data[5] = (counter >> 8) & 0xFF;
|
||||
data[6] = counter & 0xFF;
|
||||
data[7] = mazda_v0_calculate_checksum(serial, button, counter);
|
||||
|
||||
const uint8_t stored_5 = (data[6] & 0x55) | (data[5] & 0xAA);
|
||||
const uint8_t stored_6 = (data[6] & 0xAA) | (data[5] & 0x55);
|
||||
const uint8_t xor_mask = stored_5 ^ stored_6;
|
||||
const bool replace_second = ((~mazda_v0_popcount8(data[7])) & 1) != 0;
|
||||
const uint8_t forward_mask = replace_second ? stored_5 : stored_6;
|
||||
|
||||
data[5] = replace_second ? stored_5 : xor_mask;
|
||||
data[6] = replace_second ? xor_mask : stored_6;
|
||||
|
||||
for(size_t i = 0; i < 5; i++) {
|
||||
data[i] ^= forward_mask;
|
||||
}
|
||||
|
||||
return mazda_v0_bytes_to_u64_be(data);
|
||||
}
|
||||
|
||||
static bool mazda_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderMazdaV0* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(*index >= MAZDA_V0_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[(*index)++] = level_duration_make(level, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
mazda_v0_append_byte(SubGhzProtocolEncoderMazdaV0* instance, size_t* index, uint8_t value) {
|
||||
if(*index + 16 > MAZDA_V0_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t te = subghz_protocol_mazda_v0_const.te_short;
|
||||
|
||||
for(int8_t bit = 7; bit >= 0; bit--) {
|
||||
const bool bit_value = ((value >> bit) & 1) != 0;
|
||||
if(!bit_value) {
|
||||
if(!mazda_v0_encoder_add_level(instance, index, false, te)) {
|
||||
return false;
|
||||
}
|
||||
if(!mazda_v0_encoder_add_level(instance, index, true, te)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(!mazda_v0_encoder_add_level(instance, index, true, te)) {
|
||||
return false;
|
||||
}
|
||||
if(!mazda_v0_encoder_add_level(instance, index, false, te)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
size_t index = 0;
|
||||
const uint64_t key64 = instance->generic.data;
|
||||
|
||||
for(size_t r = 0; r < 12; r++) {
|
||||
if(!mazda_v0_append_byte(instance, &index, 0xFF)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mazda_v0_append_byte(instance, &index, 0xFF) ||
|
||||
!mazda_v0_append_byte(instance, &index, 0xFF) ||
|
||||
!mazda_v0_append_byte(instance, &index, MAZDA_V0_SYNC_BYTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int bi = 0; bi < 8; bi++) {
|
||||
const uint8_t raw = (uint8_t)((key64 >> (56 - bi * 8)) & 0xFF);
|
||||
const uint8_t air = (uint8_t)~raw;
|
||||
if(!mazda_v0_append_byte(instance, &index, air)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mazda_v0_append_byte(instance, &index, MAZDA_V0_TAIL_BYTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.size_upload = index;
|
||||
|
||||
return true;
|
||||
}
|
||||
//#endif
|
||||
|
||||
static SubGhzProtocolStatus mazda_v0_write_display(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* protocol_name,
|
||||
uint8_t button) {
|
||||
SubGhzProtocolStatus status = SubGhzProtocolStatusOk;
|
||||
FuriString* display = furi_string_alloc();
|
||||
|
||||
furi_string_printf(display, "%s - %s", protocol_name, mazda_v0_get_button_name(button));
|
||||
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Disp", furi_string_get_cstr(display))) {
|
||||
status = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
furi_string_free(display);
|
||||
return status;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENCODER
|
||||
// =============================================================================
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMazdaV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_mazda_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.upload = malloc(MAZDA_V0_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mazda_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
do {
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
if(!temp_str) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
|
||||
if(load_st != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
uint32_t u32 = 0;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &u32, 1)) {
|
||||
instance->generic.serial = u32;
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
|
||||
instance->generic.btn = (uint8_t)u32;
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
|
||||
instance->generic.cnt = u32;
|
||||
}
|
||||
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||
instance->encoder.repeat = 10;
|
||||
}
|
||||
instance->generic.btn &= 0x0FU;
|
||||
instance->generic.cnt &= 0xFFFFFU;
|
||||
|
||||
instance->generic.data = mazda_v0_encode_key(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
|
||||
instance->generic.data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
|
||||
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
if(!mazda_v0_build_upload(instance)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->encoder.size_upload == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key_data[sizeof(uint64_t)];
|
||||
mazda_v0_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t chk =
|
||||
mazda_v0_calculate_checksum(instance->serial, instance->button, instance->count);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Checksum", &chk, 1);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mazda_v0_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration out = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
//#endif
|
||||
|
||||
// =============================================================================
|
||||
// DECODER
|
||||
// =============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazdaV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_mazda_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->manchester_state = ManchesterStateStart1;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
bool data = false;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case MazdaV0DecoderStepReset:
|
||||
if(level && ((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) <
|
||||
(uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepPreamble;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case MazdaV0DecoderStepPreamble:
|
||||
if(!mazda_v0_get_event(duration, level, &event)) {
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||
instance->preamble_pattern = (instance->preamble_pattern << 1) | (data ? 1 : 0);
|
||||
|
||||
if(data) {
|
||||
instance->preamble_count++;
|
||||
} else if(instance->preamble_count <= MAZDA_V0_PREAMBLE_ONES - 1U) {
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if((instance->preamble_pattern == MAZDA_V0_SYNC_BYTE) &&
|
||||
(instance->preamble_count > MAZDA_V0_PREAMBLE_ONES - 1U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MazdaV0DecoderStepData:
|
||||
if(!mazda_v0_get_event(duration, level, &event)) {
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, data);
|
||||
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found) {
|
||||
instance->generic.data = ~instance->decoder.decode_data;
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
if(mazda_v0_calculate_checksum(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt) ==
|
||||
(uint8_t)instance->generic.data) {
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
instance->manchester_state = ManchesterStateStart1;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t chk =
|
||||
mazda_v0_calculate_checksum(instance->serial, instance->button, instance->count);
|
||||
flipper_format_write_uint32(flipper_format, "Checksum", &chk, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Serial", &instance->serial, 1);
|
||||
|
||||
uint32_t temp = instance->button;
|
||||
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &instance->count, 1);
|
||||
|
||||
ret = mazda_v0_write_display(
|
||||
flipper_format, instance->generic.protocol_name, instance->button);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1);
|
||||
instance->generic.serial = instance->serial;
|
||||
|
||||
uint32_t btn_temp = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1);
|
||||
instance->button = (uint8_t)btn_temp;
|
||||
instance->generic.btn = instance->button;
|
||||
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &instance->count, 1);
|
||||
instance->generic.cnt = instance->count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
const uint8_t raw_crc = instance->generic.data & 0xFF;
|
||||
const uint8_t calc_crc = mazda_v0_calculate_checksum(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit CRC:%s\r\n"
|
||||
"Key: %016llX\r\n"
|
||||
"Sn: %08lX Btn: %02X - %s\r\n"
|
||||
"Cnt: %05lX Chk: %02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(raw_crc == calc_crc) ? "OK" : "BAD",
|
||||
(unsigned long long)instance->generic.data,
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
mazda_v0_get_button_name(instance->generic.btn),
|
||||
(unsigned long)(instance->generic.cnt & 0xFFFFFU),
|
||||
raw_crc);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#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>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
//#include "../defines.h"
|
||||
|
||||
#define MAZDA_PROTOCOL_V0_NAME "Mazda V0"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_mazda_v0;
|
||||
|
||||
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_mazda_v0_free(void* context);
|
||||
void subghz_protocol_decoder_mazda_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_mazda_v0_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_mazda_v0_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context);
|
||||
@@ -59,7 +59,6 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_vag,
|
||||
&subghz_protocol_porsche_cayenne,
|
||||
&subghz_protocol_ford_v0,
|
||||
&ford_protocol_v1,
|
||||
&subghz_protocol_psa,
|
||||
&subghz_protocol_fiat_spa,
|
||||
&subghz_protocol_fiat_marelli,
|
||||
@@ -72,15 +71,21 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_kia_v3_v4,
|
||||
&subghz_protocol_kia_v5,
|
||||
&subghz_protocol_kia_v6,
|
||||
&subghz_protocol_kia_v7,
|
||||
&subghz_protocol_suzuki,
|
||||
&subghz_protocol_mitsubishi_v0,
|
||||
&subghz_protocol_star_line,
|
||||
&subghz_protocol_scher_khan,
|
||||
&subghz_protocol_sheriff_cfm,
|
||||
// until fix &subghz_protocol_honda,
|
||||
&subghz_protocol_chrysler,
|
||||
&honda_static_protocol,
|
||||
//&subghz_protocol_honda,
|
||||
&subghz_protocol_kia_v7,
|
||||
&subghz_protocol_mazda_v0,
|
||||
//&honda_static_protocol,
|
||||
&ford_protocol_v1,
|
||||
&ford_protocol_v2,
|
||||
&ford_protocol_v3,
|
||||
&subghz_protocol_land_rover_v0,
|
||||
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
#include "kia_v3_v4.h"
|
||||
#include "kia_v5.h"
|
||||
#include "kia_v6.h"
|
||||
#include "kia_v7.h"
|
||||
#include "suzuki.h"
|
||||
#include "mitsubishi_v0.h"
|
||||
#include "mazda_siemens.h"
|
||||
@@ -81,5 +80,9 @@
|
||||
#include "sheriff_cfm.h"
|
||||
#include "chrysler.h"
|
||||
#include "honda_static.h"
|
||||
#include "mazda_v0.h"
|
||||
#include "kia_v7.h"
|
||||
#include "ford_v1.h"
|
||||
//#include "honda_pandora.h"
|
||||
#include "ford_v2.h"
|
||||
#include "ford_v3.h"
|
||||
#include "land_rover_v0.h"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =========================================================
|
||||
* PROTOCOL NAME
|
||||
* ========================================================= */
|
||||
|
||||
#define SUBGHZ_PROTOCOL_PSA2_NAME "PSA OLD"
|
||||
|
||||
/* =========================================================
|
||||
* FORWARD DECLARATIONS — opaque handles
|
||||
* ========================================================= */
|
||||
|
||||
typedef struct SubGhzProtocolDecoderPSA SubGhzProtocolDecoderPSA;
|
||||
typedef struct SubGhzProtocolEncoderPSA SubGhzProtocolEncoderPSA;
|
||||
|
||||
/* =========================================================
|
||||
* PROTOCOL DESCRIPTORS — exported singletons
|
||||
* ========================================================= */
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_psa_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_psa_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_psa2;
|
||||
|
||||
/* =========================================================
|
||||
* DECODER API
|
||||
* ========================================================= */
|
||||
|
||||
/**
|
||||
* Allocate a PSA decoder instance.
|
||||
*
|
||||
* @param environment SubGHz environment (may be NULL / unused)
|
||||
* @return Opaque pointer to SubGhzProtocolDecoderPSA
|
||||
*/
|
||||
void* subghz_protocol_decoder_psa2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free a PSA decoder instance.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset the decoder state machine to its initial state.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_reset(void* context);
|
||||
|
||||
/**
|
||||
* Feed one pulse/gap sample into the decoder state machine.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param level true = RF high (pulse), false = RF low (gap)
|
||||
* @param duration Duration of this level in microseconds
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Return a one-byte hash of the most recently decoded packet.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @return Hash byte
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_psa2_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize the most recently decoded packet into a FlipperFormat stream.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param ff Open FlipperFormat file handle
|
||||
* @param preset Radio preset in use
|
||||
* @return SubGhzProtocolStatusOk on success
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_psa2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* ff,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize a previously saved packet from a FlipperFormat stream
|
||||
* into the decoder instance.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param ff Open FlipperFormat file handle (positioned at start)
|
||||
* @return SubGhzProtocolStatusOk on success
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_psa2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* ff);
|
||||
|
||||
/**
|
||||
* Build a human-readable description of the most recently decoded packet.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param output FuriString to write the description into (cleared first)
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_get_string(void* context, FuriString* output);
|
||||
|
||||
/* =========================================================
|
||||
* ENCODER API
|
||||
* ========================================================= */
|
||||
|
||||
/**
|
||||
* Allocate a PSA encoder instance.
|
||||
*
|
||||
* @param environment SubGHz environment (may be NULL / unused)
|
||||
* @return Opaque pointer to SubGhzProtocolEncoderPSA
|
||||
*/
|
||||
void* subghz_protocol_encoder_psa2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free a PSA encoder instance.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_encoder_psa2_free(void* context);
|
||||
|
||||
/**
|
||||
* Load transmit data from a FlipperFormat stream into the encoder.
|
||||
* Rebuilds the upload buffer ready for transmission.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
* @param ff Open FlipperFormat file handle (will be rewound internally)
|
||||
* @return SubGhzProtocolStatusOk on success
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_psa2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* ff);
|
||||
|
||||
/**
|
||||
* Stop an in-progress transmission immediately.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_encoder_psa2_stop(void* context);
|
||||
|
||||
/**
|
||||
* Yield the next LevelDuration sample from the upload buffer.
|
||||
* Called repeatedly by the SubGHz radio driver during transmission.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
* @return Next LevelDuration, or level_duration_reset() when done
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_psa2_yield(void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user