Compare commits

...

1 Commits

Author SHA1 Message Date
d4rks1d33 46d7e1263c Updated ProtoPirate, new Zero-Mega version (amazing updates)
Build Dev Firmware / build (push) Successful in 17m44s
2026-06-30 20:47:24 -03:00
99 changed files with 8767 additions and 3033 deletions
+6 -2
View File
@@ -29,12 +29,16 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch
| ------------------------ | ------- | ------- | --------------- | ---------- | ------------------------------ | ------------ | --------------- |
| Chrysler V0 | ✅ | ✅ | PWM | AM650 | Rolling Code | Checksum | 315.00 / 433.92 |
| Fiat V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code (static emu only) | ❌ | 315.00 / 433.92 |
| Fiat V1 | ✅ | | Manchester | AM650 | Rolling Code | CRC8 | 315.00 / 433.92 |
| Fiat V1 | ✅ | | Manchester | AM650 | HITAG2 | XOR8 | 315.00 / 433.92 |
| Fiat V2 | ✅ | ❌ | Manchester | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
| Ford V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | ✅ + Checksum | 315.00 / 433.92 |
| Ford V3 | ✅ | ❌ | Manchester | AM650 | Rolling Code | ❌ | 434.25 |
| Honda V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
| Kia V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
| Mazda V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | Checksum | 315.00 / 433.92 |
| Porsche Touareg | ✅ | ❌ | PWM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | AM650 | XTEA/XOR | CRC8 | 315.00 / 433.92 |
| Renault V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code / Replay | Type/IC | 315.00 / 433.92 |
| StarLine | ✅ | ✅ | PWM | AM650 | KeeLoq | ❌ | 315.00 / 433.92 |
| Subaru | ✅ | ✅ | PPM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
| VAG (VW/Audi/Seat/Skoda) | ✅ | ✅ | Manchester | AM650 | AUT64/XTEA | ❌ | 434.42 |
@@ -55,7 +59,7 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch
| Kia V5 | ✅ | ✅ | PWM | FM476 | Rolling Code | ✅ | 315.00 / 433.92 |
| Kia V6 | ✅ | ✅ | Manchester | FM476 | AES128 | CRC8 | 315.00 / 433.92 |
| Kia V7 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 |
| Land Rover V0 | ✅ | ✅ | PWM | F4 | Rolling Code | Check+Tail | 315.00 / 433.92 |
| Honda V2 | ✅ | ✅ | PWM | F4 | Rolling Code | Check+Tail | 315.00 / 433.92 |
| Mazda V0 | ✅ | ✅ | Manchester | FM (F2?) | Rolling Code | Checksum | 315.00 / 433.92 |
| Mitsubishi V0 | ✅ | ❌ | PWM | FM476 | Rolling Code | ❌ | 315.00 / 433.92 |
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | FM (F3?) | XTEA/XOR | CRC8 | 315.00 / 433.92 |
+310 -7
View File
@@ -1,3 +1,35 @@
# --- Main app ---
import os
def ProtoPirateDefineEnabled(name, app_manifest_path=app_manifest_path):
defines_path = os.path.join(os.path.dirname(app_manifest_path), "defines.h")
with open(defines_path) as defines_file:
for line in defines_file:
parts = line.strip().split()
if len(parts) >= 2 and parts[0] == "#define" and parts[1] == name:
return True
return False
_ENABLE_TIMING_TUNER = ProtoPirateDefineEnabled("ENABLE_TIMING_TUNER_SCENE")
_MAIN_APP_SOURCES = [
"*.c*",
"!protocols/plugins",
"!scenes/plugins",
"!protocols",
"protocols/protocol_items.c",
"protocols/protocols_common.c",
"!raw_file_reader.c",
]
if not _ENABLE_TIMING_TUNER:
_MAIN_APP_SOURCES += [
"!protopirate_scene_timing_tuner.c",
"!protocol_timings.c",
]
App(
appid="proto_pirate",
name="ProtoPirate",
@@ -12,39 +44,62 @@ App(
fap_category="Sub-GHz",
fap_icon_assets="images",
fap_file_assets="keystore",
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=_MAIN_APP_SOURCES,
)
# --- RX protocol plugins ---
App(
appid="protopirate_am_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_am_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_am_plugin.c",
"protocols/protocols_common.c",
"protocols/aut64.c",
"protocols/chrysler_v0.c",
"protocols/fiat_v0.c",
"protocols/fiat_v1.c",
"protocols/fiat_v2.c",
"protocols/ford_v0.c",
"protocols/ford_v3.c",
"protocols/honda_v1.c",
"protocols/kia_v1.c",
"protocols/kia_v2.c",
"protocols/mazda_v0.c",
"protocols/porsche_touareg.c",
"protocols/psa.c",
"protocols/psa_crypto.c",
"protocols/renault_v0.c",
"protocols/subaru.c",
"protocols/vag.c",
"protocols/star_line.c",
],
fal_embedded=True,
)
App(
appid="protopirate_am_vag_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_am_vag_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_am_vag_plugin.c",
"protocols/protocols_common.c",
"protocols/aut64.c",
"protocols/vag.c",
],
fal_embedded=True,
)
App(
appid="protopirate_fm_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_fm_plugin.c",
"protocols/protocols_common.c",
@@ -55,11 +110,6 @@ App(
"protocols/kia_v5.c",
"protocols/kia_v6.c",
"protocols/kia_v7.c",
"protocols/ford_v1.c",
"protocols/ford_v2.c",
"protocols/ford_v3.c",
"protocols/honda_static.c",
"protocols/land_rover_v0.c",
"protocols/mazda_v0.c",
"protocols/mitsubishi_v0.c",
"protocols/psa.c",
@@ -68,6 +118,258 @@ App(
fal_embedded=True,
)
App(
appid="protopirate_fm_f4_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_f4_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_fm_f4_plugin.c",
"protocols/protocols_common.c",
"protocols/ford_v1.c",
"protocols/ford_v2.c",
"protocols/ford_v3.c",
"protocols/honda_v2.c",
],
fal_embedded=True,
)
App(
appid="protopirate_fm_honda1_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_honda1_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_fm_honda1_plugin.c",
"protocols/protocols_common.c",
"protocols/honda_static.c",
],
fal_embedded=True,
)
# --- TX protocol plugins ---
_TX_PLUGIN_SOURCES = [
"protocols/plugins/protopirate_tx_protocol_plugin.c",
"protocols/protocols_common.c",
]
def ProtoPirateTxProtocolPlugin(
key,
protocol_header,
protocol_name,
protocol_item,
protocol_sources,
App=App,
FlipperAppType=FlipperAppType,
tx_plugin_sources=_TX_PLUGIN_SOURCES,
):
App(
appid=f"protopirate_tx_{key}_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_tx_protocol_plugin_ep",
requires=["proto_pirate"],
cdefines=[
"PROTOPIRATE_PROTOCOL_TX_ONLY=1",
f"PP_TX_PROTOCOL_HEADER=\\\"../{protocol_header}\\\"",
f"PP_TX_PROTOCOL_NAME={protocol_name}",
f"PP_TX_PROTOCOL_ITEM={protocol_item}",
],
sources=tx_plugin_sources + [f"protocols/{source}" for source in protocol_sources],
fal_embedded=True,
)
ProtoPirateTxProtocolPlugin(
"chrysler_v0",
"chrysler_v0.h",
"CHRYSLER_PROTOCOL_V0_NAME",
"chrysler_protocol_v0",
["chrysler_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"fiat_v0",
"fiat_v0.h",
"FIAT_PROTOCOL_V0_NAME",
"fiat_protocol_v0",
["fiat_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"fiat_v1",
"fiat_v1.h",
"FIAT_V1_PROTOCOL_NAME",
"fiat_v1_protocol",
["fiat_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"ford_v0",
"ford_v0.h",
"FORD_PROTOCOL_V0_NAME",
"ford_protocol_v0",
["ford_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"ford_v1",
"ford_v1.h",
"FORD_PROTOCOL_V1_NAME",
"ford_protocol_v1",
["ford_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"ford_v2",
"ford_v2.h",
"FORD_PROTOCOL_V2_NAME",
"ford_protocol_v2",
["ford_v2.c"],
)
ProtoPirateTxProtocolPlugin(
"honda_static",
"honda_static.h",
"HONDA_STATIC_PROTOCOL_NAME",
"honda_static_protocol",
["honda_static.c"],
)
ProtoPirateTxProtocolPlugin(
"honda_v1",
"honda_v1.h",
"HONDA_V1_PROTOCOL_NAME",
"honda_v1_protocol",
["honda_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v0",
"kia_v0.h",
"KIA_PROTOCOL_V0_NAME",
"kia_protocol_v0",
["kia_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v1",
"kia_v1.h",
"KIA_PROTOCOL_V1_NAME",
"kia_protocol_v1",
["kia_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v2",
"kia_v2.h",
"KIA_PROTOCOL_V2_NAME",
"kia_protocol_v2",
["kia_v2.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v3_v4",
"kia_v3_v4.h",
"KIA_PROTOCOL_V3_V4_NAME",
"kia_protocol_v3_v4",
["keys.c", "kia_v3_v4.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v5",
"kia_v5.h",
"KIA_PROTOCOL_V5_NAME",
"kia_protocol_v5",
["keys.c", "kia_v5.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v6",
"kia_v6.h",
"KIA_PROTOCOL_V6_NAME",
"kia_protocol_v6",
["keys.c", "kia_v6.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v7",
"kia_v7.h",
"KIA_PROTOCOL_V7_NAME",
"kia_protocol_v7",
["kia_v7.c"],
)
ProtoPirateTxProtocolPlugin(
"honda_v2",
"honda_v2.h",
"HONDA_V2_PROTOCOL_NAME",
"honda_v2_protocol",
["honda_v2.c"],
)
ProtoPirateTxProtocolPlugin(
"mazda_v0",
"mazda_v0.h",
"MAZDA_PROTOCOL_V0_NAME",
"mazda_v0_protocol",
["mazda_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"psa",
"psa.h",
"PSA_PROTOCOL_NAME",
"psa_protocol",
["psa.c", "psa_crypto.c"],
)
ProtoPirateTxProtocolPlugin(
"renault_v0",
"renault_v0.h",
"RENAULT_PROTOCOL_V0_NAME",
"renault_v0_protocol",
["renault_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"star_line",
"star_line.h",
"SUBGHZ_PROTOCOL_STAR_LINE_NAME",
"subghz_protocol_star_line",
["star_line.c"],
)
ProtoPirateTxProtocolPlugin(
"subaru",
"subaru.h",
"SUBARU_PROTOCOL_NAME",
"subaru_protocol",
["subaru.c"],
)
ProtoPirateTxProtocolPlugin(
"vag",
"vag.h",
"VAG_PROTOCOL_NAME",
"vag_protocol",
["aut64.c", "vag.c"],
)
# --- Tool / scene plugins ---
App(
appid="protopirate_sub_decode_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_sub_decode_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_SUB_DECODE_PLUGIN_BUILD=1"],
sources=[
"scenes/protopirate_scene_sub_decode.c",
"helpers/raw_file_reader.c",
"protopirate_history.c",
"helpers/protopirate_storage.c",
"protocols/protocol_items.c",
"protocols/protocols_common.c",
],
fap_icon_assets="images",
fal_embedded=True,
)
if _ENABLE_TIMING_TUNER:
App(
appid="protopirate_timing_tuner_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_timing_tuner_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_TIMING_TUNER_PLUGIN_BUILD=1"],
sources=[
"scenes/protopirate_scene_timing_tuner.c",
"protocols/protocol_timings.c",
],
fal_embedded=True,
)
App(
appid="protopirate_emulate_plugin",
apptype=FlipperAppType.PLUGIN,
@@ -75,6 +377,7 @@ App(
requires=["proto_pirate"],
sources=[
"scenes/plugins/protopirate_emulate_plugin.c",
"protocols/protocol_items.c",
],
fal_embedded=True,
)
+14 -3
View File
@@ -1,10 +1,21 @@
#pragma once
//#define ENABLE_TIMING_TUNER_SCENE
//#define ENABLE_SUB_DECODE_SCENE
// #define ENABLE_TIMING_TUNER_SCENE
#define ENABLE_SUB_DECODE_SCENE
#define ENABLE_EMULATE_FEATURE
#if defined(ENABLE_EMULATE_FEATURE) && !defined(PROTOPIRATE_PROTOCOL_RX_ONLY)
#define PROTOPIRATE_WITH_ENCODER 1
#else
#define PROTOPIRATE_WITH_ENCODER 0
#endif
#ifndef PROTOPIRATE_PROTOCOL_TX_ONLY
#define PROTOPIRATE_WITH_DECODER 1
#else
#define PROTOPIRATE_WITH_DECODER 0
#endif
#define REMOVE_LOGS
#ifdef REMOVE_LOGS
@@ -0,0 +1,447 @@
#include "../protopirate_app_i.h"
#include "protopirate_txrx.h"
#include "../protocols/protocol_items.h"
#include <loader/firmware_api/firmware_api.h>
#include <stdio.h>
#include <string.h>
#define TAG "ProtoPirateProtocolPlugin"
#define PROTOPIRATE_TX_PLUGIN_PATH_MAX 160U
static const char* protopirate_get_registry_plugin_path(ProtoPirateProtocolRegistryRoute route) {
switch(route) {
case ProtoPirateProtocolRegistryRouteAMVag:
return APP_ASSETS_PATH("plugins/protopirate_am_vag_plugin.fal");
case ProtoPirateProtocolRegistryRouteFMDefault:
return APP_ASSETS_PATH("plugins/protopirate_fm_plugin.fal");
case ProtoPirateProtocolRegistryRouteFMF4:
return APP_ASSETS_PATH("plugins/protopirate_fm_f4_plugin.fal");
case ProtoPirateProtocolRegistryRouteFMHonda1:
return APP_ASSETS_PATH("plugins/protopirate_fm_honda1_plugin.fal");
case ProtoPirateProtocolRegistryRouteAMDefault:
default:
return APP_ASSETS_PATH("plugins/protopirate_am_plugin.fal");
}
}
static bool protopirate_build_tx_protocol_plugin_path(
const char* tx_key,
char* plugin_path,
size_t plugin_path_size) {
if(!tx_key || !plugin_path || plugin_path_size == 0U) {
return false;
}
int written = snprintf(
plugin_path,
plugin_path_size,
APP_ASSETS_PATH("plugins/protopirate_tx_%s_plugin.fal"),
tx_key);
return (written > 0) && ((size_t)written < plugin_path_size);
}
static const SubGhzProtocolRegistry protopirate_empty_protocol_registry = {
.items = NULL,
.size = 0,
};
void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx) {
furi_check(txrx);
if(txrx->environment) {
subghz_environment_set_protocol_registry(
txrx->environment, &protopirate_empty_protocol_registry);
}
txrx->protocol_registry = NULL;
if(txrx->protocol_plugin && txrx->protocol_plugin->release) {
txrx->protocol_plugin->release();
}
txrx->protocol_plugin = NULL;
if(txrx->protocol_plugin_manager) {
plugin_manager_free(txrx->protocol_plugin_manager);
txrx->protocol_plugin_manager = NULL;
}
if(txrx->plugin_resolver) {
composite_api_resolver_free(txrx->plugin_resolver);
txrx->plugin_resolver = NULL;
}
}
static bool protopirate_ensure_protocol_registry_plugin(
ProtoPirateApp* app,
ProtoPirateProtocolRegistryRoute route,
const SubGhzProtocolRegistry** registry) {
furi_check(app);
furi_check(app->txrx);
furi_check(registry);
*registry = NULL;
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Cannot load protocol plugin without radio environment");
return false;
}
if(app->txrx->protocol_plugin &&
app->txrx->protocol_plugin->kind == ProtoPirateProtocolPluginKindRx &&
app->txrx->protocol_plugin->registry && app->txrx->protocol_registry_route == route) {
*registry = app->txrx->protocol_plugin->registry;
return true;
}
if(app->txrx->protocol_plugin || app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver) {
protopirate_unload_protocol_plugin(app->txrx);
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin manager");
composite_api_resolver_free(resolver);
return false;
}
const char* plugin_path = protopirate_get_registry_plugin_path(route);
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load protocol plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || !plugin->registry) {
FURI_LOG_E(TAG, "Protocol plugin entry point is invalid");
if(plugin && plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
if(plugin->kind != ProtoPirateProtocolPluginKindRx) {
FURI_LOG_E(TAG, "Protocol plugin kind mismatch for RX route");
if(plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
if(plugin->route != route) {
FURI_LOG_E(
TAG, "Protocol plugin route mismatch (expected %d got %d)", route, plugin->route);
if(plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->txrx->plugin_resolver = resolver;
app->txrx->protocol_plugin_manager = manager;
app->txrx->protocol_plugin = plugin;
app->txrx->protocol_registry_route = route;
*registry = plugin->registry;
return true;
}
static bool protopirate_ensure_tx_protocol_plugin(
ProtoPirateApp* app,
const char* protocol_name,
const SubGhzProtocolRegistry** registry) {
furi_check(app);
furi_check(app->txrx);
furi_check(registry);
*registry = NULL;
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Cannot load TX protocol plugin without radio environment");
return false;
}
const ProtoPirateProtocolCatalogEntry* catalog_entry =
protopirate_protocol_catalog_find(protocol_name);
const char* registry_name = protopirate_protocol_catalog_canonical_name(protocol_name);
const char* tx_key = protopirate_protocol_catalog_tx_key(protocol_name);
char plugin_path[PROTOPIRATE_TX_PLUGIN_PATH_MAX];
if(catalog_entry && !tx_key) {
FURI_LOG_W(TAG, "TX disabled for %s: protocol catalog has no tx_key", registry_name);
return false;
}
if(!registry_name || !tx_key ||
!protopirate_build_tx_protocol_plugin_path(tx_key, plugin_path, sizeof(plugin_path))) {
FURI_LOG_E(TAG, "No TX protocol plugin for %s", protocol_name ? protocol_name : "?");
return false;
}
if(app->txrx->protocol_plugin &&
app->txrx->protocol_plugin->kind == ProtoPirateProtocolPluginKindTx &&
app->txrx->protocol_plugin->registry && app->txrx->protocol_plugin->protocol_name &&
strcmp(app->txrx->protocol_plugin->protocol_name, registry_name) == 0) {
*registry = app->txrx->protocol_plugin->registry;
return true;
}
if(app->txrx->protocol_plugin || app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver) {
protopirate_unload_protocol_plugin(app->txrx);
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate TX protocol plugin resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate TX protocol plugin manager");
composite_api_resolver_free(resolver);
return false;
}
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load TX protocol plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || plugin->kind != ProtoPirateProtocolPluginKindTx || !plugin->registry ||
plugin->registry->size == 0U || !plugin->protocol_name ||
strcmp(plugin->protocol_name, registry_name) != 0) {
FURI_LOG_E(TAG, "TX protocol plugin entry point is invalid for %s", registry_name);
if(plugin && plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const SubGhzProtocol* tx_protocol = plugin->registry->items[0];
if(!tx_protocol || !tx_protocol->encoder || !tx_protocol->encoder->alloc ||
!tx_protocol->encoder->deserialize || !tx_protocol->encoder->yield) {
FURI_LOG_E(TAG, "TX protocol plugin for %s has no encoder", registry_name);
if(plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->txrx->plugin_resolver = resolver;
app->txrx->protocol_plugin_manager = manager;
app->txrx->protocol_plugin = plugin;
*registry = plugin->registry;
return true;
}
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment || !app->txrx->preset) {
return true;
}
const char* preset_name = furi_string_get_cstr(app->txrx->preset->name);
ProtoPirateProtocolRegistryRoute route = protopirate_get_protocol_registry_route(
preset_name,
app->txrx->preset->frequency,
app->txrx->preset->data,
app->txrx->preset->data_size,
NULL);
bool route_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_plugin->kind != ProtoPirateProtocolPluginKindRx) ||
(app->txrx->protocol_registry_route != route);
if(route_changed) {
protopirate_rx_stack_teardown_for_registry_switch(app);
} else if(ensure_receiver_ready && !app->txrx->receiver) {
protopirate_rx_stack_teardown_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, route, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s protocol registry plugin",
protopirate_get_protocol_registry_route_name(route));
return false;
}
const bool registry_already_bound = (app->txrx->protocol_registry == registry);
if(!registry_already_bound) {
FURI_LOG_I(
TAG,
"Using %s protocol registry (%zu protocols)",
protopirate_get_protocol_registry_route_name(route),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
}
if(!ensure_receiver_ready) {
return true;
}
if(app->txrx->receiver) {
return true;
}
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
if(!app->txrx->receiver) {
FURI_LOG_E(
TAG,
"Failed to allocate receiver for %s registry",
protopirate_get_protocol_registry_route_name(route));
return false;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
return true;
}
static bool protopirate_ensure_receiver_allocated(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
if(app->txrx->receiver) {
return true;
}
if(!app->txrx->environment) {
return false;
}
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
if(!app->txrx->receiver) {
FURI_LOG_E(TAG, "Failed to allocate receiver after registry restore");
return false;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
return true;
}
bool protopirate_apply_protocol_registry_for_context(
ProtoPirateApp* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment) {
return false;
}
if(!preset_name && app->txrx->preset && app->txrx->preset->name) {
preset_name = furi_string_get_cstr(app->txrx->preset->name);
}
if(frequency == 0U && app->txrx->preset) {
frequency = app->txrx->preset->frequency;
}
if((!preset_data || preset_data_size == 0U) && app->txrx->preset) {
preset_data = app->txrx->preset->data;
preset_data_size = app->txrx->preset->data_size;
}
if(protocol_name && protocol_name[0] != '\0') {
const char* registry_name = protopirate_protocol_catalog_canonical_name(protocol_name);
if(!registry_name || !protopirate_protocol_catalog_can_tx(protocol_name)) {
FURI_LOG_E(TAG, "No TX protocol plugin for %s", protocol_name);
return false;
}
bool tx_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_plugin->kind != ProtoPirateProtocolPluginKindTx) ||
!app->txrx->protocol_plugin->protocol_name ||
strcmp(app->txrx->protocol_plugin->protocol_name, registry_name) != 0;
if(tx_changed) {
protopirate_rx_stack_teardown_for_registry_switch(app);
}
const SubGhzProtocolRegistry* tx_registry = NULL;
if(!protopirate_ensure_tx_protocol_plugin(app, registry_name, &tx_registry) ||
!tx_registry) {
FURI_LOG_E(TAG, "Failed to resolve TX protocol plugin for %s", protocol_name);
return false;
}
if(app->txrx->protocol_registry == tx_registry) {
return true;
}
FURI_LOG_I(
TAG,
"Switching active protocol registry to TX %s",
registry_name ? registry_name : "?");
subghz_environment_set_protocol_registry(app->txrx->environment, tx_registry);
app->txrx->protocol_registry = tx_registry;
return true;
}
ProtoPirateProtocolRegistryRoute route = protopirate_get_protocol_registry_route(
preset_name, frequency, preset_data, preset_data_size, NULL);
bool route_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_plugin->kind != ProtoPirateProtocolPluginKindRx) ||
(app->txrx->protocol_registry_route != route);
if(route_changed) {
protopirate_rx_stack_teardown_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, route, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s registry plugin for preset apply",
protopirate_get_protocol_registry_route_name(route));
return false;
}
if(app->txrx->protocol_registry == registry) {
return protopirate_ensure_receiver_allocated(app);
}
FURI_LOG_I(
TAG,
"Switching active protocol registry to %s (%zu protocols)",
protopirate_get_protocol_registry_route_name(route),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
return protopirate_ensure_receiver_allocated(app);
}
@@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct ProtoPirateApp ProtoPirateApp;
typedef struct ProtoPirateTxRx ProtoPirateTxRx;
void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx);
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready);
bool protopirate_apply_protocol_registry_for_context(
ProtoPirateApp* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name);
@@ -80,15 +80,9 @@ static void host_receiver_info_rebuild_widget(void* app) {
protopirate_receiver_info_rebuild_normal_widget(app);
}
#ifdef ENABLE_SUB_DECODE_SCENE
static void host_subdecode_signal_info_refresh(void* app) {
protopirate_subdecode_psa_bf_complete_refresh(app);
host_send_custom_event(app, ProtoPirateCustomEventSubDecodeUpdate);
}
#else
static void host_subdecode_signal_info_refresh(void* app) {
UNUSED(app);
}
#endif
static void host_scene_previous(void* app) {
ProtoPirateApp* a = (ProtoPirateApp*)app;
@@ -0,0 +1,269 @@
#include "protopirate_radio.h"
#include "../protopirate_app_i.h"
#include <furi.h>
#include <string.h>
#define TAG "ProtoPirateRadio"
static void protopirate_radio_free_receiver(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->receiver) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Receiver was NULL, skipping free");
#endif
return;
}
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing receiver %p", app->txrx->receiver);
#endif
subghz_receiver_free(app->txrx->receiver);
app->txrx->receiver = NULL;
}
static void protopirate_radio_free_environment(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Environment was NULL, skipping free");
#endif
return;
}
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing environment %p", app->txrx->environment);
#endif
subghz_environment_free(app->txrx->environment);
app->txrx->environment = NULL;
app->txrx->protocol_registry = NULL;
}
static void protopirate_radio_end_device(ProtoPirateApp* app, bool sleep_before_end) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->radio_device) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Radio device was NULL, skipping sleep/end");
#endif
return;
}
#ifndef REMOVE_LOGS
FURI_LOG_D(
TAG,
"Putting radio device to %s and ending: %p",
sleep_before_end ? "sleep" : "idle",
app->txrx->radio_device);
#endif
if(sleep_before_end) {
subghz_devices_sleep(app->txrx->radio_device);
} else {
subghz_devices_idle(app->txrx->radio_device);
}
radio_device_loader_end(app->txrx->radio_device);
app->txrx->radio_device = NULL;
}
static void protopirate_radio_reset_state(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
app->txrx->protocol_registry = NULL;
app->txrx->protocol_plugin = NULL;
app->txrx->protocol_registry_route = ProtoPirateProtocolRegistryRouteAMDefault;
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->radio_initialized = false;
}
static void protopirate_radio_init_cleanup(ProtoPirateApp* app, bool devices_initialized) {
furi_check(app);
furi_check(app->txrx);
protopirate_radio_free_receiver(app);
protopirate_radio_end_device(app, false);
protopirate_radio_free_environment(app);
protopirate_unload_protocol_plugin(app->txrx);
if(devices_initialized) {
subghz_devices_deinit();
}
protopirate_radio_reset_state(app);
}
bool protopirate_radio_init(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
FURI_LOG_I(TAG, "=== protopirate_radio_init called ===");
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
#endif
if(app->radio_initialized) {
const bool radio_ready = (app->txrx->environment != NULL) &&
(app->txrx->radio_device != NULL);
if(radio_ready) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Radio already initialized, returning true");
#endif
return true;
}
FURI_LOG_W(
TAG,
"Radio marked initialized but resources missing (env=%p device=%p), repairing",
app->txrx->environment,
app->txrx->radio_device);
protopirate_radio_deinit(app);
}
FURI_LOG_I(TAG, "Fresh radio init - allocating all components");
app->txrx->environment = subghz_environment_alloc();
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Failed to allocate environment!");
protopirate_radio_init_cleanup(app, false);
return false;
}
app->txrx->protocol_registry = NULL;
if(!protopirate_refresh_protocol_registry(app, false)) {
FURI_LOG_E(TAG, "Failed to configure protocol registry");
protopirate_radio_init_cleanup(app, false);
return false;
}
subghz_environment_load_keystore(app->txrx->environment, PROTOPIRATE_KEYSTORE_DIR_NAME);
subghz_devices_init();
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "SubGhz devices initialized");
#endif
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101);
if(!app->txrx->radio_device) {
FURI_LOG_W(TAG, "External CC1101 not found, trying internal radio");
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeInternal);
}
if(!app->txrx->radio_device) {
FURI_LOG_E(TAG, "Failed to initialize any radio device!");
protopirate_radio_init_cleanup(app, true);
return false;
}
#ifndef REMOVE_LOGS
const char* device_name = subghz_devices_get_name(app->txrx->radio_device);
bool is_external = device_name && strstr(device_name, "ext");
FURI_LOG_I(
TAG,
"Radio device initialized: %s (%s)",
device_name ? device_name : "unknown",
is_external ? "external" : "internal");
#endif
subghz_devices_reset(app->txrx->radio_device);
subghz_devices_idle(app->txrx->radio_device);
app->radio_initialized = true;
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
#endif
return true;
}
void protopirate_radio_deinit(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
FURI_LOG_I(TAG, "=== protopirate_radio_deinit called ===");
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
FURI_LOG_D(
TAG,
"Pointers: worker=%p, environment=%p, receiver=%p, history=%p, radio_device=%p",
app->txrx->worker,
app->txrx->environment,
app->txrx->receiver,
app->txrx->history,
app->txrx->radio_device);
#endif
bool has_radio_resources = app->radio_initialized || app->txrx->worker ||
app->txrx->environment || app->txrx->receiver ||
app->txrx->history || app->txrx->radio_device ||
app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver || app->txrx->protocol_plugin;
if(!has_radio_resources) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Radio resources were not initialized, returning");
#endif
return;
}
bool devices_initialized = app->radio_initialized || (app->txrx->radio_device != NULL);
if(app->txrx->worker && app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Stopping active RX, state=%d", app->txrx->txrx_state);
#endif
subghz_worker_stop(app->txrx->worker);
if(app->txrx->radio_device) {
subghz_devices_stop_async_rx(app->txrx->radio_device);
}
}
protopirate_radio_end_device(app, true);
if(devices_initialized) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Calling subghz_devices_deinit");
#endif
subghz_devices_deinit();
}
protopirate_radio_free_receiver(app);
protopirate_radio_free_environment(app);
protopirate_unload_protocol_plugin(app->txrx);
if(app->txrx->history) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing history %p", app->txrx->history);
#endif
protopirate_history_free(app->txrx->history);
app->txrx->history = NULL;
} else {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "History was NULL, skipping free");
#endif
}
if(app->txrx->worker) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker);
#endif
subghz_worker_free(app->txrx->worker);
app->txrx->worker = NULL;
} else {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
#endif
}
protopirate_radio_reset_state(app);
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
#endif
}
@@ -0,0 +1,8 @@
#pragma once
#include <stdbool.h>
typedef struct ProtoPirateApp ProtoPirateApp;
bool protopirate_radio_init(ProtoPirateApp* app);
void protopirate_radio_deinit(ProtoPirateApp* app);
@@ -0,0 +1,166 @@
#include "protopirate_saved_match.h"
#include "protopirate_storage.h"
#include "../protocols/protocols_common.h"
#include <storage/storage.h>
#include <string.h>
#define TAG "ProtoPirateMatch"
#define CNT_MATCH_MARGIN 50
bool protopirate_saved_match_signal(
FlipperFormat* received_ff,
FuriString* out_matched_name,
FuriString* out_matched_path) {
furi_check(received_ff);
furi_check(out_matched_name);
furi_check(out_matched_path);
FuriString* rx_protocol = furi_string_alloc();
FuriString* rx_key = furi_string_alloc();
uint32_t rx_serial = 0;
uint32_t rx_cnt = 0;
bool rx_has_serial = false;
bool rx_has_key = false;
bool rx_has_cnt = false;
flipper_format_rewind(received_ff);
if(!flipper_format_read_string(received_ff, FF_PROTOCOL, rx_protocol)) {
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return false;
}
flipper_format_rewind(received_ff);
if(flipper_format_read_uint32(received_ff, FF_SERIAL, &rx_serial, 1)) {
rx_has_serial = true;
}
if(!rx_has_serial) {
flipper_format_rewind(received_ff);
if(flipper_format_read_string(received_ff, FF_KEY, rx_key)) {
rx_has_key = true;
}
}
flipper_format_rewind(received_ff);
if(flipper_format_read_uint32(received_ff, FF_CNT, &rx_cnt, 1)) {
rx_has_cnt = true;
}
if(!rx_has_serial && !rx_has_key) {
FURI_LOG_D(TAG, "No Serial or Key in received signal, skip match");
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return false;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
File* dir = storage_file_alloc(storage);
bool found = false;
if(!storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) {
FURI_LOG_D(TAG, "Cannot open saved/ folder");
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return false;
}
FileInfo file_info;
char file_name[128];
while(storage_dir_read(dir, &file_info, file_name, sizeof(file_name))) {
if(file_info.flags & FSF_DIRECTORY) continue;
size_t name_len = strlen(file_name);
if(name_len < 5) continue;
if(strcmp(file_name + name_len - 4, PROTOPIRATE_APP_EXTENSION) != 0) continue;
if(strcmp(file_name, ".temp.psf") == 0) continue;
FuriString* saved_path =
furi_string_alloc_printf("%s/%s", PROTOPIRATE_APP_FOLDER, file_name);
FlipperFormat* saved_ff = flipper_format_file_alloc(storage);
if(!flipper_format_file_open_existing(saved_ff, furi_string_get_cstr(saved_path))) {
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
FuriString* saved_protocol = furi_string_alloc();
flipper_format_rewind(saved_ff);
bool protocol_match = flipper_format_read_string(saved_ff, FF_PROTOCOL, saved_protocol) &&
furi_string_cmp(rx_protocol, saved_protocol) == 0;
furi_string_free(saved_protocol);
if(!protocol_match) {
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
bool identity_match = false;
if(rx_has_serial) {
uint32_t saved_serial = 0;
flipper_format_rewind(saved_ff);
if(flipper_format_read_uint32(saved_ff, FF_SERIAL, &saved_serial, 1)) {
identity_match = (saved_serial == rx_serial);
}
} else {
FuriString* saved_key = furi_string_alloc();
flipper_format_rewind(saved_ff);
if(flipper_format_read_string(saved_ff, FF_KEY, saved_key)) {
identity_match = (furi_string_cmp(rx_key, saved_key) == 0);
}
furi_string_free(saved_key);
}
if(!identity_match) {
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
if(rx_has_cnt) {
uint32_t saved_cnt = 0;
flipper_format_rewind(saved_ff);
if(flipper_format_read_uint32(saved_ff, FF_CNT, &saved_cnt, 1)) {
int64_t diff = (int64_t)rx_cnt - (int64_t)saved_cnt;
if(diff < 0) diff = -diff;
if(diff > CNT_MATCH_MARGIN) {
FURI_LOG_D(
TAG,
"Cnt diff %lld > %d, skip %s",
(long long)diff,
CNT_MATCH_MARGIN,
file_name);
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
}
}
size_t ext_pos = name_len - 4;
furi_string_set_strn(out_matched_name, file_name, ext_pos);
furi_string_set(out_matched_path, saved_path);
found = true;
flipper_format_free(saved_ff);
furi_string_free(saved_path);
break;
}
storage_dir_close(dir);
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return found;
}
@@ -0,0 +1,9 @@
#pragma once
#include <furi.h>
#include <flipper_format/flipper_format.h>
bool protopirate_saved_match_signal(
FlipperFormat* received_ff,
FuriString* out_matched_name,
FuriString* out_matched_path);
@@ -1,5 +1,6 @@
// helpers/protopirate_settings.c
#include "protopirate_settings.h"
#include "protopirate_storage.h"
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <furi.h>
@@ -18,6 +19,7 @@ void protopirate_settings_set_defaults(ProtoPirateSettings* settings) {
settings->auto_save = false;
settings->hopping_enabled = false;
settings->emulate_feature_enabled = false;
settings->check_saved = false;
}
void protopirate_settings_load(ProtoPirateSettings* settings) {
@@ -42,12 +44,6 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
break;
}
if(version != SETTINGS_FILE_VERSION) {
FURI_LOG_W(TAG, "Unsupported settings version %lu", (unsigned long)version);
furi_string_free(header);
break;
}
if(furi_string_cmp_str(header, SETTINGS_FILE_HEADER) != 0) {
FURI_LOG_W(TAG, "Invalid settings file header");
furi_string_free(header);
@@ -56,6 +52,14 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
furi_string_free(header);
if(version != SETTINGS_FILE_VERSION) {
FURI_LOG_I(
TAG,
"Migrating settings from version %lu to %u",
(unsigned long)version,
SETTINGS_FILE_VERSION);
}
// Read frequency
if(!flipper_format_read_uint32(ff, FF_FREQUENCY, &settings->frequency, 1)) {
FURI_LOG_W(TAG, "Failed to read frequency, using default");
@@ -84,6 +88,11 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
FURI_LOG_W(TAG, "Failed to read TXPower, using default");
tx_power_temp = 0;
}
if(tx_power_temp > PROTOPIRATE_TX_POWER_MAX_INDEX) {
FURI_LOG_W(TAG, "TXPower %lu out of range, clamping", (unsigned long)tx_power_temp);
tx_power_temp = PROTOPIRATE_TX_POWER_MAX_INDEX;
}
settings->tx_power = (uint8_t)tx_power_temp;
// Read hopping
@@ -101,14 +110,21 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
}
settings->emulate_feature_enabled = (emulate_temp == 1);
uint32_t check_saved_temp = 0;
if(!flipper_format_read_uint32(ff, "CheckSaved", &check_saved_temp, 1)) {
check_saved_temp = 0;
}
settings->check_saved = (check_saved_temp == 1);
FURI_LOG_I(
TAG,
"Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
"Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d",
settings->frequency,
settings->preset_index,
settings->auto_save,
settings->hopping_enabled,
settings->emulate_feature_enabled);
settings->emulate_feature_enabled,
settings->check_saved);
} while(false);
@@ -120,12 +136,17 @@ void protopirate_settings_save(ProtoPirateSettings* settings) {
Storage* storage = furi_record_open(RECORD_STORAGE);
// Ensure directory exists
storage_simply_mkdir(storage, PROTOPIRATE_SETTINGS_DIR);
if(!storage_simply_mkdir(storage, PROTOPIRATE_SETTINGS_DIR)) {
FURI_LOG_W(TAG, "Settings directory could not be created");
}
FlipperFormat* ff = flipper_format_file_alloc(storage);
bool write_ok = false;
const char* tmp_path = PROTOPIRATE_SETTINGS_FILE ".tmp";
do {
if(!flipper_format_file_open_always(ff, PROTOPIRATE_SETTINGS_FILE)) {
if(!flipper_format_file_open_always(ff, tmp_path)) {
FURI_LOG_E(TAG, "Failed to open settings file for writing");
break;
}
@@ -170,17 +191,35 @@ void protopirate_settings_save(ProtoPirateSettings* settings) {
break;
}
uint32_t check_saved_temp = settings->check_saved ? 1 : 0;
if(!flipper_format_write_uint32(ff, "CheckSaved", &check_saved_temp, 1)) {
FURI_LOG_E(TAG, "Failed to write check saved");
break;
}
write_ok = true;
FURI_LOG_I(
TAG,
"Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
"Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d",
settings->frequency,
settings->preset_index,
settings->auto_save,
settings->hopping_enabled,
settings->emulate_feature_enabled);
settings->emulate_feature_enabled,
settings->check_saved);
} while(false);
flipper_format_free(ff);
if(write_ok) {
if(!protopirate_storage_commit_temp_file(storage, tmp_path, PROTOPIRATE_SETTINGS_FILE)) {
FURI_LOG_E(TAG, "Failed to commit settings file");
}
} else if(storage_file_exists(storage, tmp_path)) {
storage_simply_remove(storage, tmp_path);
}
furi_record_close(RECORD_STORAGE);
}
@@ -7,6 +7,8 @@
#define PROTOPIRATE_SETTINGS_FILE APP_DATA_PATH("settings.txt")
#define PROTOPIRATE_SETTINGS_DIR APP_DATA_PATH()
#define PROTOPIRATE_TX_POWER_MAX_INDEX 8U
typedef struct {
uint32_t frequency;
uint8_t preset_index;
@@ -14,6 +16,7 @@ typedef struct {
bool auto_save;
bool hopping_enabled;
bool emulate_feature_enabled;
bool check_saved;
} ProtoPirateSettings;
void protopirate_settings_load(ProtoPirateSettings* settings);
@@ -1,7 +1,11 @@
// helpers/protopirate_storage.c
#include "protopirate_storage.h"
#include "../defines.h"
#include "../protocols/protocol_items.h"
#include "../protocols/protocols_common.h"
#include <lib/flipper_format/flipper_format_i.h>
#include <toolbox/stream/stream.h>
#include <string.h>
#define TAG "ProtoPirateStorage"
@@ -12,21 +16,67 @@ bool protopirate_storage_init(void) {
return result;
}
void protopirate_storage_wipe_history_cache(void) {
bool protopirate_storage_commit_temp_file(
Storage* storage,
const char* tmp_path,
const char* final_path) {
furi_check(storage);
furi_check(tmp_path);
furi_check(final_path);
FuriString* backup_path = furi_string_alloc();
furi_string_printf(backup_path, "%s.bak", final_path);
const char* backup_cstr = furi_string_get_cstr(backup_path);
if(storage_file_exists(storage, backup_cstr)) {
storage_simply_remove(storage, backup_cstr);
}
if(storage_file_exists(storage, final_path)) {
if(storage_common_rename(storage, final_path, backup_cstr) != FSE_OK) {
FURI_LOG_E(TAG, "Failed to stage backup for %s", final_path);
furi_string_free(backup_path);
return false;
}
}
if(storage_common_rename(storage, tmp_path, final_path) != FSE_OK) {
FURI_LOG_E(TAG, "Failed to commit %s", final_path);
if(storage_file_exists(storage, backup_cstr)) {
if(storage_common_rename(storage, backup_cstr, final_path) != FSE_OK) {
FURI_LOG_E(TAG, "Failed to restore backup for %s", final_path);
}
}
if(storage_file_exists(storage, tmp_path)) {
storage_simply_remove(storage, tmp_path);
}
furi_string_free(backup_path);
return false;
}
if(storage_file_exists(storage, backup_cstr)) {
storage_simply_remove(storage, backup_cstr);
}
furi_string_free(backup_path);
return true;
}
static void protopirate_storage_remove_history_folder(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
FURI_LOG_I(TAG, "Wiped history cache");
}
furi_record_close(RECORD_STORAGE);
}
void protopirate_storage_wipe_history_cache(void) {
protopirate_storage_remove_history_folder();
FURI_LOG_I(TAG, "Wiped history cache");
}
void protopirate_storage_purge_temp_history_at_startup(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
}
furi_record_close(RECORD_STORAGE);
protopirate_storage_remove_history_folder();
}
bool protopirate_storage_ensure_history_folder(void) {
@@ -34,8 +84,8 @@ bool protopirate_storage_ensure_history_folder(void) {
return false;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER);
bool ok = storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER);
bool ok = storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER) &&
storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER);
furi_record_close(RECORD_STORAGE);
return ok;
}
@@ -122,6 +172,33 @@ bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString
return found;
}
bool protopirate_storage_get_capture_display_protocol(
FlipperFormat* flipper_format,
FuriString* protocol_name) {
furi_check(flipper_format);
furi_check(protocol_name);
FuriString* raw_protocol = furi_string_alloc();
bool have_protocol = false;
uint32_t protocol_type = 0U;
flipper_format_rewind(flipper_format);
have_protocol = flipper_format_read_string(flipper_format, FF_PROTOCOL, raw_protocol);
if(!have_protocol) {
furi_string_set(raw_protocol, "Unknown");
}
flipper_format_rewind(flipper_format);
flipper_format_read_uint32(flipper_format, FF_TYPE, &protocol_type, 1);
const char* display_name = protopirate_protocol_catalog_display_name(
furi_string_get_cstr(raw_protocol), protocol_type);
furi_string_set(protocol_name, display_name ? display_name : "Unknown");
furi_string_free(raw_protocol);
return have_protocol;
}
static const char* const protopirate_storage_base_u32_fields[] = {
"TE",
FF_SERIAL,
@@ -141,6 +218,7 @@ static const char* const protopirate_storage_tail_u32_fields[] = {
"DataHi",
"DataLo",
"RawCnt",
"Rolling",
"Encrypted",
"Decrypted",
"KIAVersion",
@@ -161,6 +239,89 @@ static bool
return flipper_format_get_value_count(flipper_format, key, count) && (*count > 0);
}
static bool protopirate_storage_stream_read_char(Stream* stream, char* out) {
uint8_t value = 0;
if(stream_read(stream, &value, 1U) != 1U) return false;
*out = (char)value;
return true;
}
static bool protopirate_storage_stream_write_char(Stream* stream, char value) {
return stream_write_char(stream, value) == 1U;
}
static bool protopirate_storage_copy_raw_value_line(
Stream* out_stream,
Stream* in_stream,
const char* key) {
const size_t key_len = strlen(key);
if(!key_len || !stream_rewind(in_stream) || !stream_seek(out_stream, 0, StreamOffsetFromEnd)) {
return protopirate_storage_fail("Stream", key);
}
bool copied = false;
while(!stream_eof(in_stream)) {
bool line_match = true;
bool line_ended = false;
for(size_t i = 0; i < key_len; i++) {
char c = '\0';
if(!protopirate_storage_stream_read_char(in_stream, &c)) {
return protopirate_storage_fail("Read", key);
}
if(c == '\n') {
line_match = false;
line_ended = true;
break;
}
if(c != key[i]) {
line_match = false;
}
}
if(line_ended) continue;
char c = '\0';
if(!protopirate_storage_stream_read_char(in_stream, &c)) {
return protopirate_storage_fail("Read", key);
}
if(c != ':') {
line_match = false;
}
if(line_match) {
if(stream_write(out_stream, (const uint8_t*)key, key_len) != key_len ||
!protopirate_storage_stream_write_char(out_stream, ':')) {
return protopirate_storage_fail("Write", key);
}
bool wrote_newline = false;
while(protopirate_storage_stream_read_char(in_stream, &c)) {
if(!protopirate_storage_stream_write_char(out_stream, c)) {
return protopirate_storage_fail("Write", key);
}
if(c == '\n') {
wrote_newline = true;
break;
}
}
if(!wrote_newline && !protopirate_storage_stream_write_char(out_stream, '\n')) {
return protopirate_storage_fail("Write", key);
}
copied = true;
continue;
}
while(c != '\n' && protopirate_storage_stream_read_char(in_stream, &c)) {
}
}
return copied ? true : protopirate_storage_fail("Read", key);
}
static bool protopirate_storage_copy_string_optional(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
@@ -253,29 +414,19 @@ static bool protopirate_storage_copy_u32_array(
const char* key,
uint32_t count,
uint32_t max_count) {
if(count >= max_count) {
if(count > max_count) {
FURI_LOG_E(TAG, "%s too large: %lu", key, (unsigned long)count);
return false;
}
uint32_t* data = malloc(sizeof(uint32_t) * count);
if(!data) {
FURI_LOG_E(TAG, "Malloc failed: %s (%lu u32)", key, (unsigned long)count);
Stream* in_stream = flipper_format_get_raw_stream(flipper_format);
Stream* out_stream = flipper_format_get_raw_stream(save_file);
if(!in_stream || !out_stream) {
FURI_LOG_E(TAG, "Raw stream missing: %s", key);
return false;
}
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, key, data, count)) {
protopirate_storage_fail("Read", key);
} else if(!flipper_format_write_uint32(save_file, key, data, count)) {
protopirate_storage_fail("Write", key);
} else {
status = true;
}
free(data);
return status;
return protopirate_storage_copy_raw_value_line(out_stream, in_stream, key);
}
static bool protopirate_storage_copy_u32_array_if_present(
@@ -299,29 +450,19 @@ static bool protopirate_storage_copy_hex_array_if_present(
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
return true;
}
if(count >= max_count) {
if(count > max_count) {
FURI_LOG_E(TAG, "%s too large: %lu", key, (unsigned long)count);
return false;
}
uint8_t* data = malloc(count);
if(!data) {
FURI_LOG_E(TAG, "Malloc failed: %s (%lu bytes)", key, (unsigned long)count);
Stream* in_stream = flipper_format_get_raw_stream(flipper_format);
Stream* out_stream = flipper_format_get_raw_stream(save_file);
if(!in_stream || !out_stream) {
FURI_LOG_E(TAG, "Raw stream missing: %s", key);
return false;
}
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_hex(flipper_format, key, data, count)) {
protopirate_storage_fail("Read", key);
} else if(!flipper_format_write_hex(save_file, key, data, count)) {
protopirate_storage_fail("Write", key);
} else {
status = true;
}
free(data);
return status;
return protopirate_storage_copy_raw_value_line(out_stream, in_stream, key);
}
static bool protopirate_storage_copy_key(
@@ -407,7 +548,7 @@ static bool protopirate_storage_write_capture_data(
protopirate_storage_base_u32_fields,
COUNT_OF(protopirate_storage_base_u32_fields)))
break;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key2", 8, NULL)) break;
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "Key2", 4)) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "KeyIdx")) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Seed")) break;
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "ValidationField", 2))
@@ -418,6 +559,9 @@ static bool protopirate_storage_write_capture_data(
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Fx")) break;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key1", 8, NULL)) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Check")) break;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Hitag2 Key", 6, NULL))
break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Hitag2 Epoch")) break;
if(!protopirate_storage_copy_u32_array_if_present(
save_file, flipper_format, "RAW_Data", 4096))
break;
@@ -438,6 +582,73 @@ static bool protopirate_storage_write_capture_data(
return status;
}
static bool protopirate_storage_write_capture_file(
Storage* storage,
FlipperFormat* flipper_format,
const char* path) {
FlipperFormat* save_file = flipper_format_file_alloc(storage);
bool ok = false;
do {
if(!flipper_format_file_open_new(save_file, path)) {
FURI_LOG_E(TAG, "Failed to create file: %s", path);
break;
}
if(!flipper_format_write_header_cstr(
save_file, "Flipper SubGhz Key File", PROTOPIRATE_APP_FILE_VERSION)) {
FURI_LOG_E(TAG, "Failed to write header");
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
ok = true;
} while(false);
flipper_format_free(save_file);
return ok;
}
static bool protopirate_storage_save_capture_atomic(
Storage* storage,
FlipperFormat* flipper_format,
const char* full_path) {
FuriString* tmp_path = furi_string_alloc();
furi_check(tmp_path);
furi_string_printf(tmp_path, "%s.tmp", full_path);
const char* tmp_cstr = furi_string_get_cstr(tmp_path);
bool ok = false;
bool write_ok = false;
do {
if(storage_file_exists(storage, tmp_cstr)) {
storage_simply_remove(storage, tmp_cstr);
}
if(!protopirate_storage_write_capture_file(storage, flipper_format, tmp_cstr)) {
break;
}
write_ok = true;
if(!protopirate_storage_commit_temp_file(storage, tmp_cstr, full_path)) {
break;
}
ok = true;
} while(false);
if(!write_ok && storage_file_exists(storage, tmp_cstr)) {
storage_simply_remove(storage, tmp_cstr);
}
furi_string_free(tmp_path);
return ok;
}
bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path) {
furi_check(flipper_format);
furi_check(full_path);
@@ -448,37 +659,12 @@ bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, con
}
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* save_file = flipper_format_file_alloc(storage);
bool result = false;
do {
// Remove if it already exists (overwrite)
if(storage_file_exists(storage, full_path)) {
storage_simply_remove(storage, full_path);
}
if(!flipper_format_file_open_new(save_file, full_path)) {
FURI_LOG_E(TAG, "Failed to create file: %s", full_path);
break;
}
if(!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) {
FURI_LOG_E(TAG, "Failed to write header");
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
result = true;
FURI_LOG_I(TAG, "Saved capture to %s", full_path);
} while(false);
flipper_format_free(save_file);
bool result = protopirate_storage_save_capture_atomic(storage, flipper_format, full_path);
furi_record_close(RECORD_STORAGE);
if(result) {
FURI_LOG_I(TAG, "Saved capture to %s", full_path);
}
return result;
}
@@ -513,35 +699,16 @@ bool protopirate_storage_save_capture(
}
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* save_file = flipper_format_file_alloc(storage);
bool result = false;
do {
if(!flipper_format_file_open_new(save_file, furi_string_get_cstr(file_path))) {
FURI_LOG_E(TAG, "Failed to create file");
break;
}
if(!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) {
FURI_LOG_E(TAG, "Failed to write header");
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
if(out_path) furi_string_set(out_path, file_path);
result = true;
FURI_LOG_I(TAG, "Saved capture to %s", furi_string_get_cstr(file_path));
} while(false);
flipper_format_free(save_file);
furi_string_free(file_path);
bool result = protopirate_storage_save_capture_atomic(
storage, flipper_format, furi_string_get_cstr(file_path));
furi_record_close(RECORD_STORAGE);
if(result) {
if(out_path) furi_string_set(out_path, file_path);
FURI_LOG_I(TAG, "Saved capture to %s", furi_string_get_cstr(file_path));
}
furi_string_free(file_path);
return result;
}
@@ -15,6 +15,11 @@
// Initialize storage (create folder if needed)
bool protopirate_storage_init(void);
bool protopirate_storage_commit_temp_file(
Storage* storage,
const char* tmp_path,
const char* final_path);
// Save a capture to a new file (auto-generated name)
bool protopirate_storage_save_capture(
FlipperFormat* flipper_format,
@@ -24,27 +29,19 @@ bool protopirate_storage_save_capture(
// Save a capture to a specific file path (user-chosen name)
bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path);
// Save to temp file for emulation
bool protopirate_storage_save_temp(FlipperFormat* flipper_format);
// Delete temp file
void protopirate_storage_delete_temp(void);
// Get next available filename for a protocol
bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename);
bool protopirate_storage_get_capture_display_protocol(
FlipperFormat* flipper_format,
FuriString* protocol_name);
// Delete a file
bool protopirate_storage_delete_file(const char* file_path);
// Load a file (caller must close with protopirate_storage_close_file)
FlipperFormat* protopirate_storage_load_file(const char* file_path);
// Close a loaded file (by protopirate_storage_load_file only)
void protopirate_storage_close_file(FlipperFormat* flipper_format);
// Check if file exists
bool protopirate_storage_file_exists(const char* file_path);
bool protopirate_storage_ensure_history_folder(void);
void protopirate_storage_purge_temp_history_at_startup(void);
@@ -0,0 +1,294 @@
#include "../protopirate_app_i.h"
#include "protopirate_psa_bf_host.h"
#include "radio_device_loader.h"
#include <loader/firmware_api/firmware_api.h>
#include <notification/notification_messages.h>
#define TAG "ProtoPirateToolScene"
#define SUB_DECODE_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_sub_decode_plugin.fal")
#ifdef ENABLE_TIMING_TUNER_SCENE
#define TIMING_TUNER_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_timing_tuner_plugin.fal")
#endif
static const char*
protopirate_tool_scene_plugin_path(ProtoPirateToolScenePluginKind kind) {
switch(kind) {
case ProtoPirateToolScenePluginKindSubDecode:
return SUB_DECODE_PLUGIN_PATH;
#ifdef ENABLE_TIMING_TUNER_SCENE
case ProtoPirateToolScenePluginKindTimingTuner:
return TIMING_TUNER_PLUGIN_PATH;
#endif
default:
return NULL;
}
}
static bool host_ensure_receiver_view(void* app) {
return protopirate_ensure_receiver_view((ProtoPirateApp*)app);
}
static bool host_ensure_widget(void* app) {
return protopirate_ensure_widget((ProtoPirateApp*)app);
}
static bool host_ensure_view_about(void* app) {
return protopirate_ensure_view_about((ProtoPirateApp*)app);
}
static bool host_radio_init(void* app) {
return protopirate_radio_init((ProtoPirateApp*)app);
}
static void host_rx_stack_resume_after_tx(void* app) {
protopirate_rx_stack_resume_after_tx((ProtoPirateApp*)app);
}
static void host_preset_init(
void* app,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size) {
protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size);
}
static bool host_refresh_protocol_registry(void* app, bool ensure_receiver_ready) {
return protopirate_refresh_protocol_registry((ProtoPirateApp*)app, ensure_receiver_ready);
}
static bool host_apply_protocol_registry_for_context(
void* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name) {
return protopirate_apply_protocol_registry_for_context(
(ProtoPirateApp*)app,
preset_name,
frequency,
preset_data,
preset_data_size,
protocol_name);
}
static void host_begin(void* app, uint8_t* preset_data) {
protopirate_begin((ProtoPirateApp*)app, preset_data);
}
static uint32_t host_rx(void* app, uint32_t frequency) {
return protopirate_rx((ProtoPirateApp*)app, frequency);
}
static void host_rx_end(void* app) {
protopirate_rx_end((ProtoPirateApp*)app);
}
static void host_get_frequency_modulation_str(
void* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size) {
protopirate_get_frequency_modulation_str(
(ProtoPirateApp*)app, frequency, frequency_size, modulation, modulation_size);
}
static bool host_psa_bf_plugin_ensure_loaded(void* app) {
return protopirate_psa_bf_plugin_ensure_loaded((ProtoPirateApp*)app);
}
static void host_psa_bf_context_release(void* app) {
protopirate_psa_bf_context_release((ProtoPirateApp*)app);
}
static const ProtoPirateToolSceneHostApi protopirate_tool_scene_host_api = {
.ensure_receiver_view = host_ensure_receiver_view,
.ensure_widget = host_ensure_widget,
.ensure_view_about = host_ensure_view_about,
.radio_init = host_radio_init,
.rx_stack_resume_after_tx = host_rx_stack_resume_after_tx,
.preset_init = host_preset_init,
.refresh_protocol_registry = host_refresh_protocol_registry,
.apply_protocol_registry_for_context = host_apply_protocol_registry_for_context,
.begin = host_begin,
.rx = host_rx,
.rx_end = host_rx_end,
.get_frequency_modulation_str = host_get_frequency_modulation_str,
.history_release_scratch = protopirate_history_release_scratch,
.radio_device_is_external = radio_device_loader_is_external,
.receiver_add_data_statusbar = protopirate_view_receiver_add_data_statusbar,
.receiver_get_idx_menu = protopirate_view_receiver_get_idx_menu,
.receiver_set_idx_menu = protopirate_view_receiver_set_idx_menu,
.receiver_set_callback = protopirate_view_receiver_set_callback,
.receiver_set_sub_decode_mode = protopirate_view_receiver_set_sub_decode_mode,
.receiver_set_sub_decode_progress = protopirate_view_receiver_set_sub_decode_progress,
.receiver_reset_menu = protopirate_view_receiver_reset_menu,
.receiver_sync_menu_from_history = protopirate_view_receiver_sync_menu_from_history,
.psa_bf_plugin_ensure_loaded = host_psa_bf_plugin_ensure_loaded,
.psa_bf_context_release = host_psa_bf_context_release,
};
static void protopirate_tool_scene_plugin_unload(ProtoPirateApp* app) {
furi_check(app);
app->tool_scene_plugin = NULL;
if(app->tool_scene_plugin_manager) {
plugin_manager_free(app->tool_scene_plugin_manager);
app->tool_scene_plugin_manager = NULL;
}
if(app->tool_scene_plugin_resolver) {
composite_api_resolver_free(app->tool_scene_plugin_resolver);
app->tool_scene_plugin_resolver = NULL;
}
}
static bool protopirate_tool_scene_plugin_ensure_loaded(
ProtoPirateApp* app,
ProtoPirateToolScenePluginKind kind) {
furi_check(app);
if(app->tool_scene_plugin && app->tool_scene_plugin->kind == kind) {
return true;
}
if(app->tool_scene_plugin) {
if(app->tool_scene_plugin->release) {
app->tool_scene_plugin->release(app);
}
protopirate_tool_scene_plugin_unload(app);
}
const char* plugin_path = protopirate_tool_scene_plugin_path(kind);
if(!plugin_path) {
FURI_LOG_E(TAG, "No tool scene plugin path for kind %d", (int)kind);
return false;
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate tool scene resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_TOOL_SCENE_PLUGIN_APP_ID,
PROTOPIRATE_TOOL_SCENE_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate tool scene plugin manager");
composite_api_resolver_free(resolver);
return false;
}
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load tool scene plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateToolScenePlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || plugin->kind != kind || !plugin->set_host_api || !plugin->on_enter ||
!plugin->on_event || !plugin->on_exit) {
FURI_LOG_E(TAG, "Tool scene plugin entry point is invalid for kind %d", (int)kind);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->tool_scene_plugin_resolver = resolver;
app->tool_scene_plugin_manager = manager;
app->tool_scene_plugin = plugin;
app->tool_scene_plugin_kind = kind;
plugin->set_host_api(&protopirate_tool_scene_host_api);
return true;
}
static void protopirate_tool_scene_apply_pending_nav(ProtoPirateApp* app) {
furi_check(app);
const uint8_t nav = app->tool_scene_nav_pending;
if(nav == TOOL_SCENE_NAV_NONE) {
return;
}
const uint32_t target = app->tool_scene_nav_target;
app->tool_scene_nav_pending = TOOL_SCENE_NAV_NONE;
app->tool_scene_nav_target = 0;
switch(nav) {
case TOOL_SCENE_NAV_POP:
scene_manager_previous_scene(app->scene_manager);
break;
case TOOL_SCENE_NAV_NEXT:
scene_manager_next_scene(app->scene_manager, target);
break;
case TOOL_SCENE_NAV_SEARCH_PREVIOUS:
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, target);
break;
default:
break;
}
}
bool protopirate_tool_scene_on_enter(void* context, ProtoPirateToolScenePluginKind kind) {
ProtoPirateApp* app = context;
furi_check(app);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_NONE;
app->tool_scene_nav_target = 0;
if(!protopirate_tool_scene_plugin_ensure_loaded(app, kind) || !app->tool_scene_plugin) {
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
return false;
}
app->tool_scene_plugin->on_enter(app);
protopirate_tool_scene_apply_pending_nav(app);
return true;
}
bool protopirate_tool_scene_on_event(void* context, SceneManagerEvent event) {
ProtoPirateApp* app = context;
if(!app || !app->tool_scene_plugin || !app->tool_scene_plugin->on_event) {
return false;
}
const bool consumed = app->tool_scene_plugin->on_event(app, event);
protopirate_tool_scene_apply_pending_nav(app);
return consumed;
}
void protopirate_tool_scene_on_exit(void* context) {
ProtoPirateApp* app = context;
if(!app) return;
if(app->tool_scene_plugin) {
if(app->tool_scene_plugin->on_exit) {
app->tool_scene_plugin->on_exit(app);
}
if(app->tool_scene_plugin->release) {
app->tool_scene_plugin->release(app);
}
}
protopirate_tool_scene_plugin_unload(app);
}
void protopirate_tool_scene_plugin_release(ProtoPirateApp* app) {
if(!app) return;
if(app->tool_scene_plugin && app->tool_scene_plugin->release) {
app->tool_scene_plugin->release(app);
}
protopirate_tool_scene_plugin_unload(app);
}
@@ -1,35 +1,14 @@
// protopirate_app_i.c
#include "protopirate_app_i.h"
#include "protocols/protocol_items.h"
#include <loader/firmware_api/firmware_api.h>
#include "protopirate_txrx.h"
#include "../protopirate_app_i.h"
#include "protopirate_protocol_plugin_host.h"
#include "protopirate_radio.h"
#include "protopirate_views.h"
#include <stdio.h>
#define TAG "ProtoPirateTxRx"
static const char* protopirate_get_registry_plugin_path(ProtoPirateProtocolRegistryFilter filter) {
return (filter == ProtoPirateProtocolRegistryFilterFM) ?
APP_ASSETS_PATH("plugins/protopirate_fm_plugin.fal") :
APP_ASSETS_PATH("plugins/protopirate_am_plugin.fal");
}
static void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx) {
furi_check(txrx);
txrx->protocol_plugin = NULL;
txrx->protocol_registry = NULL;
if(txrx->protocol_plugin_manager) {
plugin_manager_free(txrx->protocol_plugin_manager);
txrx->protocol_plugin_manager = NULL;
}
if(txrx->plugin_resolver) {
composite_api_resolver_free(txrx->plugin_resolver);
txrx->plugin_resolver = NULL;
}
}
static void protopirate_teardown_receiver_stack_for_registry_switch(ProtoPirateApp* app) {
void protopirate_rx_stack_teardown_for_registry_switch(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
@@ -57,186 +36,6 @@ static void protopirate_teardown_receiver_stack_for_registry_switch(ProtoPirateA
}
}
static bool protopirate_ensure_protocol_registry_plugin(
ProtoPirateApp* app,
ProtoPirateProtocolRegistryFilter filter,
const SubGhzProtocolRegistry** registry) {
furi_check(app);
furi_check(app->txrx);
furi_check(registry);
*registry = NULL;
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Cannot load protocol plugin without radio environment");
return false;
}
if(app->txrx->protocol_plugin && app->txrx->protocol_plugin->registry &&
app->txrx->protocol_registry_filter == filter) {
*registry = app->txrx->protocol_plugin->registry;
return true;
}
if(app->txrx->protocol_plugin || app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver) {
protopirate_unload_protocol_plugin(app->txrx);
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin manager");
composite_api_resolver_free(resolver);
return false;
}
const char* plugin_path = protopirate_get_registry_plugin_path(filter);
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load protocol plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || !plugin->registry) {
FURI_LOG_E(TAG, "Protocol plugin entry point is invalid");
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
if(plugin->filter != filter) {
FURI_LOG_E(
TAG, "Protocol plugin filter mismatch (expected %d got %d)", filter, plugin->filter);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->txrx->plugin_resolver = resolver;
app->txrx->protocol_plugin_manager = manager;
app->txrx->protocol_plugin = plugin;
app->txrx->protocol_registry_filter = filter;
*registry = plugin->registry;
return true;
}
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment || !app->txrx->preset) {
return true;
}
ProtoPirateProtocolRegistryFilter filter = protopirate_get_protocol_registry_filter_for_preset(
app->txrx->preset->data, app->txrx->preset->data_size);
bool filter_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_registry_filter != filter);
if(filter_changed) {
protopirate_teardown_receiver_stack_for_registry_switch(app);
} else if(ensure_receiver_ready && !app->txrx->receiver) {
protopirate_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s protocol registry plugin",
protopirate_get_protocol_registry_filter_name(filter));
return false;
}
const bool registry_already_bound = (app->txrx->protocol_registry == registry);
if(!registry_already_bound) {
FURI_LOG_I(
TAG,
"Using %s protocol registry (%zu protocols)",
protopirate_get_protocol_registry_filter_name(filter),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
}
if(!ensure_receiver_ready) {
return true;
}
if(app->txrx->receiver) {
return true;
}
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
if(!app->txrx->receiver) {
FURI_LOG_E(
TAG,
"Failed to allocate receiver for %s registry",
protopirate_get_protocol_registry_filter_name(filter));
return false;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
return true;
}
bool protopirate_apply_protocol_registry_for_preset_data(
ProtoPirateApp* app,
const uint8_t* preset_data,
size_t preset_data_size) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment) {
return false;
}
ProtoPirateProtocolRegistryFilter filter =
protopirate_get_protocol_registry_filter_for_preset(preset_data, preset_data_size);
bool filter_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_registry_filter != filter);
if(filter_changed) {
protopirate_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s registry plugin for preset apply",
protopirate_get_protocol_registry_filter_name(filter));
return false;
}
if(app->txrx->protocol_registry == registry) {
return true;
}
FURI_LOG_I(
TAG,
"Switching active protocol registry to %s (%zu protocols)",
protopirate_get_protocol_registry_filter_name(filter),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
return true;
}
void protopirate_preset_init(
void* context,
const char* preset_name,
@@ -318,7 +117,12 @@ uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency) {
}
if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) {
furi_crash("ProtoPirate: Incorrect RX frequency.");
FURI_LOG_E(TAG, "RX start rejected: invalid frequency %lu", frequency);
if(app->notifications) {
notification_message(app->notifications, &sequence_error);
}
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
return 0;
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx ||
app->txrx->txrx_state == ProtoPirateTxRxStateSleep) {
@@ -418,17 +222,17 @@ void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app) {
}
}
void protopirate_hopper_update(ProtoPirateApp* app) {
bool protopirate_hopper_update(ProtoPirateApp* app) {
furi_check(app);
switch(app->txrx->hopper_state) {
case ProtoPirateHopperStateOFF:
case ProtoPirateHopperStatePause:
return;
return false;
case ProtoPirateHopperStateRSSITimeOut:
if(app->txrx->hopper_timeout != 0) {
app->txrx->hopper_timeout--;
return;
return false;
}
break;
default:
@@ -441,7 +245,7 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
if(rssi > -90.0f) {
app->txrx->hopper_timeout = 10;
app->txrx->hopper_state = ProtoPirateHopperStateRSSITimeOut;
return;
return false;
}
} else {
app->txrx->hopper_state = ProtoPirateHopperStateRunning;
@@ -451,7 +255,7 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
if(hopper_count == 0) {
app->txrx->hopper_state = ProtoPirateHopperStateOFF;
app->txrx->hopper_idx_frequency = 0;
return;
return false;
}
if(app->txrx->hopper_idx_frequency < hopper_count - 1) {
app->txrx->hopper_idx_frequency++;
@@ -462,12 +266,17 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE && app->txrx->receiver) {
subghz_receiver_reset(app->txrx->receiver);
if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE) {
app->txrx->preset->frequency =
subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency);
protopirate_rx(app, app->txrx->preset->frequency);
if(!protopirate_refresh_protocol_registry(app, true) || !app->txrx->receiver) {
FURI_LOG_E(TAG, "Failed to refresh registry while hopping");
return false;
}
return true;
}
return false;
}
void protopirate_tx(ProtoPirateApp* app, uint32_t frequency) {
@@ -0,0 +1,41 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <core/string.h>
typedef struct ProtoPirateApp ProtoPirateApp;
void protopirate_rx_stack_teardown_for_registry_switch(ProtoPirateApp* app);
void protopirate_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size);
void protopirate_get_frequency_modulation(
ProtoPirateApp* app,
FuriString* frequency,
FuriString* modulation);
void protopirate_get_frequency_modulation_str(
ProtoPirateApp* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size);
void protopirate_begin(ProtoPirateApp* app, uint8_t* preset_data);
uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency);
void protopirate_idle(ProtoPirateApp* app);
void protopirate_rx_end(ProtoPirateApp* app);
void protopirate_sleep(ProtoPirateApp* app);
bool protopirate_hopper_update(ProtoPirateApp* app);
void protopirate_tx(ProtoPirateApp* app, uint32_t frequency);
void protopirate_tx_stop(ProtoPirateApp* app);
void protopirate_release_shared_radio_state(ProtoPirateApp* app);
void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app);
void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app);
@@ -28,6 +28,7 @@ typedef enum {
// File management
ProtoPirateCustomEventReceiverInfoSave,
ProtoPirateCustomEventReceiverInfoSaveConfirm,
ProtoPirateCustomEventReceiverInfoUpdate,
ProtoPirateCustomEventReceiverInfoEmulate,
ProtoPirateCustomEventReceiverInfoBruteforceStart,
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
@@ -40,6 +41,7 @@ typedef enum {
// Sub decode
ProtoPirateCustomEventSubDecodeUpdate,
ProtoPirateCustomEventSubDecodeSave,
ProtoPirateCustomEventSubDecodeEmulate,
ProtoPirateCustomEventSubDecodeBruteforceStart,
ProtoPirateCustomEventPsaBruteforceComplete,
// File Browser
@@ -0,0 +1,135 @@
#include "protopirate_views.h"
#include "../protopirate_app_i.h"
#include <furi.h>
#define TAG "ProtoPirateViews"
bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) {
furi_check(app);
if(app->variable_item_list) {
return true;
}
app->variable_item_list = variable_item_list_alloc();
if(!app->variable_item_list) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewVariableItemList,
variable_item_list_get_view(app->variable_item_list));
return true;
}
bool protopirate_ensure_widget(ProtoPirateApp* app) {
furi_check(app);
if(app->widget) {
return true;
}
app->widget = widget_alloc();
if(!app->widget) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewWidget, widget_get_view(app->widget));
return true;
}
bool protopirate_ensure_text_input(ProtoPirateApp* app) {
furi_check(app);
if(app->text_input) {
return true;
}
app->text_input = text_input_alloc();
if(!app->text_input) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewTextInput, text_input_get_view(app->text_input));
return true;
}
bool protopirate_ensure_view_about(ProtoPirateApp* app) {
furi_check(app);
if(app->view_about) {
return true;
}
app->view_about = view_alloc();
if(!app->view_about) {
return false;
}
view_dispatcher_add_view(app->view_dispatcher, ProtoPirateViewAbout, app->view_about);
return true;
}
bool protopirate_ensure_receiver_view(ProtoPirateApp* app) {
furi_check(app);
if(app->protopirate_receiver) {
return true;
}
app->protopirate_receiver = protopirate_view_receiver_alloc(app->auto_save);
if(!app->protopirate_receiver) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewReceiver,
protopirate_view_receiver_get_view(app->protopirate_receiver));
return true;
}
void protopirate_views_free(ProtoPirateApp* app) {
furi_check(app);
if(app->submenu) {
FURI_LOG_D(TAG, "Removing submenu view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewSubmenu);
submenu_free(app->submenu);
app->submenu = NULL;
}
if(app->variable_item_list) {
FURI_LOG_D(TAG, "Removing variable_item_list view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList);
variable_item_list_free(app->variable_item_list);
app->variable_item_list = NULL;
}
if(app->view_about) {
FURI_LOG_D(TAG, "Removing about view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewAbout);
view_free(app->view_about);
app->view_about = NULL;
}
if(app->widget) {
FURI_LOG_D(TAG, "Removing widget view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewWidget);
widget_free(app->widget);
app->widget = NULL;
}
if(app->text_input) {
FURI_LOG_D(TAG, "Removing text_input view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewTextInput);
text_input_free(app->text_input);
app->text_input = NULL;
}
if(app->protopirate_receiver) {
FURI_LOG_D(TAG, "Removing receiver view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver);
protopirate_view_receiver_free(app->protopirate_receiver);
app->protopirate_receiver = NULL;
}
}
@@ -0,0 +1,12 @@
#pragma once
#include <stdbool.h>
typedef struct ProtoPirateApp ProtoPirateApp;
bool protopirate_ensure_variable_item_list(ProtoPirateApp* app);
bool protopirate_ensure_widget(ProtoPirateApp* app);
bool protopirate_ensure_text_input(ProtoPirateApp* app);
bool protopirate_ensure_view_about(ProtoPirateApp* app);
bool protopirate_ensure_receiver_view(ProtoPirateApp* app);
void protopirate_views_free(ProtoPirateApp* app);
@@ -2,25 +2,26 @@
#ifdef ENABLE_SUB_DECODE_SCENE
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <toolbox/stream/stream.h>
#include <lib/flipper_format/flipper_format.h>
#include <lib/flipper_format/flipper_format_i.h>
#include "../protocols/protocols_common.h"
#define TAG "RawFileReader"
#define RAW_READER_MAX_TOKEN_LEN 64U
#define RAW_READER_KEY "RAW_Data:"
static const char local_flipper_format_delimiter = ':';
static const char local_flipper_format_comment = '#';
static const char local_flipper_format_eoln = '\n';
static const char local_flipper_format_eolr = '\r';
struct FlipperFormat {
Stream* stream;
bool strict_mode;
};
RawFileReader* raw_file_reader_alloc(void) {
RawFileReader* reader = malloc(sizeof(RawFileReader));
furi_check(reader);
if(!reader) return NULL;
memset(reader, 0, sizeof(RawFileReader));
return reader;
}
@@ -31,84 +32,6 @@ void raw_file_reader_free(RawFileReader* reader) {
free(reader);
}
static inline bool local_flipper_format_stream_is_space(char c) {
return c == ' ' || c == '\t' || c == local_flipper_format_eolr;
}
static bool local_flipper_format_stream_read_value(Stream* stream, FuriString* value, bool* last) {
enum {
LeadingSpace,
ReadValue,
TrailingSpace
} state = LeadingSpace;
const size_t buffer_size = 32;
uint8_t buffer[buffer_size];
bool result = false;
bool error = false;
furi_string_reset(value);
while(true) {
size_t was_read = stream_read(stream, buffer, buffer_size);
if(was_read == 0) {
if(state != LeadingSpace && stream_eof(stream)) {
result = true;
*last = true;
} else {
error = true;
}
}
for(size_t i = 0; i < was_read; i++) {
const uint8_t data = buffer[i];
if(state == LeadingSpace) {
if(local_flipper_format_stream_is_space(data)) {
continue;
} else if(data == local_flipper_format_eoln) {
stream_seek(stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent);
error = true;
break;
} else {
state = ReadValue;
furi_string_push_back(value, data);
}
} else if(state == ReadValue) {
if(local_flipper_format_stream_is_space(data)) {
state = TrailingSpace;
} else if(data == local_flipper_format_eoln) {
if(!stream_seek(
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
error = true;
} else {
result = true;
*last = true;
}
break;
} else {
furi_string_push_back(value, data);
}
} else if(state == TrailingSpace) {
if(local_flipper_format_stream_is_space(data)) {
continue;
} else if(!stream_seek(
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
error = true;
} else {
*last = (data == local_flipper_format_eoln);
result = true;
}
break;
}
}
if(error || result) break;
}
return result;
}
static bool local_flipper_format_stream_read_valid_key(Stream* stream, FuriString* key) {
furi_string_reset(key);
const size_t buffer_size = 32;
@@ -165,8 +88,12 @@ static bool local_flipper_format_stream_read_valid_key(Stream* stream, FuriStrin
// just new symbol, reset the new_line flag
new_line = false;
if(accumulate) {
// and accumulate data if we want
furi_string_push_back(key, data);
if(furi_string_size(key) >= RAW_READER_MAX_TOKEN_LEN) {
accumulate = false;
} else {
furi_string_push_back(key, data);
}
}
}
}
@@ -202,36 +129,125 @@ static bool
return found;
}
static bool local_flipper_format_stream_get_value_count(
Stream* stream,
const char* key,
uint32_t* count,
bool strict_mode) {
bool result = false;
bool last = false;
static bool raw_file_reader_stream_read_char(RawFileReader* reader, char* out) {
if(!reader || !reader->stream || !out) {
return false;
}
FuriString* value;
value = furi_string_alloc();
if(reader->buffer_index >= reader->buffer_count) {
const size_t was_read =
stream_read(reader->stream, reader->buffer, RAW_READER_BUFFER_SIZE);
if(was_read == 0U) {
return false;
}
do {
if(!local_flipper_format_stream_seek_to_key(stream, key, strict_mode)) break;
*count = 0;
reader->buffer_count = (uint16_t)was_read;
reader->buffer_index = 0U;
}
result = true;
while(true) {
if(!local_flipper_format_stream_read_value(stream, value, &last)) {
result = false;
const uint8_t value = reader->buffer[reader->buffer_index++];
if(reader->stream_pos < UINT64_MAX) {
reader->stream_pos++;
}
*out = (char)value;
return true;
}
static bool raw_file_reader_at_logical_eof(const RawFileReader* reader) {
return reader && reader->stream && reader->buffer_index >= reader->buffer_count &&
stream_eof(reader->stream);
}
static void raw_file_reader_mark_finished(RawFileReader* reader) {
reader->file_finished = true;
reader->stream_pos = reader->file_size > 0U ? reader->file_size : reader->stream_pos;
}
static bool raw_file_reader_seek_next_values(RawFileReader* reader) {
const char* key = RAW_READER_KEY;
size_t match = 0U;
char c = '\0';
while(raw_file_reader_stream_read_char(reader, &c)) {
if(c == key[match]) {
match++;
if(key[match] == '\0') {
reader->in_line = true;
return true;
}
} else {
match = (c == key[0]) ? 1U : 0U;
}
}
raw_file_reader_mark_finished(reader);
return false;
}
static bool raw_file_reader_stream_next_int(RawFileReader* reader, int32_t* out) {
if(!reader || !out || !reader->stream) return false;
while(true) {
if(!reader->in_line && !raw_file_reader_seek_next_values(reader)) {
return false;
}
bool negative = false;
bool sign_seen = false;
bool have_digits = false;
int32_t value = 0;
char c = '\0';
while(raw_file_reader_stream_read_char(reader, &c)) {
if(c == '\r') {
continue;
}
if(c == '\n') {
reader->in_line = false;
if(have_digits) {
*out = negative ? -value : value;
return true;
}
break;
}
*count = *count + 1;
if(last) break;
if(!sign_seen && !have_digits && (c == ' ' || c == '\t' || c == ',')) {
continue;
}
if(!sign_seen && !have_digits && (c == '-' || c == '+')) {
negative = (c == '-');
sign_seen = true;
continue;
}
if(c >= '0' && c <= '9') {
have_digits = true;
value = (value * 10) + (c - '0');
continue;
}
if(have_digits) {
*out = negative ? -value : value;
return true;
}
negative = false;
sign_seen = false;
}
} while(false);
if(have_digits) {
raw_file_reader_mark_finished(reader);
*out = negative ? -value : value;
return true;
}
furi_string_free(value);
return result;
if(raw_file_reader_at_logical_eof(reader)) {
raw_file_reader_mark_finished(reader);
return false;
}
}
}
bool raw_file_reader_open(RawFileReader* reader, const char* file_path) {
@@ -287,26 +303,43 @@ bool raw_file_reader_open(RawFileReader* reader, const char* file_path) {
reader->buffer_count = 0;
reader->buffer_index = 0;
reader->file_finished = false;
reader->current_level = true;
reader->in_line = false;
FURI_LOG_I(TAG, "Opened RAW file: %s", file_path);
reader->count = 0;
uint32_t temp_count = 0;
while(local_flipper_format_stream_get_value_count(
reader->ff->stream, "RAW_Data", &temp_count, reader->ff->strict_mode)) {
//reader->file_finished = true;
reader->count += temp_count;
reader->stream = flipper_format_get_raw_stream(reader->ff);
if(!reader->stream) {
FURI_LOG_E(TAG, "Missing raw stream");
raw_file_reader_close(reader);
return false;
}
flipper_format_rewind(reader->ff);
if(!local_flipper_format_stream_seek_to_key(reader->stream, "RAW_Data", false)) {
FURI_LOG_E(TAG, "RAW file has no samples");
raw_file_reader_close(reader);
return false;
}
reader->in_line = true;
reader->data_start_offset = stream_tell(reader->stream);
reader->stream_pos = reader->data_start_offset;
reader->file_size = stream_size(reader->stream);
if(reader->file_size == 0) {
FileInfo file_info = {0};
if(storage_common_stat(reader->storage, file_path, &file_info) == FSE_OK) {
reader->file_size = file_info.size;
}
}
FURI_LOG_I(
TAG,
"Opened RAW file for streaming decode: %s",
file_path);
return true;
}
void raw_file_reader_close(RawFileReader* reader) {
if(!reader) return;
reader->stream = NULL;
if(reader->ff) {
flipper_format_free(reader->ff);
reader->ff = NULL;
@@ -320,44 +353,26 @@ void raw_file_reader_close(RawFileReader* reader) {
reader->storage = NULL;
reader->buffer_count = 0;
reader->buffer_index = 0;
reader->count = 0;
reader->in_line = false;
reader->file_finished = false;
}
static bool raw_file_reader_load_chunk(RawFileReader* reader) {
if(reader->file_finished) return false;
size_t to_read = (reader->count < RAW_READER_BUFFER_SIZE) ? reader->count :
RAW_READER_BUFFER_SIZE;
if(!flipper_format_read_int32(reader->ff, "RAW_Data", reader->buffer, to_read)) {
reader->file_finished = true;
return false;
}
reader->buffer_count = to_read;
reader->buffer_index = 0;
reader->count -= to_read;
return true;
reader->file_size = 0;
reader->data_start_offset = 0;
reader->stream_pos = 0;
}
bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* duration) {
if(!reader || !level || !duration) return false;
if(memmgr_get_free_heap() < 1024) {
if(reader->buffer_index >= reader->buffer_count && memmgr_get_free_heap() < 1024) {
FURI_LOG_E(TAG, "Not enough memory to continue reading");
return false;
}
if(reader->buffer_index >= reader->buffer_count) {
if(!raw_file_reader_load_chunk(reader)) {
return false;
}
int32_t value = 0;
if(!raw_file_reader_stream_next_int(reader, &value)) {
return false;
}
int32_t value = reader->buffer[reader->buffer_index++];
if(value >= 0) {
*level = true;
*duration = (uint32_t)value;
@@ -371,6 +386,22 @@ bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* dura
bool raw_file_reader_is_finished(RawFileReader* reader) {
if(!reader) return true;
return reader->file_finished && (reader->buffer_index >= reader->buffer_count);
return reader->file_finished && reader->buffer_index >= reader->buffer_count;
}
uint8_t raw_file_reader_get_progress(const RawFileReader* reader) {
if(!reader) return 0;
if(reader->file_finished && reader->buffer_index >= reader->buffer_count) return 100;
if(reader->file_size <= reader->data_start_offset) return 0;
const uint64_t total = reader->file_size - reader->data_start_offset;
const uint64_t current_pos = reader->stream_pos;
uint64_t done = 0;
if(current_pos > reader->data_start_offset) {
done = current_pos - reader->data_start_offset;
}
if(done >= total) return 100;
return (uint8_t)((done * 100ULL) / total);
}
#endif // ENABLE_SUB_DECODE_SCENE
@@ -5,19 +5,23 @@
#include <furi.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <toolbox/stream/stream.h>
#define RAW_READER_BUFFER_SIZE 512
#define RAW_READER_BUFFER_SIZE 2048U
typedef struct {
Storage* storage;
FlipperFormat* ff;
int32_t buffer[RAW_READER_BUFFER_SIZE];
size_t buffer_count;
size_t buffer_index;
uint32_t count;
Stream* stream;
uint8_t buffer[RAW_READER_BUFFER_SIZE];
uint16_t buffer_count;
uint16_t buffer_index;
bool in_line;
bool file_finished;
bool current_level;
bool storage_opened;
uint64_t file_size;
uint64_t data_start_offset;
uint64_t stream_pos;
} RawFileReader;
RawFileReader* raw_file_reader_alloc(void);
@@ -26,4 +30,5 @@ bool raw_file_reader_open(RawFileReader* reader, const char* file_path);
void raw_file_reader_close(RawFileReader* reader);
bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* duration);
bool raw_file_reader_is_finished(RawFileReader* reader);
uint8_t raw_file_reader_get_progress(const RawFileReader* reader);
#endif // ENABLE_SUB_DECODE_SCENE
@@ -215,7 +215,7 @@ static void chrysler_v0_decoder_commit(SubGhzProtocolDecoderChrysler_V0* instanc
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t chrysler_v0_payload_get_bit(const uint8_t payload[10], uint8_t index) {
const uint8_t byte = payload[index >> 3U];
@@ -295,7 +295,7 @@ const SubGhzProtocolDecoder subghz_protocol_chrysler_v0_decoder = {
.get_string = subghz_protocol_decoder_chrysler_v0_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_chrysler_v0_encoder = {
.alloc = subghz_protocol_encoder_chrysler_v0_alloc,
.free = pp_encoder_free,
@@ -318,15 +318,23 @@ const SubGhzProtocol chrysler_protocol_v0 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_chrysler_v0_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_chrysler_v0_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_chrysler_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -31,7 +31,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_chrysler_v0_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_chrysler_v0_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format);
@@ -1,11 +1,8 @@
#include "fiat_v0.h"
#include "protocols_common.h"
#include "../protopirate_app_i.h"
#include <lib/toolbox/manchester_decoder.h>
#include <inttypes.h>
#define TAG "FiatProtocolV0"
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
#define FIAT_V0_PREAMBLE_PAIRS 150
#define FIAT_V0_GAP_US 800
#define FIAT_V0_TOTAL_BURSTS 3
@@ -42,6 +39,17 @@ typedef enum {
FiatV0DecoderStepData = 2,
} FiatV0DecoderStep;
static const char* fiat_v0_display_suffix(uint8_t endbyte) {
const uint8_t low_nibble = endbyte & 0x0FU;
if((low_nibble >= 0x04U) && (low_nibble <= 0x07U)) {
return "Lock";
}
if((low_nibble >= 0x08U) && (low_nibble <= 0x0BU)) {
return "Unlock";
}
return "??";
}
static void fiat_v0_finish_packet(struct SubGhzProtocolDecoderFiatV0* instance) {
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
@@ -78,7 +86,7 @@ const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
.get_string = subghz_protocol_decoder_fiat_v0_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
.free = pp_encoder_free,
@@ -102,14 +110,22 @@ const SubGhzProtocol fiat_protocol_v0 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_fiat_v0_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_fiat_v0_encoder,
#else
.encoder = NULL,
#endif
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -128,7 +144,7 @@ void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) {
furi_check(instance);
@@ -220,7 +236,7 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -239,10 +255,9 @@ SubGhzProtocolStatus
uint32_t bit_count = 0;
if(pp_encoder_read_bit(flipper_format, allowed_bits, 2, &bit_count) !=
SubGhzProtocolStatusOk) {
instance->generic.data_count_bit = 71; // legacy default for garbage Bit values
} else {
instance->generic.data_count_bit = bit_count;
return SubGhzProtocolStatusErrorValueBitCount;
}
instance->generic.data_count_bit = bit_count;
uint64_t key = 0;
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &key)) {
@@ -489,51 +504,64 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
do {
if(!flipper_format_write_uint32(flipper_format, FF_FREQUENCY, &preset->frequency, 1))
break;
ret = pp_serialize_fields(
flipper_format,
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
instance->fix,
instance->endbyte,
instance->hop,
0);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
if(!flipper_format_write_string_cstr(
flipper_format, FF_PRESET, furi_string_get_cstr(preset->name)))
break;
uint32_t endbyte_ff = instance->endbyte;
if(!flipper_format_write_uint32(flipper_format, "EndByte", &endbyte_ff, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
if(!flipper_format_write_string_cstr(
flipper_format, FF_PROTOCOL, instance->generic.protocol_name))
break;
uint32_t bits = instance->generic.data_count_bit;
if(!flipper_format_write_uint32(flipper_format, FF_BIT, &bits, 1)) break;
char key_str[20];
snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix);
if(!flipper_format_write_string_cstr(flipper_format, FF_KEY, key_str)) break;
if(pp_serialize_fields(
flipper_format,
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
instance->fix,
instance->endbyte,
instance->hop,
0) != SubGhzProtocolStatusOk)
break;
uint32_t endbyte_ff = instance->endbyte;
if(!flipper_format_write_uint32(flipper_format, "EndByte", &endbyte_ff, 1)) break;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
return pp_write_display(
flipper_format,
instance->generic.protocol_name,
fiat_v0_display_suffix(instance->endbyte));
}
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_fiat_v0_const.min_count_bit_for_found);
if(status != SubGhzProtocolStatusOk) {
return status;
}
instance->hop = (uint32_t)(instance->generic.data >> 32U);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFFU);
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
uint32_t endbyte_u32 = 0U;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "EndByte", &endbyte_u32, 1)) {
instance->endbyte = (uint8_t)(endbyte_u32 & 0x7FU);
} else {
pp_encoder_read_fields(flipper_format, NULL, &endbyte_u32, NULL, NULL);
instance->endbyte = (uint8_t)(endbyte_u32 & 0x7FU);
}
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
return status;
}
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output) {
@@ -12,6 +12,8 @@
#include "../defines.h"
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0;
typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0;
@@ -19,7 +21,6 @@ extern const SubGhzProtocol fiat_protocol_v0;
// Decoder functions
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v0_free(void* context);
void subghz_protocol_decoder_fiat_v0_reset(void* context);
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context);
File diff suppressed because it is too large Load Diff
@@ -5,28 +5,32 @@
#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 <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#include "../defines.h"
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat V1"
#define FIAT_V1_PROTOCOL_NAME "Fiat V1"
typedef struct SubGhzProtocolDecoderFiatMarelli SubGhzProtocolDecoderFiatMarelli;
typedef struct SubGhzProtocolDecoderFiatV1 SubGhzProtocolDecoderFiatV1;
typedef struct SubGhzProtocolEncoderFiatV1 SubGhzProtocolEncoderFiatV1;
extern const SubGhzProtocol fiat_v1_protocol;
void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_marelli_free(void* context);
void subghz_protocol_decoder_fiat_marelli_reset(void* context);
void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_marelli_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize(
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v1_reset(void* context);
void subghz_protocol_decoder_fiat_v1_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
subghz_protocol_decoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_fiat_v1_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
@@ -0,0 +1,417 @@
#include "fiat_v2.h"
#include "protocols_common.h"
#include <string.h>
#define TAG "FiatProtocolV2"
#define FIAT_V2_TE_SHORT 210U
#define FIAT_V2_TE_LONG 420U
#define FIAT_V2_TE_DELTA 100U
#define FIAT_V2_BOUNDARY_MIN_US 900U
#define FIAT_V2_WIRE_BITS 112U
#define FIAT_V2_WIRE_BYTES 14U
#define FIAT_V2_WIRE_CELLS (FIAT_V2_WIRE_BITS * 2U)
#define FIAT_V2_LOGICAL_BITS 112U
#define FIAT_V2_MARKER0 0x00U
#define FIAT_V2_MARKER1 0x01U
#define FIAT_V2_BTN_SHIFT 6U
#define FIAT_V2_BUTTON_LOCK 0x2U
#define FIAT_V2_BUTTON_UNLOCK 0x3U
#define FIAT_V2_BUTTON_TRUNK 0x1U
#define FIAT_V2_CNT_SHIFT 3U
#define FIAT_V2_FCA_TYPE_NIBBLE 0xD0U
#define FIAT_V2_RAW_FIELD "Raw"
#define FIAT_V2_HOP_FIELD "Hop"
#define FIAT_V2_BTN_FIELD "Btn"
static const SubGhzBlockConst subghz_protocol_fiat_v2_const = {
.te_short = FIAT_V2_TE_SHORT,
.te_long = FIAT_V2_TE_LONG,
.te_delta = FIAT_V2_TE_DELTA,
.min_count_bit_for_found = FIAT_V2_LOGICAL_BITS,
};
typedef enum {
FiatV2DecoderStepReset = 0,
FiatV2DecoderStepData = 1,
} FiatV2DecoderStep;
struct SubGhzProtocolDecoderFiatV2 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint8_t cells[FIAT_V2_WIRE_CELLS];
uint16_t cell_count;
uint8_t raw_data[FIAT_V2_WIRE_BYTES];
uint8_t last_raw_data[FIAT_V2_WIRE_BYTES];
bool last_raw_valid;
uint32_t uid;
uint32_t hop;
uint8_t button;
};
static bool fiat_v2_feed_data_pulse(
SubGhzProtocolDecoderFiatV2* instance,
bool level,
uint32_t duration);
static bool fiat_v2_frame_valid(const uint8_t raw[FIAT_V2_WIRE_BYTES]);
const SubGhzProtocolDecoder subghz_protocol_fiat_v2_decoder = {
.alloc = subghz_protocol_decoder_fiat_v2_alloc,
.free = pp_decoder_free_default,
.feed = subghz_protocol_decoder_fiat_v2_feed,
.reset = subghz_protocol_decoder_fiat_v2_reset,
.get_hash_data = subghz_protocol_decoder_fiat_v2_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_v2_serialize,
.deserialize = subghz_protocol_decoder_fiat_v2_deserialize,
.get_string = subghz_protocol_decoder_fiat_v2_get_string,
};
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_fiat_v2_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
#endif
const SubGhzProtocol fiat_v2_protocol = {
.name = FIAT_V2_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_fiat_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_fiat_v2_encoder,
#else
.encoder = NULL,
#endif
};
static bool fiat_v2_duration_is_short(uint32_t duration) {
return pp_is_short(duration, &subghz_protocol_fiat_v2_const);
}
static bool fiat_v2_duration_is_long(uint32_t duration) {
return pp_is_long(duration, &subghz_protocol_fiat_v2_const);
}
static bool fiat_v2_button_valid(uint8_t button) {
const uint8_t sel = button >> FIAT_V2_BTN_SHIFT;
return sel == FIAT_V2_BUTTON_LOCK || sel == FIAT_V2_BUTTON_UNLOCK ||
sel == FIAT_V2_BUTTON_TRUNK;
}
static const char* fiat_v2_button_name(uint8_t button) {
switch(button >> FIAT_V2_BTN_SHIFT) {
case FIAT_V2_BUTTON_LOCK:
return "Lock";
case FIAT_V2_BUTTON_UNLOCK:
return "Unlock";
case FIAT_V2_BUTTON_TRUNK:
return "Trunk";
default:
return "Unknown";
}
}
static uint32_t fiat_v2_uid(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
return ((uint32_t)raw[2] << 24U) | ((uint32_t)raw[3] << 16U) |
((uint32_t)raw[4] << 8U) | raw[5];
}
static bool fiat_v2_is_fca(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
return (raw[6] & 0xF0U) == FIAT_V2_FCA_TYPE_NIBBLE;
}
static uint32_t fiat_v2_hop(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(fiat_v2_is_fca(raw)) {
return ((uint32_t)raw[10] << 24U) | ((uint32_t)raw[11] << 16U) |
((uint32_t)raw[12] << 8U) | raw[13];
}
return ((uint32_t)raw[9] << 24U) | ((uint32_t)raw[10] << 16U) |
((uint32_t)raw[11] << 8U) | raw[12];
}
static uint32_t fiat_v2_counter(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(fiat_v2_is_fca(raw)) {
const uint32_t raw_cnt = ((uint32_t)raw[8] << 6U) | (uint32_t)(raw[9] >> 2U);
return (~raw_cnt) & 0x3FFFU;
}
const uint32_t raw_cnt =
((uint32_t)(raw[7] & 0x3FU) << 5U) | (uint32_t)(raw[8] >> FIAT_V2_CNT_SHIFT);
return (~raw_cnt) & 0x7FFU;
}
static bool fiat_v2_frame_valid(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(raw[0] != FIAT_V2_MARKER0 || raw[1] != FIAT_V2_MARKER1) {
return false;
}
if(!fiat_v2_button_valid(raw[7])) {
return false;
}
const uint32_t uid = fiat_v2_uid(raw);
return uid != 0U && uid != UINT32_MAX;
}
static void fiat_v2_clear_cells(SubGhzProtocolDecoderFiatV2* instance) {
instance->cell_count = 0U;
memset(instance->cells, 0, sizeof(instance->cells));
}
static void fiat_v2_decode_fields(SubGhzProtocolDecoderFiatV2* instance) {
instance->uid = fiat_v2_uid(instance->raw_data);
instance->button = instance->raw_data[7];
instance->hop = fiat_v2_hop(instance->raw_data);
instance->generic.serial = instance->uid;
instance->generic.btn = instance->button;
instance->generic.cnt = fiat_v2_counter(instance->raw_data);
instance->generic.data = ((uint64_t)instance->generic.serial << 32U) | instance->hop;
instance->generic.data_count_bit = FIAT_V2_LOGICAL_BITS;
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
}
static bool fiat_v2_commit(
SubGhzProtocolDecoderFiatV2* instance,
const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(!fiat_v2_frame_valid(raw)) {
return false;
}
if(instance->last_raw_valid && memcmp(instance->last_raw_data, raw, FIAT_V2_WIRE_BYTES) == 0) {
return true;
}
memcpy(instance->raw_data, raw, FIAT_V2_WIRE_BYTES);
memcpy(instance->last_raw_data, raw, FIAT_V2_WIRE_BYTES);
instance->last_raw_valid = true;
fiat_v2_decode_fields(instance);
FURI_LOG_D(
TAG,
"Accepted UID:%08lX Btn:%02X Cnt:%02lX Hop:%08lX",
(unsigned long)instance->uid,
instance->button,
(unsigned long)instance->generic.cnt,
(unsigned long)instance->hop);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
return true;
}
static bool fiat_v2_try_decode_window(SubGhzProtocolDecoderFiatV2* instance, bool invert) {
if(instance->cell_count != FIAT_V2_WIRE_CELLS) {
return false;
}
uint8_t raw[FIAT_V2_WIRE_BYTES] = {0};
for(uint8_t bit_index = 0U; bit_index < FIAT_V2_WIRE_BITS; bit_index++) {
const uint8_t first = instance->cells[bit_index * 2U];
const uint8_t second = instance->cells[bit_index * 2U + 1U];
if(first == second) {
return false;
}
bool bit = first != 0U;
if(invert) {
bit = !bit;
}
if(bit) {
raw[bit_index >> 3U] |= (uint8_t)(1U << (7U - (bit_index & 7U)));
}
}
return fiat_v2_commit(instance, raw);
}
static void fiat_v2_try_decode(SubGhzProtocolDecoderFiatV2* instance) {
if(fiat_v2_try_decode_window(instance, false)) {
return;
}
(void)fiat_v2_try_decode_window(instance, true);
}
static void fiat_v2_push_cell(SubGhzProtocolDecoderFiatV2* instance, bool level) {
if(instance->cell_count < FIAT_V2_WIRE_CELLS) {
instance->cells[instance->cell_count++] = level ? 1U : 0U;
} else {
memmove(instance->cells, &instance->cells[1], FIAT_V2_WIRE_CELLS - 1U);
instance->cells[FIAT_V2_WIRE_CELLS - 1U] = level ? 1U : 0U;
}
fiat_v2_try_decode(instance);
}
static bool fiat_v2_feed_data_pulse(
SubGhzProtocolDecoderFiatV2* instance,
bool level,
uint32_t duration) {
if(fiat_v2_duration_is_short(duration)) {
fiat_v2_push_cell(instance, level);
return true;
}
if(fiat_v2_duration_is_long(duration)) {
fiat_v2_push_cell(instance, level);
fiat_v2_push_cell(instance, level);
return true;
}
if(!level && duration >= FIAT_V2_BOUNDARY_MIN_US) {
fiat_v2_push_cell(instance, false);
}
fiat_v2_clear_cells(instance);
return false;
}
void* subghz_protocol_decoder_fiat_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV2));
furi_check(instance);
instance->base.protocol = &fiat_v2_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
subghz_protocol_decoder_fiat_v2_reset(instance);
return instance;
}
void subghz_protocol_decoder_fiat_v2_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
memset(instance->raw_data, 0, sizeof(instance->raw_data));
memset(instance->last_raw_data, 0, sizeof(instance->last_raw_data));
instance->decoder.parser_step = FiatV2DecoderStepReset;
instance->decoder.decode_data = 0U;
instance->decoder.decode_count_bit = 0U;
instance->last_raw_valid = false;
instance->generic.data = 0U;
instance->generic.data_count_bit = 0U;
instance->generic.serial = 0U;
instance->generic.btn = 0U;
instance->generic.cnt = 0U;
instance->uid = 0U;
instance->hop = 0U;
instance->button = 0U;
fiat_v2_clear_cells(instance);
}
void subghz_protocol_decoder_fiat_v2_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
switch(instance->decoder.parser_step) {
case FiatV2DecoderStepReset:
if(fiat_v2_duration_is_short(duration) || fiat_v2_duration_is_long(duration)) {
fiat_v2_clear_cells(instance);
instance->decoder.parser_step = FiatV2DecoderStepData;
(void)fiat_v2_feed_data_pulse(instance, level, duration);
}
break;
case FiatV2DecoderStepData:
if(!fiat_v2_feed_data_pulse(instance, level, duration)) {
instance->decoder.parser_step = FiatV2DecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_fiat_v2_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->generic.data,
.decode_count_bit = 64U,
};
return subghz_protocol_blocks_get_hash_data(&decoder, 8U) ^ instance->generic.cnt ^
instance->button;
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
flipper_format_rewind(flipper_format);
flipper_format_insert_or_update_hex(
flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES);
uint32_t hop = instance->hop;
uint32_t button = instance->button;
if(!flipper_format_write_uint32(flipper_format, FIAT_V2_HOP_FIELD, &hop, 1) ||
!flipper_format_write_uint32(flipper_format, FIAT_V2_BTN_FIELD, &button, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->generic.cnt);
return SubGhzProtocolStatusOk;
}
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v2_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_fiat_v2_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
if(instance->generic.data_count_bit != FIAT_V2_LOGICAL_BITS) {
return SubGhzProtocolStatusErrorValueBitCount;
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_hex(
flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES)) {
if(!fiat_v2_frame_valid(instance->raw_data)) {
return SubGhzProtocolStatusErrorParserOthers;
}
fiat_v2_decode_fields(instance);
return SubGhzProtocolStatusOk;
}
return SubGhzProtocolStatusErrorParserOthers;
}
void subghz_protocol_decoder_fiat_v2_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
furi_string_cat_printf(
output,
"%s %ubit\r\n"
"UID:%08lX\r\n"
"Hop:%08lX Type:%01X\r\n"
"Btn:%02X [%s] Cnt:%02lX\r\n",
instance->generic.protocol_name,
FIAT_V2_LOGICAL_BITS,
(unsigned long)instance->uid,
(unsigned long)instance->hop,
(unsigned)(instance->raw_data[6] >> 4),
instance->button,
fiat_v2_button_name(instance->button),
(unsigned long)instance->generic.cnt);
}
@@ -0,0 +1,31 @@
#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 "../defines.h"
#define FIAT_V2_PROTOCOL_NAME "Fiat V2"
typedef struct SubGhzProtocolDecoderFiatV2 SubGhzProtocolDecoderFiatV2;
extern const SubGhzProtocol fiat_v2_protocol;
void* subghz_protocol_decoder_fiat_v2_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v2_reset(void* context);
void subghz_protocol_decoder_fiat_v2_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v2_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v2_get_string(void* context, FuriString* output);
@@ -61,7 +61,7 @@ typedef struct SubGhzProtocolDecoderFordV0 {
uint8_t button;
uint32_t count;
} SubGhzProtocolDecoderFordV0;
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderFordV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -94,7 +94,7 @@ static void decode_ford_v0(
uint32_t* serial,
uint8_t* button,
uint32_t* count);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void encode_ford_v0(
uint8_t header_byte,
uint32_t serial,
@@ -120,7 +120,7 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v0_decoder = {
.get_string = subghz_protocol_decoder_ford_v0_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_ford_v0_encoder = {
.alloc = subghz_protocol_encoder_ford_v0_alloc,
.free = pp_encoder_free,
@@ -144,14 +144,22 @@ const SubGhzProtocol ford_protocol_v0 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v0_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v0_encoder,
#else
.encoder = NULL,
#endif
};
// =============================================================================
// CHECKSUM CALCULATION
// =============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t ford_v0_calculate_checksum(uint32_t serial, uint32_t count, uint8_t button) {
return (uint8_t)((((count >> 24) & 0xFF) + ((count >> 16) & 0xFF) + ((count >> 8) & 0xFF) +
(count & 0xFF) + ((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) +
@@ -179,7 +187,7 @@ static uint8_t ford_v0_calculate_crc(uint8_t* buf) {
return crc;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t ford_v0_calculate_crc_for_tx(uint64_t key1, uint8_t checksum) {
uint8_t buf[16] = {0};
@@ -274,7 +282,7 @@ static void decode_ford_v0(
// =============================================================================
// ENCODE FUNCTION
// =============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void encode_ford_v0(
uint8_t header_byte,
uint32_t serial,
@@ -357,7 +365,10 @@ static void encode_ford_v0(
void* subghz_protocol_encoder_ford_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFordV0* instance = malloc(sizeof(SubGhzProtocolEncoderFordV0));
SubGhzProtocolEncoderFordV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFordV0));
if(!instance) {
return NULL;
}
instance->base.protocol = &ford_protocol_v0;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -607,7 +618,10 @@ static bool ford_v0_process_data(SubGhzProtocolDecoderFordV0* instance) {
void* subghz_protocol_decoder_ford_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFordV0* instance = malloc(sizeof(SubGhzProtocolDecoderFordV0));
SubGhzProtocolDecoderFordV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV0));
if(!instance) {
return NULL;
}
instance->base.protocol = &ford_protocol_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
@@ -66,7 +66,7 @@ static bool ford_v1_extract_plain_from_raw(
const uint8_t* raw17_in,
uint8_t* plain9_out,
uint8_t* raw17_canonical_out_opt);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void
ford_v1_plain_apply_fields(uint8_t* plain9, uint32_t serial, uint8_t btn, uint32_t cnt);
static void ford_v1_encoder_rebuild_raw_from_plain(uint8_t* raw17, const uint8_t* plain9);
@@ -83,7 +83,7 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v1_decoder = {
.get_string = subghz_protocol_decoder_ford_v1_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder = {
.alloc = subghz_protocol_encoder_ford_v1_alloc,
.free = pp_encoder_free,
@@ -106,12 +106,20 @@ const SubGhzProtocol ford_protocol_v1 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v1_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v1_encoder,
#else
.encoder = NULL,
#endif
};
#define ford_v1_crc16(data, len) subghz_protocol_blocks_crc16((data), (len), 0x1021, 0x0000)
@@ -796,11 +804,15 @@ SubGhzProtocolStatus
if(ret == SubGhzProtocolStatusOk) {
flipper_format_rewind(flipper_format);
uint8_t key1_bytes[8] = {0};
flipper_format_read_hex(flipper_format, FF_KEY, key1_bytes, 8);
if(!flipper_format_read_hex(flipper_format, FF_KEY, key1_bytes, 8)) {
return SubGhzProtocolStatusErrorParserKey;
}
flipper_format_rewind(flipper_format);
uint8_t key2_bytes[8] = {0};
flipper_format_read_hex(flipper_format, "Key_2", key2_bytes, 8);
if(!flipper_format_read_hex(flipper_format, "Key_2", key2_bytes, 8)) {
return SubGhzProtocolStatusErrorParserOthers;
}
flipper_format_rewind(flipper_format);
uint8_t key3_bytes[4] = {0};
@@ -912,7 +924,7 @@ void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* outpu
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
#define FORD_V1_ENC_BURST_COUNT 6U
#define FORD_V1_ENC_PREAMBLE_PAIRS 400U
@@ -1200,4 +1212,4 @@ SubGhzProtocolStatus
return ret;
}
#endif // ENABLE_EMULATE_FEATURE
#endif // PROTOPIRATE_WITH_ENCODER
@@ -32,7 +32,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
@@ -16,7 +16,6 @@
#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)
@@ -70,7 +69,7 @@ typedef struct SubGhzProtocolDecoderFordV2 {
bool structure_ok;
} SubGhzProtocolDecoderFordV2;
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderFordV2 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -125,7 +124,7 @@ static bool ford_v2_button_is_valid(uint8_t btn) {
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t ford_v2_uint8_parity(uint8_t value) {
uint8_t parity = 0U;
while(value) {
@@ -350,7 +349,7 @@ static void ford_v2_decoder_rebuild_raw_buffer(SubGhzProtocolDecoderFordV2* inst
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static inline void ford_v2_encoder_add_level(
SubGhzProtocolEncoderFordV2* instance,
bool level,
@@ -360,7 +359,7 @@ static inline void ford_v2_encoder_add_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);
furi_check(idx < FORD_V2_ENC_UPLOAD_ELEMS);
instance->encoder.upload[idx] = level_duration_make(level, duration);
instance->encoder.size_upload++;
}
@@ -486,11 +485,8 @@ static SubGhzProtocolStatus
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;
}
instance->encoder.repeat =
(int32_t)pp_encoder_read_repeat(flipper_format, FORD_V2_ENCODER_DEFAULT_REPEAT);
}
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
@@ -501,7 +497,7 @@ void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
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));
instance->encoder.upload = calloc(FORD_V2_ENC_UPLOAD_ELEMS, sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
@@ -797,7 +793,7 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v2_decoder = {
.get_string = subghz_protocol_decoder_ford_v2_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder = {
.alloc = subghz_protocol_encoder_ford_v2_alloc,
.free = subghz_protocol_encoder_ford_v2_free,
@@ -820,10 +816,18 @@ const SubGhzProtocol ford_protocol_v2 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v2_encoder,
#else
.encoder = NULL,
#endif
};
@@ -30,7 +30,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format);
@@ -3,16 +3,24 @@
#include "protocols_common.h"
#include <string.h>
#define FORD_V3_TE_SHORT 240U
#define FORD_V3_TE_LONG 480U
#define FORD_V3_TE_DELTA 60U
#define FORD_V3_DATA_BITS 104U
#define FORD_V3_DATA_BYTES 13U
#define FORD_V3_PREAMBLE_MIN 30U
#define FORD_V3_TE_SHORT 240U
#define FORD_V3_TE_LONG 480U
#define FORD_V3_TE_DELTA 60U
#define FORD_V3_CELL_TE_DELTA 120U
#define FORD_V3_DATA_BITS 104U
#define FORD_V3_DATA_BYTES 13U
#define FORD_V3_PREAMBLE_MIN 30U
#define FORD_V3_CELL_CAP 320U
#define FORD_V3_CELL_MIN 200U
#define FORD_V3_CELL_MIN_BITS 100U
#define FORD_V3_FF_VARIANT "Variant"
#define FORD_V3_BTN_LOCK 0x01U
#define FORD_V3_BTN_UNLOCK 0x02U
#define FORD_V3_VARIANT_EU 0U
#define FORD_V3_VARIANT_US 1U
static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
.te_short = FORD_V3_TE_SHORT,
.te_long = FORD_V3_TE_LONG,
@@ -20,6 +28,13 @@ static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
.min_count_bit_for_found = FORD_V3_DATA_BITS,
};
static const SubGhzBlockConst subghz_protocol_ford_v3_cell_const = {
.te_short = FORD_V3_TE_SHORT,
.te_long = FORD_V3_TE_LONG,
.te_delta = FORD_V3_CELL_TE_DELTA,
.min_count_bit_for_found = FORD_V3_DATA_BITS,
};
typedef enum {
FordV3DecoderStepReset = 0,
FordV3DecoderStepPreamble = 1,
@@ -32,21 +47,52 @@ typedef struct SubGhzProtocolDecoderFordV3 {
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
uint8_t bit_count;
uint8_t manchester_raw[FORD_V3_DATA_BYTES];
uint8_t manchester_bit_count;
uint16_t preamble_count;
uint8_t cells[FORD_V3_CELL_CAP];
uint16_t cell_count;
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
uint8_t last_raw_bytes[FORD_V3_DATA_BYTES];
bool last_raw_valid;
uint8_t variant;
uint8_t flag;
uint32_t serial;
uint16_t counter;
} SubGhzProtocolDecoderFordV3;
static void ford_v3_reset_data(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_add_bit(SubGhzProtocolDecoderFordV3* instance, bool bit);
static void ford_v3_reset_manchester(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_reset_cells(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_add_manchester_bit(SubGhzProtocolDecoderFordV3* instance, bool bit);
static bool ford_v3_cell_frame_valid(const uint8_t* raw);
static uint8_t ford_v3_variant_from_saved_or_raw(const uint8_t* raw, uint32_t saved_variant);
static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_emit_if_ready(SubGhzProtocolDecoderFordV3* instance);
static const char* ford_v3_button_name(uint8_t btn);
static bool ford_v3_commit_frame(
SubGhzProtocolDecoderFordV3* instance,
const uint8_t* raw,
uint8_t variant);
static void ford_v3_manchester_emit_if_ready(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_cell_process(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_cell_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration);
static void
ford_v3_manchester_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration);
static const char* ford_v3_button_name(uint8_t btn, uint8_t variant);
static const char* ford_v3_button_name(uint8_t btn, uint8_t variant) {
if(variant == FORD_V3_VARIANT_US) {
switch(btn) {
case FORD_V3_BTN_LOCK:
return "Lock";
case FORD_V3_BTN_UNLOCK:
return "Unlock";
default:
return "?";
}
}
static const char* ford_v3_button_name(uint8_t btn) {
switch(btn) {
case FORD_V3_BTN_LOCK:
return "Lock";
@@ -57,25 +103,62 @@ static const char* ford_v3_button_name(uint8_t btn) {
}
}
static void ford_v3_reset_data(SubGhzProtocolDecoderFordV3* instance) {
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
instance->bit_count = 0;
static bool ford_v3_cell_frame_valid(const uint8_t* raw) {
if(raw[0] != 0xFFU) {
return false;
}
const uint32_t serial = ((uint32_t)raw[1] << 24) | ((uint32_t)raw[2] << 16) |
((uint32_t)raw[3] << 8) | (uint32_t)raw[4];
if(serial == 0U || serial == 0xFFFFFFFFU) {
return false;
}
if(raw[6] != FORD_V3_BTN_LOCK && raw[6] != FORD_V3_BTN_UNLOCK) {
return false;
}
if((raw[5] & 0x80U) == 0U) {
return false;
}
return true;
}
static uint8_t ford_v3_variant_from_saved_or_raw(const uint8_t* raw, uint32_t saved_variant) {
if(saved_variant == FORD_V3_VARIANT_US) {
return FORD_V3_VARIANT_US;
}
if(saved_variant == FORD_V3_VARIANT_EU) {
return FORD_V3_VARIANT_EU;
}
return ford_v3_cell_frame_valid(raw) ? FORD_V3_VARIANT_US : FORD_V3_VARIANT_EU;
}
static void ford_v3_reset_manchester(SubGhzProtocolDecoderFordV3* instance) {
memset(instance->manchester_raw, 0, sizeof(instance->manchester_raw));
instance->manchester_bit_count = 0;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
static void ford_v3_add_bit(SubGhzProtocolDecoderFordV3* instance, bool bit) {
if(instance->bit_count >= FORD_V3_DATA_BITS) {
static void ford_v3_reset_cells(SubGhzProtocolDecoderFordV3* instance) {
instance->cell_count = 0;
}
static void ford_v3_add_manchester_bit(SubGhzProtocolDecoderFordV3* instance, bool bit) {
if(instance->manchester_bit_count >= FORD_V3_DATA_BITS) {
return;
}
const uint8_t byte_index = instance->bit_count / 8U;
const uint8_t bit_in_byte = 7U - (instance->bit_count % 8U);
const uint8_t byte_index = instance->manchester_bit_count / 8U;
const uint8_t bit_in_byte = 7U - (instance->manchester_bit_count % 8U);
if(bit) {
instance->raw_bytes[byte_index] |= (uint8_t)(1U << bit_in_byte);
instance->manchester_raw[byte_index] |= (uint8_t)(1U << bit_in_byte);
}
instance->bit_count++;
instance->manchester_bit_count++;
}
static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance) {
@@ -83,55 +166,121 @@ static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance) {
instance->serial = ((uint32_t)b[1] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 8) |
(uint32_t)b[4];
instance->counter = (uint16_t)((((uint16_t)(uint8_t)~b[7]) << 8) | (uint8_t)~b[8]);
instance->generic.serial = instance->serial;
instance->generic.btn = (b[6] & 0x01U) ? FORD_V3_BTN_UNLOCK : FORD_V3_BTN_LOCK;
if(instance->variant == FORD_V3_VARIANT_US) {
instance->flag = b[5];
instance->counter = (uint16_t)(((uint16_t)b[7] << 8) | (uint16_t)b[8]);
instance->generic.btn = b[6];
} else {
instance->flag = 0;
instance->counter = (uint16_t)((((uint16_t)(uint8_t)~b[7]) << 8) | (uint8_t)~b[8]);
instance->generic.btn = (b[6] & 0x01U) ? FORD_V3_BTN_UNLOCK : FORD_V3_BTN_LOCK;
}
instance->generic.cnt = instance->counter;
}
static void ford_v3_emit_if_ready(SubGhzProtocolDecoderFordV3* instance) {
if(instance->bit_count < FORD_V3_DATA_BITS) {
return;
static bool ford_v3_commit_frame(
SubGhzProtocolDecoderFordV3* instance,
const uint8_t* raw,
uint8_t variant) {
if(instance->last_raw_valid && memcmp(instance->last_raw_bytes, raw, FORD_V3_DATA_BYTES) == 0) {
return true;
}
memcpy(instance->raw_bytes, raw, FORD_V3_DATA_BYTES);
memcpy(instance->last_raw_bytes, raw, FORD_V3_DATA_BYTES);
instance->last_raw_valid = true;
instance->variant = variant;
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
ford_v3_parse_fields(instance);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
return true;
}
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
static void ford_v3_manchester_emit_if_ready(SubGhzProtocolDecoderFordV3* instance) {
if(instance->manchester_bit_count < FORD_V3_DATA_BITS) {
return;
}
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)ford_v3_commit_frame(instance, instance->manchester_raw, FORD_V3_VARIANT_EU);
}
void subghz_protocol_decoder_ford_v3_reset(void* context) {
furi_check(context);
static bool ford_v3_cell_decode(const uint8_t* cells, uint16_t cell_count, uint8_t* raw_out) {
for(int phase = 0; phase < 2; phase++) {
uint8_t frame[FORD_V3_DATA_BYTES];
memset(frame, 0, sizeof(frame));
SubGhzProtocolDecoderFordV3* instance = context;
instance->decoder.parser_step = FordV3DecoderStepReset;
ford_v3_reset_data(instance);
int bit_count = 0;
bool ok = true;
for(int i = phase; (i + 1) < (int)cell_count && bit_count < (int)FORD_V3_DATA_BITS; i += 2) {
const uint8_t first = cells[i];
const uint8_t second = cells[i + 1];
if(first == second) {
ok = false;
break;
}
if(first) {
frame[bit_count >> 3] |= (uint8_t)(1U << (7 - (bit_count & 7)));
}
bit_count++;
}
if(!ok || bit_count < (int)FORD_V3_CELL_MIN_BITS) {
continue;
}
if(!ford_v3_cell_frame_valid(frame)) {
continue;
}
memcpy(raw_out, frame, FORD_V3_DATA_BYTES);
return true;
}
return false;
}
void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
static void ford_v3_cell_process(SubGhzProtocolDecoderFordV3* instance) {
if(instance->cell_count < FORD_V3_CELL_MIN) {
return;
}
SubGhzProtocolDecoderFordV3* instance = context;
uint8_t raw[FORD_V3_DATA_BYTES];
if(!ford_v3_cell_decode(instance->cells, instance->cell_count, raw)) {
return;
}
(void)ford_v3_commit_frame(instance, raw, FORD_V3_VARIANT_US);
}
static void ford_v3_cell_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration) {
if(pp_is_short(duration, &subghz_protocol_ford_v3_cell_const)) {
if(instance->cell_count < FORD_V3_CELL_CAP) {
instance->cells[instance->cell_count++] = level ? 1U : 0U;
}
} else if(pp_is_long(duration, &subghz_protocol_ford_v3_cell_const)) {
if(instance->cell_count + 2U <= FORD_V3_CELL_CAP) {
instance->cells[instance->cell_count++] = level ? 1U : 0U;
instance->cells[instance->cell_count++] = level ? 1U : 0U;
}
} else {
ford_v3_cell_process(instance);
instance->cell_count = 0;
}
}
static void
ford_v3_manchester_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration) {
switch(instance->decoder.parser_step) {
case FordV3DecoderStepReset:
if(pp_is_short(duration, &subghz_protocol_ford_v3_const)) {
ford_v3_reset_data(instance);
ford_v3_reset_manchester(instance);
instance->preamble_count = 1U;
instance->decoder.parser_step = FordV3DecoderStepPreamble;
}
@@ -151,7 +300,7 @@ void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t du
const bool valid = manchester_advance(
instance->manchester_state, event, &instance->manchester_state, &data_bit);
if(valid) {
ford_v3_add_bit(instance, data_bit);
ford_v3_add_manchester_bit(instance, data_bit);
}
instance->decoder.parser_step = FordV3DecoderStepData;
} else {
@@ -159,14 +308,14 @@ void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t du
}
break;
case FordV3DecoderStepData: {
case FordV3DecoderStepData:
if(!pp_is_short(duration, &subghz_protocol_ford_v3_const) &&
!pp_is_long(duration, &subghz_protocol_ford_v3_const)) {
ford_v3_emit_if_ready(instance);
ford_v3_manchester_emit_if_ready(instance);
instance->decoder.parser_step = FordV3DecoderStepReset;
if(pp_is_short(duration, &subghz_protocol_ford_v3_const)) {
ford_v3_reset_data(instance);
ford_v3_reset_manchester(instance);
instance->preamble_count = 1U;
instance->decoder.parser_step = FordV3DecoderStepPreamble;
}
@@ -189,15 +338,44 @@ void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t du
instance->manchester_state, event, &instance->manchester_state, &data_bit);
if(valid) {
ford_v3_add_bit(instance, data_bit);
if(instance->bit_count >= FORD_V3_DATA_BITS) {
ford_v3_emit_if_ready(instance);
ford_v3_add_manchester_bit(instance, data_bit);
if(instance->manchester_bit_count >= FORD_V3_DATA_BITS) {
ford_v3_manchester_emit_if_ready(instance);
instance->decoder.parser_step = FordV3DecoderStepReset;
}
}
break;
}
}
}
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_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFordV3* instance = context;
instance->decoder.parser_step = FordV3DecoderStepReset;
ford_v3_reset_manchester(instance);
ford_v3_reset_cells(instance);
instance->last_raw_valid = false;
}
void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFordV3* instance = context;
ford_v3_cell_feed(instance, level, duration);
ford_v3_manchester_feed(instance, level, duration);
}
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context) {
@@ -239,6 +417,7 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->counter);
pp_flipper_update_or_insert_u32(flipper_format, FORD_V3_FF_VARIANT, instance->variant);
}
return ret;
@@ -266,7 +445,13 @@ SubGhzProtocolStatus
flipper_format_rewind(flipper_format);
flipper_format_read_hex(flipper_format, "Raw", instance->raw_bytes, FORD_V3_DATA_BYTES);
instance->bit_count = FORD_V3_DATA_BITS;
uint32_t variant = UINT32_MAX;
if(!flipper_format_read_uint32(flipper_format, FORD_V3_FF_VARIANT, &variant, 1)) {
variant = UINT32_MAX;
}
instance->variant = ford_v3_variant_from_saved_or_raw(instance->raw_bytes, variant);
instance->manchester_bit_count = FORD_V3_DATA_BITS;
ford_v3_parse_fields(instance);
return ret;
@@ -278,6 +463,39 @@ void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* outpu
SubGhzProtocolDecoderFordV3* instance = context;
const uint8_t* k = instance->raw_bytes;
if(instance->variant == FORD_V3_VARIANT_US) {
furi_string_cat_printf(
output,
"%s US %dbit\r\n"
"Key:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
"Sn:%08lX Btn:%02X %s\r\n"
"Cnt:%04X Hop:%02X%02X%02X%02X\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],
(unsigned long)instance->generic.serial,
instance->generic.btn,
ford_v3_button_name(instance->generic.btn, FORD_V3_VARIANT_US),
(unsigned)instance->counter,
k[9],
k[10],
k[11],
k[12]);
return;
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
@@ -301,7 +519,7 @@ void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* outpu
k[12],
(unsigned long)instance->generic.serial,
instance->generic.btn,
ford_v3_button_name(instance->generic.btn),
ford_v3_button_name(instance->generic.btn, FORD_V3_VARIANT_EU),
(unsigned)instance->counter,
k[9],
k[10],
@@ -331,8 +549,17 @@ const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder = {
const SubGhzProtocol ford_protocol_v3 = {
.name = FORD_PROTOCOL_V3_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v3_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v3_encoder,
#else
.encoder = NULL,
#endif
};
@@ -20,7 +20,7 @@ _Static_assert(
HONDA_STATIC_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
"HONDA_STATIC_UPLOAD_CAPACITY exceeds shared upload slab");
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
#endif
static const char* const honda_static_button_names[9] = {
@@ -50,7 +50,7 @@ struct SubGhzProtocolDecoderHondaStatic {
uint16_t symbols_count;
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
struct SubGhzProtocolEncoderHondaStatic {
SubGhzProtocolEncoderBase base;
@@ -92,7 +92,7 @@ static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, ui
return value;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
@@ -143,7 +143,7 @@ static bool honda_static_is_valid_serial(uint32_t serial) {
return (serial != 0U) && (serial != 0x0FFFFFFFU);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
if(button < 2U) {
return 1U;
@@ -212,7 +212,7 @@ static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
return pp_bytes_to_u64_be(compact);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
memset(packet, 0, 8);
@@ -408,7 +408,7 @@ static void honda_static_decoder_commit(
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
uint8_t packet[8];
honda_static_build_packet_bytes(&instance->decoded, packet);
@@ -449,7 +449,7 @@ const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
.get_string = subghz_protocol_decoder_honda_static_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
.alloc = subghz_protocol_encoder_honda_static_alloc,
.free = pp_encoder_free,
@@ -473,11 +473,19 @@ const SubGhzProtocol honda_static_protocol = {
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_honda_static_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_honda_static_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -30,7 +30,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
@@ -62,7 +62,7 @@ static const char* const honda_v1_button_names[HONDA_V1_BUTTON_MAX + 1U] = {
[HondaV1ButtonPanic] = "Panic",
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static const uint32_t honda_v1_button_codes[HONDA_V1_BUTTON_MAX + 1U] = {
[HondaV1ButtonUnlock] = 0x00080808,
[HondaV1ButtonLock] = 0x00088888,
@@ -87,7 +87,7 @@ struct SubGhzProtocolDecoderHondaV1 {
uint8_t k2;
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
struct SubGhzProtocolEncoderHondaV1 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -109,7 +109,7 @@ static const char* honda_v1_button_name(uint8_t b) {
return "Unknown";
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint32_t honda_v1_button_code(uint8_t button) {
if(!honda_v1_button_valid(button)) {
return HONDA_V1_BUTTON_FALLBACK_CODE;
@@ -177,7 +177,7 @@ static void honda_v1_decode_fields(SubGhzBlockGeneric* generic) {
generic->data_count_bit = HONDA_V1_BIT_COUNT;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint64_t honda_v1_build_key(uint32_t serial, uint8_t button, uint16_t counter) {
const uint32_t table = honda_v1_button_code(button);
const uint32_t low = ((table & HONDA_V1_COUNTER_MASK) << 16U) | counter;
@@ -306,7 +306,7 @@ static void
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static bool honda_v1_append_frame(
SubGhzProtocolEncoderHondaV1* instance,
size_t* index,
@@ -407,7 +407,7 @@ const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder = {
.get_string = subghz_protocol_decoder_honda_v1_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder = {
.alloc = subghz_protocol_encoder_honda_v1_alloc,
.free = pp_encoder_free,
@@ -430,15 +430,23 @@ const SubGhzProtocol honda_v1_protocol = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_honda_v1_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_honda_v1_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -29,7 +29,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_v1_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
@@ -1,40 +1,40 @@
#include "land_rover_v0.h"
#include "honda_v2.h"
#include "protocols_common.h"
#include <string.h>
#define TAG "LandRoverV0"
#define TAG "HondaV2"
static const SubGhzBlockConst subghz_protocol_land_rover_v0_const = {
static const SubGhzBlockConst subghz_protocol_honda_v2_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 HONDA_V2_PREAMBLE_PAIRS 319U
#define HONDA_V2_MIN_PREAMBLE_PAIRS 64U
#define HONDA_V2_SYNC_US 750U
#define HONDA_V2_SYNC_DELTA_US 120U
#define HONDA_V2_UPLOAD_CAPACITY 1024U
#define HONDA_V2_GAP_US 50000U
_Static_assert(
LAND_ROVER_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
"LAND_ROVER_V0_UPLOAD_CAPACITY exceeds shared upload slab");
HONDA_V2_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
"HONDA_V2_UPLOAD_CAPACITY exceeds shared upload slab");
#define LAND_ROVER_V0_BTN_UNKNOWN 0x00U
#define LAND_ROVER_V0_BTN_LOCK 0x02U
#define LAND_ROVER_V0_BTN_UNLOCK 0x04U
#define HONDA_V2_BTN_UNKNOWN 0x00U
#define HONDA_V2_BTN_LOCK 0x02U
#define HONDA_V2_BTN_UNLOCK 0x04U
#define LAND_ROVER_V0_SIG_UNLOCK 0xA285E3UL
#define LAND_ROVER_V0_SIG_LOCK 0xC20363UL
#define HONDA_V2_SIG_UNLOCK 0xA285E3UL
#define HONDA_V2_SIG_LOCK 0xC20363UL
#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"
#define HONDA_V2_FF_BTNSIG "BtnSig"
#define HONDA_V2_FF_CHECK "Check"
#define HONDA_V2_FF_TAIL "Tail"
#define HONDA_V2_FF_EXTRA_BIT "ExtraBit"
typedef struct SubGhzProtocolDecoderLandRoverV0 {
typedef struct SubGhzProtocolDecoderHondaV2 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
@@ -56,10 +56,10 @@ typedef struct SubGhzProtocolDecoderLandRoverV0 {
uint8_t check;
bool check_ok;
bool tail_ok;
} SubGhzProtocolDecoderLandRoverV0;
} SubGhzProtocolDecoderHondaV2;
#ifdef ENABLE_EMULATE_FEATURE
typedef struct SubGhzProtocolEncoderLandRoverV0 {
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderHondaV2 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
@@ -71,77 +71,77 @@ typedef struct SubGhzProtocolEncoderLandRoverV0 {
uint32_t count;
uint8_t button;
uint8_t check;
} SubGhzProtocolEncoderLandRoverV0;
} SubGhzProtocolEncoderHondaV2;
#endif
typedef enum {
LandRoverV0DecoderStepReset = 0,
LandRoverV0DecoderStepPreambleLow,
LandRoverV0DecoderStepPreambleHigh,
LandRoverV0DecoderStepSyncLow,
LandRoverV0DecoderStepData,
} LandRoverV0DecoderStep;
HondaV2DecoderStepReset = 0,
HondaV2DecoderStepPreambleLow,
HondaV2DecoderStepPreambleHigh,
HondaV2DecoderStepSyncLow,
HondaV2DecoderStepData,
} HondaV2DecoderStep;
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(
static uint8_t honda_v2_button_from_signature(uint32_t signature);
static const char* honda_v2_button_name(uint8_t button);
static uint8_t honda_v2_calculate_check(uint32_t count);
static bool honda_v2_calculate_tail_msb(uint32_t count);
static uint16_t honda_v2_calculate_tail(uint32_t count);
static void honda_v2_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(
static bool honda_v2_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,
static bool honda_v2_add_decoded_bit(SubGhzProtocolDecoderHondaV2* instance, bool bit);
static bool honda_v2_process_transition(
SubGhzProtocolDecoderHondaV2* instance,
bool level,
uint32_t duration);
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance);
static bool honda_v2_finish_frame(SubGhzProtocolDecoderHondaV2* instance);
#ifdef ENABLE_EMULATE_FEATURE
static bool land_rover_v0_encoder_add_level(
SubGhzProtocolEncoderLandRoverV0* instance,
#if PROTOPIRATE_WITH_ENCODER
static bool honda_v2_encoder_add_level(
SubGhzProtocolEncoderHondaV2* instance,
size_t* index,
bool level,
uint32_t duration);
static bool land_rover_v0_encoder_add_bit(
SubGhzProtocolEncoderLandRoverV0* instance,
static bool honda_v2_encoder_add_bit(
SubGhzProtocolEncoderHondaV2* instance,
size_t* index,
bool* previous_bit,
bool bit);
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance);
static bool honda_v2_build_upload(SubGhzProtocolEncoderHondaV2* instance);
#endif
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 SubGhzProtocolDecoder subghz_protocol_honda_v2_decoder = {
.alloc = subghz_protocol_decoder_honda_v2_alloc,
.free = subghz_protocol_decoder_honda_v2_free,
.feed = subghz_protocol_decoder_honda_v2_feed,
.reset = subghz_protocol_decoder_honda_v2_reset,
.get_hash_data = subghz_protocol_decoder_honda_v2_get_hash_data,
.serialize = subghz_protocol_decoder_honda_v2_serialize,
.deserialize = subghz_protocol_decoder_honda_v2_deserialize,
.get_string = subghz_protocol_decoder_honda_v2_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
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,
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_honda_v2_encoder = {
.alloc = subghz_protocol_encoder_honda_v2_alloc,
.free = subghz_protocol_encoder_honda_v2_free,
.deserialize = subghz_protocol_encoder_honda_v2_deserialize,
.stop = subghz_protocol_encoder_honda_v2_stop,
.yield = subghz_protocol_encoder_honda_v2_yield,
};
#else
const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
const SubGhzProtocolEncoder subghz_protocol_honda_v2_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
@@ -150,49 +150,57 @@ const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
};
#endif
const SubGhzProtocol land_rover_v0_protocol = {
.name = LAND_ROVER_PROTOCOL_V0_NAME,
const SubGhzProtocol honda_v2_protocol = {
.name = HONDA_V2_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | 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,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_honda_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_honda_v2_encoder,
#else
.encoder = NULL,
#endif
};
static bool land_rover_v0_is_short(uint32_t duration) {
return pp_is_short(duration, &subghz_protocol_land_rover_v0_const);
static bool honda_v2_is_short(uint32_t duration) {
return pp_is_short(duration, &subghz_protocol_honda_v2_const);
}
static bool land_rover_v0_is_long(uint32_t duration) {
return pp_is_long(duration, &subghz_protocol_land_rover_v0_const);
static bool honda_v2_is_long(uint32_t duration) {
return pp_is_long(duration, &subghz_protocol_honda_v2_const);
}
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 bool honda_v2_is_sync(uint32_t duration) {
return DURATION_DIFF(duration, HONDA_V2_SYNC_US) < HONDA_V2_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;
} else if(signature == LAND_ROVER_V0_SIG_LOCK) {
return LAND_ROVER_V0_BTN_LOCK;
static uint8_t honda_v2_button_from_signature(uint32_t signature) {
if(signature == HONDA_V2_SIG_UNLOCK) {
return HONDA_V2_BTN_UNLOCK;
} else if(signature == HONDA_V2_SIG_LOCK) {
return HONDA_V2_BTN_LOCK;
}
return LAND_ROVER_V0_BTN_UNKNOWN;
return HONDA_V2_BTN_UNKNOWN;
}
static const char* land_rover_v0_button_name(uint8_t button) {
static const char* honda_v2_button_name(uint8_t button) {
switch(button) {
case LAND_ROVER_V0_BTN_LOCK:
case HONDA_V2_BTN_LOCK:
return "Lock";
case LAND_ROVER_V0_BTN_UNLOCK:
case HONDA_V2_BTN_UNLOCK:
return "Unlock";
default:
return "Unknown";
}
}
static uint8_t land_rover_v0_calculate_check(uint32_t count) {
static uint8_t honda_v2_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) ^
@@ -204,16 +212,16 @@ static uint8_t land_rover_v0_calculate_check(uint32_t count) {
return (uint8_t)(c0 | (c1 << 1) | (c2 << 2));
}
static bool land_rover_v0_calculate_tail_msb(uint32_t count) {
static bool honda_v2_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 uint16_t honda_v2_calculate_tail(uint32_t count) {
return honda_v2_calculate_tail_msb(count) ? 0xFFFFU : 0x7FFFU;
}
static void land_rover_v0_parse_key_fields(
static void honda_v2_parse_key_fields(
uint64_t key,
uint32_t* signature,
uint32_t* serial,
@@ -232,11 +240,11 @@ static void land_rover_v0_parse_key_fields(
if(signature) *signature = sig;
if(serial) *serial = sn;
if(count) *count = cnt;
if(button) *button = land_rover_v0_button_from_signature(sig);
if(button) *button = honda_v2_button_from_signature(sig);
if(check) *check = key_bytes[7] & 0x07U;
}
static bool land_rover_v0_validate_frame(
static bool honda_v2_validate_frame(
uint64_t key,
uint16_t tail,
bool extra_bit,
@@ -246,8 +254,8 @@ static bool land_rover_v0_validate_frame(
pp_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 uint8_t expected_check = honda_v2_calculate_check(count);
const uint16_t expected_tail = honda_v2_calculate_tail(count);
const bool local_check_ok = ((key_bytes[7] & 0x78U) == 0U) &&
((key_bytes[7] & 0x07U) == expected_check);
@@ -259,7 +267,7 @@ static bool land_rover_v0_validate_frame(
return local_check_ok && local_tail_ok;
}
static bool land_rover_v0_add_decoded_bit(SubGhzProtocolDecoderLandRoverV0* instance, bool bit) {
static bool honda_v2_add_decoded_bit(SubGhzProtocolDecoderHondaV2* 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);
@@ -276,11 +284,11 @@ static bool land_rover_v0_add_decoded_bit(SubGhzProtocolDecoderLandRoverV0* inst
return true;
}
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance) {
static bool honda_v2_finish_frame(SubGhzProtocolDecoderHondaV2* instance) {
const uint64_t key = pp_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(
if(!honda_v2_validate_frame(
key, tail, instance->extra_bit, &instance->check_ok, &instance->tail_ok)) {
return false;
}
@@ -288,7 +296,7 @@ static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instanc
instance->key = key;
instance->tail = tail;
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
key,
&instance->command_signature,
&instance->serial,
@@ -297,7 +305,7 @@ static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instanc
&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.data_count_bit = subghz_protocol_honda_v2_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
@@ -305,12 +313,12 @@ static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instanc
return true;
}
static bool land_rover_v0_process_transition(
SubGhzProtocolDecoderLandRoverV0* instance,
static bool honda_v2_process_transition(
SubGhzProtocolDecoderHondaV2* instance,
bool level,
uint32_t duration) {
if(!instance->boundary_pad_skipped) {
if(level && land_rover_v0_is_short(duration)) {
if(level && honda_v2_is_short(duration)) {
instance->boundary_pad_skipped = true;
return true;
}
@@ -318,31 +326,31 @@ static bool land_rover_v0_process_transition(
}
if(instance->pending_short) {
if(!instance->previous_bit && !level && land_rover_v0_is_short(duration)) {
if(!instance->previous_bit && !level && honda_v2_is_short(duration)) {
instance->pending_short = false;
return land_rover_v0_add_decoded_bit(instance, false);
} else if(instance->previous_bit && level && land_rover_v0_is_short(duration)) {
return honda_v2_add_decoded_bit(instance, false);
} else if(instance->previous_bit && level && honda_v2_is_short(duration)) {
instance->pending_short = false;
return land_rover_v0_add_decoded_bit(instance, true);
return honda_v2_add_decoded_bit(instance, true);
}
return false;
}
if(!instance->previous_bit) {
if(level && land_rover_v0_is_long(duration)) {
if(level && honda_v2_is_long(duration)) {
instance->previous_bit = true;
return land_rover_v0_add_decoded_bit(instance, true);
} else if(level && land_rover_v0_is_short(duration)) {
return honda_v2_add_decoded_bit(instance, true);
} else if(level && honda_v2_is_short(duration)) {
instance->pending_short = true;
return true;
}
return false;
}
if(!level && land_rover_v0_is_long(duration)) {
if(!level && honda_v2_is_long(duration)) {
instance->previous_bit = false;
return land_rover_v0_add_decoded_bit(instance, false);
} else if(!level && land_rover_v0_is_short(duration)) {
return honda_v2_add_decoded_bit(instance, false);
} else if(!level && honda_v2_is_short(duration)) {
instance->pending_short = true;
return true;
}
@@ -350,43 +358,43 @@ static bool land_rover_v0_process_transition(
return false;
}
#ifdef ENABLE_EMULATE_FEATURE
static bool land_rover_v0_encoder_add_level(
SubGhzProtocolEncoderLandRoverV0* instance,
#if PROTOPIRATE_WITH_ENCODER
static bool honda_v2_encoder_add_level(
SubGhzProtocolEncoderHondaV2* instance,
size_t* index,
bool level,
uint32_t duration) {
if(*index >= LAND_ROVER_V0_UPLOAD_CAPACITY) {
if(*index >= HONDA_V2_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,
static bool honda_v2_encoder_add_bit(
SubGhzProtocolEncoderHondaV2* 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;
const uint32_t te_short = subghz_protocol_honda_v2_const.te_short;
const uint32_t te_long = subghz_protocol_honda_v2_const.te_long;
if(!*previous_bit && !bit) {
if(!land_rover_v0_encoder_add_level(instance, index, true, te_short) ||
!land_rover_v0_encoder_add_level(instance, index, false, te_short)) {
if(!honda_v2_encoder_add_level(instance, index, true, te_short) ||
!honda_v2_encoder_add_level(instance, index, false, te_short)) {
return false;
}
} else if(!*previous_bit && bit) {
if(!land_rover_v0_encoder_add_level(instance, index, true, te_long)) {
if(!honda_v2_encoder_add_level(instance, index, true, te_long)) {
return false;
}
} else if(*previous_bit && !bit) {
if(!land_rover_v0_encoder_add_level(instance, index, false, te_long)) {
if(!honda_v2_encoder_add_level(instance, index, false, te_long)) {
return false;
}
} else {
if(!land_rover_v0_encoder_add_level(instance, index, false, te_short) ||
!land_rover_v0_encoder_add_level(instance, index, true, te_short)) {
if(!honda_v2_encoder_add_level(instance, index, false, te_short) ||
!honda_v2_encoder_add_level(instance, index, true, te_short)) {
return false;
}
}
@@ -395,30 +403,30 @@ static bool land_rover_v0_encoder_add_bit(
return true;
}
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance) {
static bool honda_v2_build_upload(SubGhzProtocolEncoderHondaV2* instance) {
furi_check(instance);
size_t index = 0;
const uint32_t te_short = subghz_protocol_land_rover_v0_const.te_short;
const uint32_t te_short = subghz_protocol_honda_v2_const.te_short;
uint8_t key_bytes[8];
pp_u64_to_bytes_be(instance->key, key_bytes);
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)) {
for(uint16_t i = 0; i < HONDA_V2_PREAMBLE_PAIRS; i++) {
if(!honda_v2_encoder_add_level(instance, &index, true, te_short) ||
!honda_v2_encoder_add_level(instance, &index, false, te_short)) {
return false;
}
}
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)) {
if(!honda_v2_encoder_add_level(instance, &index, true, HONDA_V2_SYNC_US) ||
!honda_v2_encoder_add_level(instance, &index, false, HONDA_V2_SYNC_US) ||
!honda_v2_encoder_add_level(instance, &index, true, te_short)) {
return false;
}
bool previous_bit = true;
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, false)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, false)) {
return false;
}
@@ -426,24 +434,24 @@ static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instanc
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)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, bit)) {
return false;
}
}
instance->tail = land_rover_v0_calculate_tail(instance->count);
instance->tail = honda_v2_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)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, bit)) {
return false;
}
}
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, true)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, true)) {
return false;
}
if(!land_rover_v0_encoder_add_level(instance, &index, false, LAND_ROVER_V0_GAP_US)) {
if(!honda_v2_encoder_add_level(instance, &index, false, HONDA_V2_GAP_US)) {
return false;
}
@@ -453,29 +461,29 @@ static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instanc
}
#endif
void* subghz_protocol_decoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
void* subghz_protocol_decoder_honda_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderLandRoverV0* instance =
calloc(1, sizeof(SubGhzProtocolDecoderLandRoverV0));
SubGhzProtocolDecoderHondaV2* instance =
calloc(1, sizeof(SubGhzProtocolDecoderHondaV2));
furi_check(instance);
instance->base.protocol = &land_rover_v0_protocol;
instance->base.protocol = &honda_v2_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_land_rover_v0_free(void* context) {
void subghz_protocol_decoder_honda_v2_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
free(instance);
}
void subghz_protocol_decoder_land_rover_v0_reset(void* context) {
void subghz_protocol_decoder_honda_v2_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
instance->decoder.te_last = 0;
instance->preamble_count = 0;
memset(instance->raw, 0, sizeof(instance->raw));
@@ -486,65 +494,65 @@ void subghz_protocol_decoder_land_rover_v0_reset(void* context) {
instance->pending_short = false;
}
void subghz_protocol_decoder_land_rover_v0_feed(void* context, bool level, uint32_t duration) {
void subghz_protocol_decoder_honda_v2_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
switch(instance->decoder.parser_step) {
case LandRoverV0DecoderStepReset:
if(level && land_rover_v0_is_short(duration)) {
case HondaV2DecoderStepReset:
if(level && honda_v2_is_short(duration)) {
instance->preamble_count = 0;
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
instance->decoder.parser_step = HondaV2DecoderStepPreambleLow;
}
break;
case LandRoverV0DecoderStepPreambleLow:
if(!level && land_rover_v0_is_short(duration)) {
case HondaV2DecoderStepPreambleLow:
if(!level && honda_v2_is_short(duration)) {
instance->preamble_count++;
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleHigh;
instance->decoder.parser_step = HondaV2DecoderStepPreambleHigh;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
case LandRoverV0DecoderStepPreambleHigh:
if(level && land_rover_v0_is_short(duration)) {
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
case HondaV2DecoderStepPreambleHigh:
if(level && honda_v2_is_short(duration)) {
instance->decoder.parser_step = HondaV2DecoderStepPreambleLow;
} else if(
level && land_rover_v0_is_sync(duration) &&
instance->preamble_count >= LAND_ROVER_V0_MIN_PREAMBLE_PAIRS) {
instance->decoder.parser_step = LandRoverV0DecoderStepSyncLow;
level && honda_v2_is_sync(duration) &&
instance->preamble_count >= HONDA_V2_MIN_PREAMBLE_PAIRS) {
instance->decoder.parser_step = HondaV2DecoderStepSyncLow;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
case LandRoverV0DecoderStepSyncLow:
if(!level && land_rover_v0_is_sync(duration)) {
case HondaV2DecoderStepSyncLow:
if(!level && honda_v2_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;
honda_v2_add_decoded_bit(instance, true);
instance->decoder.parser_step = HondaV2DecoderStepData;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
case LandRoverV0DecoderStepData:
if(!land_rover_v0_process_transition(instance, level, duration)) {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
case HondaV2DecoderStepData:
if(!honda_v2_process_transition(instance, level, duration)) {
instance->decoder.parser_step = HondaV2DecoderStepReset;
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) {
if(instance->bit_count == subghz_protocol_honda_v2_const.min_count_bit_for_found) {
if(honda_v2_finish_frame(instance) && instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
}
@@ -552,9 +560,9 @@ void subghz_protocol_decoder_land_rover_v0_feed(void* context, bool level, uint3
instance->decoder.te_last = duration;
}
uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context) {
uint8_t subghz_protocol_decoder_honda_v2_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->key,
@@ -567,12 +575,12 @@ uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context) {
return hash;
}
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
@@ -585,27 +593,27 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->button);
pp_flipper_update_or_insert_u32(
flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
flipper_format, HONDA_V2_FF_BTNSIG, instance->command_signature);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->count);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(
flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, instance->extra_bit ? 1U : 0U);
flipper_format, HONDA_V2_FF_EXTRA_BIT, instance->extra_bit ? 1U : 0U);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* 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);
subghz_protocol_honda_v2_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
uint8_t key_bytes[8] = {0};
@@ -624,27 +632,27 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
uint32_t temp = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_TAIL, &temp, 1)) {
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_TAIL, &temp, 1)) {
instance->tail = 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);
instance->tail = honda_v2_calculate_tail(count);
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, &temp, 1)) {
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_EXTRA_BIT, &temp, 1)) {
instance->extra_bit = (temp & 1U) != 0;
} else {
instance->extra_bit = true;
}
land_rover_v0_validate_frame(
honda_v2_validate_frame(
instance->key,
instance->tail,
instance->extra_bit,
&instance->check_ok,
&instance->tail_ok);
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
@@ -654,7 +662,7 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
instance->generic.data = instance->key;
instance->generic.data_count_bit =
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
subghz_protocol_honda_v2_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
@@ -663,9 +671,9 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
return ret;
}
void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString* output) {
void subghz_protocol_decoder_honda_v2_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
furi_string_cat_printf(
output,
@@ -679,7 +687,7 @@ void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString*
(unsigned long long)instance->key,
(unsigned long)instance->serial,
instance->button,
land_rover_v0_button_name(instance->button),
honda_v2_button_name(instance->button),
(unsigned long)instance->command_signature,
(unsigned long)instance->count,
instance->check,
@@ -688,20 +696,20 @@ void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString*
instance->tail_ok ? "OK" : "BAD");
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint32_t land_rover_v0_signature_from_button(uint8_t button) {
static uint32_t honda_v2_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;
case HONDA_V2_BTN_LOCK:
return HONDA_V2_SIG_LOCK;
case HONDA_V2_BTN_UNLOCK:
return HONDA_V2_SIG_UNLOCK;
default:
return 0;
}
}
static uint64_t land_rover_v0_build_key(uint32_t signature, uint32_t serial, uint32_t count) {
static uint64_t honda_v2_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);
@@ -712,39 +720,39 @@ static uint64_t land_rover_v0_build_key(uint32_t signature, uint32_t serial, uin
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);
const uint8_t check = honda_v2_calculate_check(count);
key_bytes[7] = (counter_lsb ? 0x80U : 0x00U) | check;
return pp_bytes_to_u64_be(key_bytes);
}
void* subghz_protocol_encoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
void* subghz_protocol_encoder_honda_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderLandRoverV0* instance =
calloc(1, sizeof(SubGhzProtocolEncoderLandRoverV0));
SubGhzProtocolEncoderHondaV2* instance =
calloc(1, sizeof(SubGhzProtocolEncoderHondaV2));
furi_check(instance);
instance->base.protocol = &land_rover_v0_protocol;
instance->base.protocol = &honda_v2_protocol;
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;
pp_encoder_buffer_ensure(instance, LAND_ROVER_V0_UPLOAD_CAPACITY);
pp_encoder_buffer_ensure(instance, HONDA_V2_UPLOAD_CAPACITY);
return instance;
}
void subghz_protocol_encoder_land_rover_v0_free(void* context) {
void subghz_protocol_encoder_honda_v2_free(void* context) {
furi_check(context);
pp_encoder_free(context);
}
SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
SubGhzProtocolStatus subghz_protocol_encoder_honda_v2_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderLandRoverV0* instance = context;
SubGhzProtocolEncoderHondaV2* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
@@ -760,7 +768,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
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);
subghz_protocol_honda_v2_const.min_count_bit_for_found);
if(load_status != SubGhzProtocolStatusOk) {
break;
}
@@ -778,7 +786,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
}
if(have_key) {
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
@@ -807,12 +815,12 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_BTNSIG, &u32, 1)) {
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_BTNSIG, &u32, 1)) {
instance->command_signature = u32 & 0xFFFFFFU;
}
if(have_button) {
const uint32_t signature = land_rover_v0_signature_from_button(instance->button);
const uint32_t signature = honda_v2_signature_from_button(instance->button);
if(signature != 0U) {
instance->command_signature = signature;
}
@@ -822,13 +830,13 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
break;
}
instance->key = land_rover_v0_build_key(
instance->key = honda_v2_build_key(
instance->command_signature, instance->serial, instance->count);
pp_u64_to_bytes_be(instance->key, key_bytes);
instance->tail = land_rover_v0_calculate_tail(instance->count);
instance->tail = honda_v2_calculate_tail(instance->count);
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
@@ -838,7 +846,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
instance->generic.data = instance->key;
instance->generic.data_count_bit =
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
subghz_protocol_honda_v2_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
@@ -846,7 +854,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
flipper_format_rewind(flipper_format);
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 10);
if(!land_rover_v0_build_upload(instance) || instance->encoder.size_upload == 0U) {
if(!honda_v2_build_upload(instance) || instance->encoder.size_upload == 0U) {
break;
}
@@ -855,11 +863,11 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->button);
pp_flipper_update_or_insert_u32(
flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
flipper_format, HONDA_V2_FF_BTNSIG, instance->command_signature);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->count);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, 1U);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_EXTRA_BIT, 1U);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
@@ -868,11 +876,11 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
return ret;
}
void subghz_protocol_encoder_land_rover_v0_stop(void* context) {
void subghz_protocol_encoder_honda_v2_stop(void* context) {
pp_encoder_stop(context);
}
LevelDuration subghz_protocol_encoder_land_rover_v0_yield(void* context) {
LevelDuration subghz_protocol_encoder_honda_v2_yield(void* context) {
return pp_encoder_yield(context);
}
#endif
@@ -0,0 +1,38 @@
#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 "../defines.h"
#define HONDA_V2_PROTOCOL_NAME "Honda V2"
extern const SubGhzProtocol honda_v2_protocol;
void* subghz_protocol_decoder_honda_v2_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_honda_v2_free(void* context);
void subghz_protocol_decoder_honda_v2_reset(void* context);
void subghz_protocol_decoder_honda_v2_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_honda_v2_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_honda_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_v2_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_honda_v2_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_honda_v2_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_honda_v2_stop(void* context);
LevelDuration subghz_protocol_encoder_honda_v2_yield(void* context);
@@ -10,26 +10,63 @@ uint64_t kia_v6_a_key = 0;
uint64_t kia_v6_b_key = 0;
uint64_t kia_v5_key = 0;
void protopirate_keys_load(SubGhzEnvironment* environment) {
static bool kia_mf_key_loaded = false;
static bool kia_v6_a_key_loaded = false;
static bool kia_v6_b_key_loaded = false;
static bool kia_v5_key_loaded = false;
static void protopirate_keys_reset(void) {
kia_mf_key = 0;
kia_v6_a_key = 0;
kia_v6_b_key = 0;
kia_v5_key = 0;
kia_mf_key_loaded = false;
kia_v6_a_key_loaded = false;
kia_v6_b_key_loaded = false;
kia_v5_key_loaded = false;
}
bool protopirate_keys_load(SubGhzEnvironment* environment) {
protopirate_keys_reset();
if(!environment) {
return false;
}
SubGhzKeystore* keystore = subghz_environment_get_keystore(environment);
// Load keys from secure keystore
for
M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) {
switch(manufacture_code->type) {
case KIA_KEY1:
kia_mf_key = manufacture_code->key;
break;
case KIA_KEY2:
kia_v6_a_key = manufacture_code->key;
break;
case KIA_KEY3:
kia_v6_b_key = manufacture_code->key;
break;
case KIA_KEY4:
kia_v5_key = manufacture_code->key;
break;
}
}
if(!keystore) {
return false;
}
SubGhzKeyArray_t* key_data = subghz_keystore_get_data(keystore);
if(!key_data) {
return false;
}
for
M_EACH(manufacture_code, *key_data, SubGhzKeyArray_t) {
switch(manufacture_code->type) {
case KIA_KEY1:
kia_mf_key = manufacture_code->key;
kia_mf_key_loaded = true;
break;
case KIA_KEY2:
kia_v6_a_key = manufacture_code->key;
kia_v6_a_key_loaded = true;
break;
case KIA_KEY3:
kia_v6_b_key = manufacture_code->key;
kia_v6_b_key_loaded = true;
break;
case KIA_KEY4:
kia_v5_key = manufacture_code->key;
kia_v5_key_loaded = true;
break;
default:
break;
}
}
return kia_mf_key_loaded || kia_v6_a_key_loaded || kia_v6_b_key_loaded || kia_v5_key_loaded;
}
uint64_t get_kia_mf_key() {
@@ -47,3 +84,19 @@ uint64_t get_kia_v6_keystore_b() {
uint64_t get_kia_v5_key() {
return kia_v5_key;
}
bool protopirate_keys_has_kia_mf_key(void) {
return kia_mf_key_loaded;
}
bool protopirate_keys_has_kia_v6_keystore_a(void) {
return kia_v6_a_key_loaded;
}
bool protopirate_keys_has_kia_v6_keystore_b(void) {
return kia_v6_b_key_loaded;
}
bool protopirate_keys_has_kia_v5_key(void) {
return kia_v5_key_loaded;
}
@@ -1,5 +1,6 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <lib/subghz/environment.h>
@@ -18,4 +19,9 @@ uint64_t get_kia_v6_keystore_b();
uint64_t get_kia_v5_key();
void protopirate_keys_load(SubGhzEnvironment* environment);
bool protopirate_keys_has_kia_mf_key(void);
bool protopirate_keys_has_kia_v6_keystore_a(void);
bool protopirate_keys_has_kia_v6_keystore_b(void);
bool protopirate_keys_has_kia_v5_key(void);
bool protopirate_keys_load(SubGhzEnvironment* environment);
@@ -458,7 +458,7 @@ static bool kia_v0_decoder_try_honda(SubGhzProtocolDecoderKIA* instance) {
kia_v0_decoder_commit(instance, key, KIA_V0_TYPE_HONDA, KIA_V0_BIT_COUNT_HONDA);
return true;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static size_t kia_v0_append_short_pairs(LevelDuration* upload, size_t index, size_t count) {
return pp_emit_short_pairs(
@@ -466,7 +466,7 @@ static size_t kia_v0_append_short_pairs(LevelDuration* upload, size_t index, siz
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static size_t kia_v0_append_data_pairs(
LevelDuration* upload,
@@ -484,7 +484,7 @@ static size_t kia_v0_append_data_pairs(
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_build_honda_upload(SubGhzProtocolEncoderKIA* instance, uint64_t raw) {
size_t index = 0;
@@ -507,7 +507,7 @@ static void kia_v0_build_honda_upload(SubGhzProtocolEncoderKIA* instance, uint64
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_build_kia_upload(SubGhzProtocolEncoderKIA* instance, uint64_t raw) {
size_t index = 0;
@@ -528,7 +528,7 @@ static void kia_v0_build_kia_upload(SubGhzProtocolEncoderKIA* instance, uint64_t
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_build_suzuki_upload(SubGhzProtocolEncoderKIA* instance, uint64_t shifted) {
size_t index = 0;
@@ -557,7 +557,7 @@ static uint8_t kia_v0_infer_type_from_bits(uint32_t bits) {
if(bits == KIA_V0_BIT_COUNT_HONDA) return KIA_V0_TYPE_HONDA;
return KIA_V0_TYPE_KIA;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_encoder_apply_fields(SubGhzProtocolEncoderKIA* instance) {
instance->generic.serial = instance->fields.serial;
@@ -611,7 +611,7 @@ static void kia_v0_encoder_apply_fields(SubGhzProtocolEncoderKIA* instance) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_encoder_sync_from_generic(SubGhzProtocolEncoderKIA* instance) {
instance->fields.serial = instance->generic.serial;
@@ -625,7 +625,7 @@ static void kia_v0_encoder_sync_from_generic(SubGhzProtocolEncoderKIA* instance)
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_encoder_apply_flipper_fields(
SubGhzProtocolEncoderKIA* instance,
@@ -668,7 +668,7 @@ static void kia_v0_encoder_apply_flipper_fields(
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_kia_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -686,7 +686,7 @@ void* subghz_protocol_encoder_kia_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
subghz_protocol_encoder_kia_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -764,7 +764,7 @@ SubGhzProtocolStatus
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void subghz_protocol_encoder_kia_set_button(void* context, uint8_t button) {
furi_check(context);
@@ -778,7 +778,7 @@ void subghz_protocol_encoder_kia_set_button(void* context, uint8_t button) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void subghz_protocol_encoder_kia_set_counter(void* context, uint16_t counter) {
furi_check(context);
@@ -788,7 +788,7 @@ void subghz_protocol_encoder_kia_set_counter(void* context, uint16_t counter) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void subghz_protocol_encoder_kia_increment_counter(void* context) {
furi_check(context);
@@ -798,7 +798,7 @@ void subghz_protocol_encoder_kia_increment_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint16_t subghz_protocol_encoder_kia_get_counter(void* context) {
furi_check(context);
@@ -807,7 +807,7 @@ uint16_t subghz_protocol_encoder_kia_get_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint8_t subghz_protocol_encoder_kia_get_button(void* context) {
furi_check(context);
@@ -1106,7 +1106,7 @@ const SubGhzProtocolDecoder subghz_protocol_kia_decoder = {
.get_string = subghz_protocol_decoder_kia_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_kia_encoder = {
.alloc = subghz_protocol_encoder_kia_alloc,
.free = pp_encoder_free,
@@ -1130,6 +1130,14 @@ const SubGhzProtocol kia_protocol_v0 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_kia_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_kia_encoder,
#else
.encoder = NULL,
#endif
};
@@ -60,7 +60,7 @@ const SubGhzProtocolDecoder kia_protocol_v1_decoder = {
.get_string = kia_protocol_decoder_v1_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v1_encoder = {
.alloc = kia_protocol_encoder_v1_alloc,
.free = pp_encoder_free,
@@ -86,8 +86,20 @@ const SubGhzProtocol kia_protocol_v1 = {
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v1_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v1_encoder,
#else
.encoder = NULL,
#endif
};
static void kia_v1_check_remote_controller(SubGhzProtocolDecoderKiaV1* instance);
@@ -144,10 +156,13 @@ static const char* kia_v1_get_button_name(uint8_t btn) {
}
return name;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV1* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV1));
SubGhzProtocolEncoderKiaV1* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV1));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v1;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -161,12 +176,14 @@ void* kia_protocol_encoder_v1_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v1_get_upload(SubGhzProtocolEncoderKiaV1* instance) {
furi_check(instance);
if(instance->encoder.upload == NULL) return; // lazy buffer not yet allocated
size_t index = 0;
LevelDuration* up = instance->encoder.upload;
const size_t cap = KIA_V1_UPLOAD_CAPACITY;
uint8_t cnt_high = (instance->generic.cnt >> 8) & 0xF;
uint8_t char_data[7];
@@ -184,34 +201,25 @@ static void kia_protocol_encoder_v1_get_upload(SubGhzProtocolEncoderKiaV1* insta
instance->generic.btn << 16 | (instance->generic.cnt & 0xFF) << 8 |
((instance->generic.cnt >> 8) & 0xF) << 4 | crc;
const uint32_t te_short = (uint32_t)kia_protocol_v1_const.te_short;
const uint32_t te_long = (uint32_t)kia_protocol_v1_const.te_long;
for(uint8_t burst = 0; burst < KIA_V1_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, KIA_V1_INTER_BURST_GAP_US);
index = pp_emit(up, index, cap, false, KIA_V1_INTER_BURST_GAP_US);
}
for(int i = 0; i < KIA_V1_HEADER_PULSES; i++) {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_long);
index = pp_emit(up, index, cap, false, te_long);
index = pp_emit(up, index, cap, true, te_long);
}
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
index = pp_emit(up, index, cap, false, te_short);
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
if(bit_read(instance->generic.data, i - 2)) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_short);
}
bool bit = bit_read(instance->generic.data, i - 2);
index = pp_emit(up, index, cap, bit, te_short);
index = pp_emit(up, index, cap, !bit, te_short);
}
}
@@ -228,7 +236,7 @@ static void kia_protocol_encoder_v1_get_upload(SubGhzProtocolEncoderKiaV1* insta
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
kia_protocol_encoder_v1_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -287,7 +295,7 @@ SubGhzProtocolStatus
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v1_set_button(void* context, uint8_t button) {
furi_check(context);
@@ -298,7 +306,7 @@ void kia_protocol_encoder_v1_set_button(void* context, uint8_t button) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v1_set_counter(void* context, uint16_t counter) {
furi_check(context);
@@ -312,7 +320,7 @@ void kia_protocol_encoder_v1_set_counter(void* context, uint16_t counter) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v1_increment_counter(void* context) {
furi_check(context);
@@ -326,7 +334,7 @@ void kia_protocol_encoder_v1_increment_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint16_t kia_protocol_encoder_v1_get_counter(void* context) {
furi_check(context);
@@ -335,7 +343,7 @@ uint16_t kia_protocol_encoder_v1_get_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint8_t kia_protocol_encoder_v1_get_button(void* context) {
furi_check(context);
@@ -347,7 +355,10 @@ uint8_t kia_protocol_encoder_v1_get_button(void* context) {
void* kia_protocol_decoder_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV1* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV1));
SubGhzProtocolDecoderKiaV1* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV1));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v1;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
@@ -52,7 +52,7 @@ const SubGhzProtocolDecoder kia_protocol_v2_decoder = {
.get_string = kia_protocol_decoder_v2_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v2_encoder = {
.alloc = kia_protocol_encoder_v2_alloc,
.free = pp_encoder_free,
@@ -76,8 +76,16 @@ const SubGhzProtocol kia_protocol_v2 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v2_encoder,
#else
.encoder = NULL,
#endif
};
static uint8_t kia_v2_calculate_crc(uint64_t data) {
@@ -100,41 +108,33 @@ static uint8_t kia_v2_calculate_crc(uint64_t data) {
return (crc + 1) & 0x0F;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v2_get_upload(SubGhzProtocolEncoderKiaV2* instance) {
furi_check(instance);
if(instance->encoder.upload == NULL) return;
size_t index = 0;
LevelDuration* up = instance->encoder.upload;
const size_t cap = KIA_V2_UPLOAD_CAPACITY;
const uint32_t te_short = (uint32_t)kia_protocol_v2_const.te_short;
const uint32_t te_long = (uint32_t)kia_protocol_v2_const.te_long;
uint8_t crc = kia_v2_calculate_crc(instance->generic.data);
instance->generic.data = (instance->generic.data & ~0x0FULL) | crc;
for(uint8_t burst = 0; burst < KIA_V2_TOTAL_BURSTS; burst++) {
for(int i = 0; i < KIA_V2_HEADER_PAIRS; i++) {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_long);
index = pp_emit(up, index, cap, false, te_long);
index = pp_emit(up, index, cap, true, te_long);
}
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
index = pp_emit(up, index, cap, false, te_short);
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
bool bit = bit_read(instance->generic.data, i - 2);
if(bit) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_short);
}
index = pp_emit(up, index, cap, bit, te_short);
index = pp_emit(up, index, cap, !bit, te_short);
}
}
@@ -151,11 +151,14 @@ static void kia_protocol_encoder_v2_get_upload(SubGhzProtocolEncoderKiaV2* insta
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV2* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV2));
SubGhzProtocolEncoderKiaV2* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV2));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v2;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -170,7 +173,7 @@ void* kia_protocol_encoder_v2_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
kia_protocol_encoder_v2_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -256,7 +259,10 @@ SubGhzProtocolStatus
void* kia_protocol_decoder_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV2* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV2));
SubGhzProtocolDecoderKiaV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV2));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v2;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
@@ -87,7 +87,7 @@ static void kia_v3_v4_add_raw_bit(SubGhzProtocolDecoderKiaV3V4* instance, bool b
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static inline void kia_v3_v4_emit_bit_pwm(LevelDuration* upload, size_t* idx, bool bit, bool v4) {
const uint32_t te_short = kia_protocol_v3_v4_const.te_short;
const uint32_t te_long = kia_protocol_v3_v4_const.te_long;
@@ -173,7 +173,7 @@ const SubGhzProtocolDecoder kia_protocol_v3_v4_decoder = {
.get_string = kia_protocol_decoder_v3_v4_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v3_v4_encoder = {
.alloc = kia_protocol_encoder_v3_v4_alloc,
.free = pp_encoder_free,
@@ -197,22 +197,34 @@ const SubGhzProtocol kia_protocol_v3_v4 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v3_v4_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v3_v4_encoder,
#else
.encoder = NULL,
#endif
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v3_v4_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV3V4));
furi_check(instance);
if(environment) {
protopirate_keys_load(environment);
}
if(!protopirate_keys_has_kia_mf_key()) {
FURI_LOG_E(TAG, "Kia V3/V4 encoder missing KIA_KEY1 keystore entry");
return NULL;
}
SubGhzProtocolEncoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV3V4));
furi_check(instance);
instance->base.protocol = &kia_protocol_v3_v4;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -234,7 +246,7 @@ void* kia_protocol_encoder_v3_v4_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v3_v4_build_packet(
SubGhzProtocolEncoderKiaV3V4* instance,
@@ -303,7 +315,7 @@ static void kia_protocol_encoder_v3_v4_patch_crc(SubGhzProtocolEncoderKiaV3V4* i
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4* instance) {
furi_check(instance);
@@ -370,7 +382,7 @@ static void kia_protocol_encoder_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4*
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
kia_protocol_encoder_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -474,7 +486,7 @@ SubGhzProtocolStatus
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_stop(void* context) {
if(!context) return;
@@ -484,7 +496,7 @@ void kia_protocol_encoder_v3_v4_stop(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
LevelDuration kia_protocol_encoder_v3_v4_yield(void* context) {
SubGhzProtocolEncoderKiaV3V4* instance = context;
@@ -537,7 +549,7 @@ LevelDuration kia_protocol_encoder_v3_v4_yield(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_set_button(void* context, uint8_t button) {
furi_check(context);
@@ -549,7 +561,7 @@ void kia_protocol_encoder_v3_v4_set_button(void* context, uint8_t button) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_set_counter(void* context, uint16_t counter) {
furi_check(context);
@@ -561,7 +573,7 @@ void kia_protocol_encoder_v3_v4_set_counter(void* context, uint16_t counter) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_increment_counter(void* context) {
furi_check(context);
@@ -573,7 +585,7 @@ void kia_protocol_encoder_v3_v4_increment_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint16_t kia_protocol_encoder_v3_v4_get_counter(void* context) {
furi_check(context);
@@ -582,7 +594,7 @@ uint16_t kia_protocol_encoder_v3_v4_get_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint8_t kia_protocol_encoder_v3_v4_get_button(void* context) {
furi_check(context);
@@ -79,7 +79,7 @@ static uint16_t mixer_decode(uint32_t encrypted) {
return (s0 + (s1 << 8)) & 0xFFFF;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint32_t mixer_encode(uint32_t serial, uint16_t counter, uint8_t button) {
build_keystore_from_mfkey(keystore_bytes);
@@ -196,7 +196,7 @@ const SubGhzProtocolDecoder kia_protocol_v5_decoder = {
.get_string = kia_protocol_decoder_v5_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v5_encoder = {
.alloc = kia_protocol_encoder_v5_alloc,
.free = pp_encoder_free,
@@ -219,15 +219,23 @@ const SubGhzProtocol kia_protocol_v5 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v5_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v5_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t kia_v5_calculate_crc(uint64_t data) {
uint8_t crc = 0;
@@ -243,12 +251,16 @@ static uint8_t kia_v5_calculate_crc(uint64_t data) {
}
void* kia_protocol_encoder_v5_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderKiaV5* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV5));
furi_check(instance);
if(environment) {
protopirate_keys_load(environment);
}
if(!protopirate_keys_has_kia_v5_key()) {
FURI_LOG_E(TAG, "Kia V5 encoder missing KIA_KEY4 keystore entry");
return NULL;
}
SubGhzProtocolEncoderKiaV5* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV5));
furi_check(instance);
instance->base.protocol = &kia_protocol_v5;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -114,7 +114,7 @@ const SubGhzProtocolDecoder kia_protocol_v6_decoder = {
.get_string = kia_protocol_decoder_v6_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v6_encoder = {
.alloc = kia_protocol_encoder_v6_alloc,
.free = pp_encoder_free,
@@ -137,8 +137,16 @@ const SubGhzProtocol kia_protocol_v6 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v6_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v6_encoder,
#else
.encoder = NULL,
#endif
};
#define kia_v6_crc8(data, len) subghz_protocol_blocks_crc8((data), (len), 0x07, 0xFF)
@@ -215,7 +223,7 @@ static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) {
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void aes_subbytes(uint8_t* state) {
for(int row = 0; row < 4; row++) {
for(int col = 0; col < 4; col++) {
@@ -358,7 +366,7 @@ static void get_kia_v6_aes_key(uint8_t* aes_key) {
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v6_encrypt_payload(
uint8_t fx_field,
uint32_t serial,
@@ -812,7 +820,7 @@ void kia_protocol_decoder_v6_get_string(void* context, FuriString* output) {
instance->crc2_field);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
#define KIA_V6_PREAMBLE_PAIRS_1 640
#define KIA_V6_PREAMBLE_PAIRS_2 38
@@ -928,13 +936,17 @@ static void kia_protocol_encoder_v6_build_upload(SubGhzProtocolEncoderKiaV6* ins
}
void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6));
if(!instance) return NULL;
memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6));
if(environment) {
protopirate_keys_load(environment);
}
if(!protopirate_keys_has_kia_v6_keystore_a() || !protopirate_keys_has_kia_v6_keystore_b()) {
FURI_LOG_E(TAG, "Kia V6 encoder missing KIA_KEY2/KIA_KEY3 keystore entries");
return NULL;
}
SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6));
if(!instance) return NULL;
memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6));
instance->base.protocol = &kia_protocol_v6;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -985,4 +997,4 @@ SubGhzProtocolStatus
return SubGhzProtocolStatusOk;
}
#endif // ENABLE_EMULATE_FEATURE
#endif // PROTOPIRATE_WITH_ENCODER
@@ -37,7 +37,7 @@ SubGhzProtocolStatus
kia_protocol_decoder_v6_deserialize(void* context, FlipperFormat* flipper_format);
void kia_protocol_decoder_v6_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
kia_protocol_encoder_v6_deserialize(void* context, FlipperFormat* flipper_format);
@@ -43,7 +43,7 @@ struct SubGhzProtocolDecoderKiaV7 {
bool crc_valid;
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
struct SubGhzProtocolEncoderKiaV7 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -155,7 +155,7 @@ static uint64_t kia_v7_encode_key(
return pp_bytes_to_u64_be(bytes);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
@@ -233,7 +233,7 @@ const SubGhzProtocolDecoder kia_protocol_v7_decoder = {
.get_string = kia_protocol_decoder_v7_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
.alloc = kia_protocol_encoder_v7_alloc,
.free = pp_encoder_free,
@@ -257,11 +257,19 @@ const SubGhzProtocol kia_protocol_v7 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v7_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v7_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -35,7 +35,7 @@ SubGhzProtocolStatus
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
@@ -1,38 +0,0 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#include <lib/toolbox/level_duration.h>
#include "../defines.h"
#define LAND_ROVER_PROTOCOL_V0_NAME "Land Rover V0"
extern const SubGhzProtocol land_rover_v0_protocol;
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);
@@ -41,7 +41,7 @@ typedef struct SubGhzProtocolDecoderMazdaV0 {
uint32_t count;
} SubGhzProtocolDecoderMazdaV0;
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderMazdaV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -65,7 +65,7 @@ typedef enum {
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
#if PROTOPIRATE_WITH_ENCODER
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,
@@ -96,7 +96,7 @@ const SubGhzProtocolDecoder subghz_protocol_mazda_v0_decoder = {
.get_string = subghz_protocol_decoder_mazda_v0_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
.alloc = subghz_protocol_encoder_mazda_v0_alloc,
.free = pp_encoder_free,
@@ -117,11 +117,20 @@ const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
const SubGhzProtocol mazda_v0_protocol = {
.name = MAZDA_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_mazda_v0_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_mazda_v0_encoder,
#else
.encoder = NULL,
#endif
};
// =============================================================================
@@ -193,7 +202,7 @@ static void mazda_v0_decode_key(SubGhzBlockGeneric* generic) {
generic->data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter) {
uint8_t data[8];
@@ -305,7 +314,7 @@ static SubGhzProtocolStatus mazda_v0_write_display(
// ENCODER
// =============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -122,8 +122,16 @@ const SubGhzProtocol mitsubishi_v0_protocol = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_mitsubishi_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_mitsubishi_encoder,
#else
.encoder = NULL,
#endif
};
void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment) {
@@ -1,14 +1,18 @@
#include "../protopirate_protocol_plugins.h"
#include "../protocols_common.h"
#include "../chrysler_v0.h"
#include "../fiat_v0.h"
#include "../fiat_v1.h"
#include "../fiat_v2.h"
#include "../ford_v0.h"
#include "../ford_v3.h"
#include "../kia_v1.h"
#include "../kia_v2.h"
#include "../mazda_v0.h"
#include "../porsche_touareg.h"
#include "../psa.h"
#include "../renault_v0.h"
#include "../subaru.h"
#include "../vag.h"
#include "../star_line.h"
#include "../honda_v1.h"
@@ -16,14 +20,17 @@ static const SubGhzProtocol* const protopirate_protocol_registry_am_items[] = {
&chrysler_protocol_v0,
&fiat_protocol_v0,
&fiat_v1_protocol,
&fiat_v2_protocol,
&ford_protocol_v0,
&ford_protocol_v3,
&honda_v1_protocol,
&kia_protocol_v1,
&kia_protocol_v2,
&mazda_v0_protocol,
&porsche_touareg_protocol,
&psa_protocol,
&renault_v0_protocol,
&subaru_protocol,
&vag_protocol,
&subghz_protocol_star_line,
};
@@ -34,9 +41,11 @@ static const SubGhzProtocolRegistry protopirate_protocol_registry_am = {
};
static const ProtoPirateProtocolPlugin protopirate_am_plugin = {
.plugin_name = "ProtoPirate AM Registry",
.filter = ProtoPirateProtocolRegistryFilterAM,
.plugin_name = "ProtoPirate AM Default Registry",
.kind = ProtoPirateProtocolPluginKindRx,
.route = ProtoPirateProtocolRegistryRouteAMDefault,
.registry = &protopirate_protocol_registry_am,
.release = NULL,
};
static const FlipperAppPluginDescriptor protopirate_am_plugin_descriptor = {
@@ -0,0 +1,31 @@
#include "../protopirate_protocol_plugins.h"
#include "../protocols_common.h"
#include "../vag.h"
static const SubGhzProtocol* const protopirate_protocol_registry_am_vag_items[] = {
&vag_protocol,
};
static const SubGhzProtocolRegistry protopirate_protocol_registry_am_vag = {
.items = protopirate_protocol_registry_am_vag_items,
.size = sizeof(protopirate_protocol_registry_am_vag_items) /
sizeof(protopirate_protocol_registry_am_vag_items[0]),
};
static const ProtoPirateProtocolPlugin protopirate_am_vag_plugin = {
.plugin_name = "ProtoPirate AM VAG Registry",
.kind = ProtoPirateProtocolPluginKindRx,
.route = ProtoPirateProtocolRegistryRouteAMVag,
.registry = &protopirate_protocol_registry_am_vag,
.release = NULL,
};
static const FlipperAppPluginDescriptor protopirate_am_vag_plugin_descriptor = {
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &protopirate_am_vag_plugin,
};
const FlipperAppPluginDescriptor* protopirate_am_vag_plugin_ep(void) {
return &protopirate_am_vag_plugin_descriptor;
}
@@ -0,0 +1,37 @@
#include "../protopirate_protocol_plugins.h"
#include "../protocols_common.h"
#include "../ford_v1.h"
#include "../ford_v2.h"
#include "../ford_v3.h"
#include "../honda_v2.h"
static const SubGhzProtocol* const protopirate_protocol_registry_fm_f4_items[] = {
&ford_protocol_v1,
&ford_protocol_v2,
&ford_protocol_v3,
&honda_v2_protocol,
};
static const SubGhzProtocolRegistry protopirate_protocol_registry_fm_f4 = {
.items = protopirate_protocol_registry_fm_f4_items,
.size = sizeof(protopirate_protocol_registry_fm_f4_items) /
sizeof(protopirate_protocol_registry_fm_f4_items[0]),
};
static const ProtoPirateProtocolPlugin protopirate_fm_f4_plugin = {
.plugin_name = "ProtoPirate FM F4 Registry",
.kind = ProtoPirateProtocolPluginKindRx,
.route = ProtoPirateProtocolRegistryRouteFMF4,
.registry = &protopirate_protocol_registry_fm_f4,
.release = NULL,
};
static const FlipperAppPluginDescriptor protopirate_fm_f4_plugin_descriptor = {
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &protopirate_fm_f4_plugin,
};
const FlipperAppPluginDescriptor* protopirate_fm_f4_plugin_ep(void) {
return &protopirate_fm_f4_plugin_descriptor;
}
@@ -0,0 +1,31 @@
#include "../protopirate_protocol_plugins.h"
#include "../protocols_common.h"
#include "../honda_static.h"
static const SubGhzProtocol* const protopirate_protocol_registry_fm_honda1_items[] = {
&honda_static_protocol,
};
static const SubGhzProtocolRegistry protopirate_protocol_registry_fm_honda1 = {
.items = protopirate_protocol_registry_fm_honda1_items,
.size = sizeof(protopirate_protocol_registry_fm_honda1_items) /
sizeof(protopirate_protocol_registry_fm_honda1_items[0]),
};
static const ProtoPirateProtocolPlugin protopirate_fm_honda1_plugin = {
.plugin_name = "ProtoPirate FM Honda1 Registry",
.kind = ProtoPirateProtocolPluginKindRx,
.route = ProtoPirateProtocolRegistryRouteFMHonda1,
.registry = &protopirate_protocol_registry_fm_honda1,
.release = NULL,
};
static const FlipperAppPluginDescriptor protopirate_fm_honda1_plugin_descriptor = {
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &protopirate_fm_honda1_plugin,
};
const FlipperAppPluginDescriptor* protopirate_fm_honda1_plugin_ep(void) {
return &protopirate_fm_honda1_plugin_descriptor;
}
@@ -1,15 +1,11 @@
#include "../protopirate_protocol_plugins.h"
#include "../protocols_common.h"
#include "../scher_khan.h"
#include "../kia_v0.h"
#include "../kia_v3_v4.h"
#include "../kia_v5.h"
#include "../kia_v6.h"
#include "../kia_v7.h"
#include "../ford_v1.h"
#include "../ford_v2.h"
#include "../ford_v3.h"
#include "../honda_static.h"
#include "../land_rover_v0.h"
#include "../mazda_v0.h"
#include "../mitsubishi_v0.h"
#include "../psa.h"
@@ -20,11 +16,6 @@ static const SubGhzProtocol* const protopirate_protocol_registry_fm_items[] = {
&kia_protocol_v3_v4,
&kia_protocol_v5,
&kia_protocol_v6,
&ford_protocol_v1,
&ford_protocol_v2,
&ford_protocol_v3,
&honda_static_protocol,
&land_rover_v0_protocol,
&mazda_v0_protocol,
&mitsubishi_v0_protocol,
&kia_protocol_v7,
@@ -38,9 +29,11 @@ static const SubGhzProtocolRegistry protopirate_protocol_registry_fm = {
};
static const ProtoPirateProtocolPlugin protopirate_fm_plugin = {
.plugin_name = "ProtoPirate FM Registry",
.filter = ProtoPirateProtocolRegistryFilterFM,
.plugin_name = "ProtoPirate FM Default Registry",
.kind = ProtoPirateProtocolPluginKindRx,
.route = ProtoPirateProtocolRegistryRouteFMDefault,
.registry = &protopirate_protocol_registry_fm,
.release = NULL,
};
static const FlipperAppPluginDescriptor protopirate_fm_plugin_descriptor = {
@@ -0,0 +1,44 @@
#include "../protopirate_protocol_plugins.h"
#include "../protocols_common.h"
#ifndef PP_TX_PROTOCOL_HEADER
#error "PP_TX_PROTOCOL_HEADER must be defined for TX protocol plugins"
#endif
#include PP_TX_PROTOCOL_HEADER
#ifndef PP_TX_PROTOCOL_NAME
#error "PP_TX_PROTOCOL_NAME must be defined for TX protocol plugins"
#endif
#ifndef PP_TX_PROTOCOL_ITEM
#error "PP_TX_PROTOCOL_ITEM must be defined for TX protocol plugins"
#endif
static const SubGhzProtocol* const protopirate_tx_protocol_registry_items[] = {
&PP_TX_PROTOCOL_ITEM,
};
static const SubGhzProtocolRegistry protopirate_tx_protocol_registry = {
.items = protopirate_tx_protocol_registry_items,
.size = 1U,
};
static const ProtoPirateProtocolPlugin protopirate_tx_protocol_plugin = {
.plugin_name = PP_TX_PROTOCOL_NAME,
.kind = ProtoPirateProtocolPluginKindTx,
.route = ProtoPirateProtocolRegistryRouteAMDefault,
.protocol_name = PP_TX_PROTOCOL_NAME,
.registry = &protopirate_tx_protocol_registry,
.release = pp_shared_upload_release,
};
static const FlipperAppPluginDescriptor protopirate_tx_protocol_plugin_descriptor = {
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &protopirate_tx_protocol_plugin,
};
const FlipperAppPluginDescriptor* protopirate_tx_protocol_plugin_ep(void) {
return &protopirate_tx_protocol_plugin_descriptor;
}
@@ -159,8 +159,16 @@ const SubGhzProtocol porsche_touareg_protocol = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_porsche_cayenne_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_porsche_cayenne_encoder,
#else
.encoder = NULL,
#endif
};
void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment) {
@@ -1,10 +1,9 @@
#include "protocol_items.h"
#include <furi.h>
#ifdef ENABLE_TIMING_TUNER_SCENE
#include <string.h>
#endif
#define TAG "ProtoPirateRegistry"
#include <furi.h>
#include <string.h>
#define TAG "ProtoPirateCatalog"
#define PROTOPIRATE_CC1101_REG_MDMCFG2 0x12U
#define PROTOPIRATE_CC1101_MOD_FORMAT_MASK 0x70U
@@ -13,6 +12,178 @@
#define PROTOPIRATE_CC1101_MOD_FORMAT_ASK_OOK 0x30U
#define PROTOPIRATE_CC1101_MOD_FORMAT_4FSK 0x40U
#define PROTOPIRATE_CC1101_MOD_FORMAT_MSK 0x70U
#define PROTOPIRATE_VAG_FREQUENCY_MIN 434190000UL
#define PROTOPIRATE_VAG_FREQUENCY_MAX 434450000UL
#define PROTOPIRATE_COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
typedef enum {
ProtoPirateProtocolCatalogModulationAM = 0,
ProtoPirateProtocolCatalogModulationFM,
} ProtoPirateProtocolCatalogModulation;
typedef struct {
const char* alias;
const char* canonical_name;
} ProtoPirateProtocolCatalogAlias;
static const ProtoPirateProtocolCatalogEntry protopirate_protocol_catalog[] = {
{"Chrysler V0", ProtoPirateProtocolCatalogRouteAMDefault, "chrysler_v0"},
{"Fiat V0", ProtoPirateProtocolCatalogRouteAMDefault, "fiat_v0"},
{"Fiat V1", ProtoPirateProtocolCatalogRouteAMDefault, "fiat_v1"},
{"Fiat V2", ProtoPirateProtocolCatalogRouteAMDefault, NULL},
{"Ford V0", ProtoPirateProtocolCatalogRouteAMDefault, "ford_v0"},
{"Ford V1", ProtoPirateProtocolCatalogRouteFMF4, "ford_v1"},
{"Ford V2", ProtoPirateProtocolCatalogRouteFMF4, "ford_v2"},
{"Ford V3", ProtoPirateProtocolCatalogRouteFMF4, NULL},
{"Honda Static", ProtoPirateProtocolCatalogRouteFMHonda1, "honda_static"},
{"Honda V1", ProtoPirateProtocolCatalogRouteAMDefault, "honda_v1"},
{"Kia V0", ProtoPirateProtocolCatalogRouteFMDefault, "kia_v0"},
{"Kia V1", ProtoPirateProtocolCatalogRouteAMDefault, "kia_v1"},
{"Kia V2", ProtoPirateProtocolCatalogRouteAMDefault, "kia_v2"},
{"Kia V3/V4", ProtoPirateProtocolCatalogRouteFMDefault, "kia_v3_v4"},
{"Kia V5", ProtoPirateProtocolCatalogRouteFMDefault, "kia_v5"},
{"Kia V6", ProtoPirateProtocolCatalogRouteFMDefault, "kia_v6"},
{"Kia V7", ProtoPirateProtocolCatalogRouteFMDefault, "kia_v7"},
{"Honda V2", ProtoPirateProtocolCatalogRouteFMF4, "honda_v2"},
{"Mazda V0", ProtoPirateProtocolCatalogRouteByModulation, "mazda_v0"},
{"Mitsubishi V0", ProtoPirateProtocolCatalogRouteFMDefault, NULL},
{"Porsche Touareg", ProtoPirateProtocolCatalogRouteAMDefault, NULL},
{"PSA", ProtoPirateProtocolCatalogRouteByModulation, "psa"},
{"Renault V0", ProtoPirateProtocolCatalogRouteAMDefault, "renault_v0"},
{"Scher-Khan", ProtoPirateProtocolCatalogRouteFMDefault, NULL},
{"Star Line", ProtoPirateProtocolCatalogRouteAMDefault, "star_line"},
{"Subaru", ProtoPirateProtocolCatalogRouteAMDefault, "subaru"},
{"VAG", ProtoPirateProtocolCatalogRouteAMVag, "vag"},
};
static const ProtoPirateProtocolCatalogAlias protopirate_protocol_catalog_aliases[] = {
{"StarLine", "Star Line"},
{"Kia V3", "Kia V3/V4"},
{"Kia V4", "Kia V3/V4"},
{"KIA/HYU V3", "Kia V3/V4"},
{"KIA/HYU V4", "Kia V3/V4"},
{"Suzuki", "Kia V0"},
{"Suzuki V0", "Kia V0"},
{"Honda V0", "Kia V0"},
{"Land Rover V0", "Honda V2"},
{"VW", "VAG"},
};
static bool protopirate_catalog_string_equal(const char* a, const char* b) {
return a && b && strcmp(a, b) == 0;
}
static bool protopirate_catalog_string_contains(const char* haystack, const char* needle) {
return haystack && needle && strstr(haystack, needle) != NULL;
}
static const ProtoPirateProtocolCatalogEntry*
protopirate_protocol_catalog_find_canonical(const char* canonical_name) {
if(!canonical_name || canonical_name[0] == '\0') {
return NULL;
}
for(size_t i = 0; i < PROTOPIRATE_COUNT_OF(protopirate_protocol_catalog); i++) {
if(protopirate_catalog_string_equal(
canonical_name, protopirate_protocol_catalog[i].canonical_name)) {
return &protopirate_protocol_catalog[i];
}
}
return NULL;
}
static const char* protopirate_protocol_catalog_alias_to_canonical(const char* protocol_name) {
if(!protocol_name || protocol_name[0] == '\0') {
return NULL;
}
for(size_t i = 0; i < PROTOPIRATE_COUNT_OF(protopirate_protocol_catalog_aliases); i++) {
if(protopirate_catalog_string_equal(
protocol_name, protopirate_protocol_catalog_aliases[i].alias)) {
return protopirate_protocol_catalog_aliases[i].canonical_name;
}
}
return NULL;
}
static const ProtoPirateProtocolCatalogEntry*
protopirate_protocol_catalog_find_substring(const char* protocol_name) {
if(!protocol_name || protocol_name[0] == '\0') {
return NULL;
}
for(size_t i = 0; i < PROTOPIRATE_COUNT_OF(protopirate_protocol_catalog); i++) {
if(protopirate_catalog_string_contains(
protocol_name, protopirate_protocol_catalog[i].canonical_name)) {
return &protopirate_protocol_catalog[i];
}
}
for(size_t i = 0; i < PROTOPIRATE_COUNT_OF(protopirate_protocol_catalog_aliases); i++) {
if(protopirate_catalog_string_contains(
protocol_name, protopirate_protocol_catalog_aliases[i].alias)) {
return protopirate_protocol_catalog_find_canonical(
protopirate_protocol_catalog_aliases[i].canonical_name);
}
}
return NULL;
}
const ProtoPirateProtocolCatalogEntry*
protopirate_protocol_catalog_find(const char* protocol_name) {
if(!protocol_name || protocol_name[0] == '\0') {
return NULL;
}
const ProtoPirateProtocolCatalogEntry* entry =
protopirate_protocol_catalog_find_canonical(protocol_name);
if(entry) {
return entry;
}
const char* canonical_name = protopirate_protocol_catalog_alias_to_canonical(protocol_name);
if(canonical_name) {
return protopirate_protocol_catalog_find_canonical(canonical_name);
}
return NULL;
}
const char* protopirate_protocol_catalog_canonical_name(const char* protocol_name) {
const ProtoPirateProtocolCatalogEntry* entry = protopirate_protocol_catalog_find(protocol_name);
return entry ? entry->canonical_name : protocol_name;
}
bool protopirate_protocol_catalog_can_tx(const char* protocol_name) {
return protopirate_protocol_catalog_tx_key(protocol_name) != NULL;
}
const char* protopirate_protocol_catalog_tx_key(const char* protocol_name) {
const ProtoPirateProtocolCatalogEntry* entry = protopirate_protocol_catalog_find(protocol_name);
return entry ? entry->tx_key : NULL;
}
const char*
protopirate_protocol_catalog_display_name(const char* protocol_name, uint32_t protocol_type) {
if(!protocol_name) {
return NULL;
}
if(protopirate_catalog_string_equal(protocol_name, "Suzuki") ||
protopirate_catalog_string_equal(protocol_name, "Suzuki V0")) {
return "Suzuki V0";
}
if(protopirate_catalog_string_equal(protocol_name, "Honda V0")) {
return "Honda V0";
}
const char* canonical_name = protopirate_protocol_catalog_canonical_name(protocol_name);
if(protopirate_catalog_string_equal(canonical_name, "Kia V0")) {
if(protocol_type == 2U) {
return "Suzuki V0";
}
if(protocol_type == 3U) {
return "Honda V0";
}
}
return canonical_name;
}
static bool protopirate_preset_try_get_register(
const uint8_t* preset_data,
@@ -40,7 +211,7 @@ static bool protopirate_preset_try_get_register(
return false;
}
ProtoPirateProtocolRegistryFilter protopirate_get_protocol_registry_filter_for_preset(
static ProtoPirateProtocolCatalogModulation protopirate_protocol_catalog_get_modulation(
const uint8_t* preset_data,
size_t preset_data_size) {
uint8_t mdmcfg2 = 0U;
@@ -48,267 +219,114 @@ ProtoPirateProtocolRegistryFilter protopirate_get_protocol_registry_filter_for_p
if(!protopirate_preset_try_get_register(
preset_data, preset_data_size, PROTOPIRATE_CC1101_REG_MDMCFG2, &mdmcfg2)) {
FURI_LOG_W(TAG, "Preset missing MDMCFG2, defaulting to AM registry");
return ProtoPirateProtocolRegistryFilterAM;
return ProtoPirateProtocolCatalogModulationAM;
}
// MDMCFG2[6:4] stores the CC1101 modulation format.
// ASK/OOK maps to our AM decoder set; the FSK-family formats map to FM.
switch(mdmcfg2 & PROTOPIRATE_CC1101_MOD_FORMAT_MASK) {
case PROTOPIRATE_CC1101_MOD_FORMAT_ASK_OOK:
return ProtoPirateProtocolRegistryFilterAM;
return ProtoPirateProtocolCatalogModulationAM;
case PROTOPIRATE_CC1101_MOD_FORMAT_2FSK:
case PROTOPIRATE_CC1101_MOD_FORMAT_GFSK:
case PROTOPIRATE_CC1101_MOD_FORMAT_4FSK:
case PROTOPIRATE_CC1101_MOD_FORMAT_MSK:
return ProtoPirateProtocolRegistryFilterFM;
return ProtoPirateProtocolCatalogModulationFM;
default:
FURI_LOG_W(TAG, "Unknown MDMCFG2 0x%02X, defaulting to AM registry", mdmcfg2);
return ProtoPirateProtocolRegistryFilterAM;
return ProtoPirateProtocolCatalogModulationAM;
}
}
static bool protopirate_frequency_in_vag_band(uint32_t frequency) {
return frequency >= PROTOPIRATE_VAG_FREQUENCY_MIN &&
frequency <= PROTOPIRATE_VAG_FREQUENCY_MAX;
}
static ProtoPirateProtocolRegistryRoute protopirate_catalog_route_from_policy(
ProtoPirateProtocolCatalogRoutePolicy policy,
ProtoPirateProtocolCatalogModulation modulation) {
switch(policy) {
case ProtoPirateProtocolCatalogRouteAMVag:
return ProtoPirateProtocolRegistryRouteAMVag;
case ProtoPirateProtocolCatalogRouteFMDefault:
return ProtoPirateProtocolRegistryRouteFMDefault;
case ProtoPirateProtocolCatalogRouteFMF4:
return ProtoPirateProtocolRegistryRouteFMF4;
case ProtoPirateProtocolCatalogRouteFMHonda1:
return ProtoPirateProtocolRegistryRouteFMHonda1;
case ProtoPirateProtocolCatalogRouteByModulation:
return (modulation == ProtoPirateProtocolCatalogModulationAM) ?
ProtoPirateProtocolRegistryRouteAMDefault :
ProtoPirateProtocolRegistryRouteFMDefault;
case ProtoPirateProtocolCatalogRouteAMDefault:
default:
return ProtoPirateProtocolRegistryRouteAMDefault;
}
}
ProtoPirateProtocolRegistryRoute protopirate_protocol_catalog_get_route(
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name) {
const ProtoPirateProtocolCatalogEntry* entry =
protopirate_protocol_catalog_find(protocol_name);
if(!entry) {
entry = protopirate_protocol_catalog_find_substring(protocol_name);
}
if(entry && entry->route_policy != ProtoPirateProtocolCatalogRouteByModulation) {
return protopirate_catalog_route_from_policy(
entry->route_policy, ProtoPirateProtocolCatalogModulationAM);
}
const ProtoPirateProtocolCatalogModulation modulation =
protopirate_protocol_catalog_get_modulation(preset_data, preset_data_size);
if(entry) {
return protopirate_catalog_route_from_policy(entry->route_policy, modulation);
}
if(modulation == ProtoPirateProtocolCatalogModulationAM) {
if(protopirate_frequency_in_vag_band(frequency)) {
return ProtoPirateProtocolRegistryRouteAMVag;
}
return ProtoPirateProtocolRegistryRouteAMDefault;
}
if(protopirate_catalog_string_contains(preset_name, "F4")) {
return ProtoPirateProtocolRegistryRouteFMF4;
}
if(protopirate_catalog_string_contains(preset_name, "Honda1") ||
protopirate_catalog_string_contains(preset_name, "Honda 1")) {
return ProtoPirateProtocolRegistryRouteFMHonda1;
}
return ProtoPirateProtocolRegistryRouteFMDefault;
}
ProtoPirateProtocolRegistryRoute protopirate_get_protocol_registry_route(
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name) {
return protopirate_protocol_catalog_get_route(
preset_name, frequency, preset_data, preset_data_size, protocol_name);
}
const char*
protopirate_get_protocol_registry_filter_name(ProtoPirateProtocolRegistryFilter filter) {
return (filter == ProtoPirateProtocolRegistryFilterFM) ? "FM" : "AM";
}
#ifdef ENABLE_TIMING_TUNER_SCENE
// Protocol timing definitions - mirrors the SubGhzBlockConst in each protocol
static const ProtoPirateProtocolTiming protocol_timings[] = {
// Honda Static
{
.name = HONDA_STATIC_PROTOCOL_NAME,
.te_short = 63,
.te_long = 700,
.te_delta = 120,
.min_count_bit = 64,
},
// Honda V1: Manchester 1000/2000µs
{
.name = HONDA_V1_PROTOCOL_NAME,
.te_short = 1000,
.te_long = 2000,
.te_delta = 400,
.min_count_bit = 64,
},
// Kia V0: PWM 250/500µs — Kia 61bit, Suzuki 64bit, Honda V0 72bit
{
.name = "Kia V0",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 61,
},
// Kia V1: OOK PCM 800µs timing
{
.name = "Kia V1",
.te_short = 800,
.te_long = 1600,
.te_delta = 200,
.min_count_bit = 56,
},
// Kia V2: Manchester 500/1000µs
{
.name = "Kia V2",
.te_short = 500,
.te_long = 1000,
.te_delta = 150,
.min_count_bit = 51,
},
// Kia V3/V4: PWM 400/800µs
{
.name = "Kia V3/V4",
.te_short = 400,
.te_long = 800,
.te_delta = 150,
.min_count_bit = 64,
},
// Kia V5: PWM 400/800µs (same as V3/V4)
{
.name = "Kia V5",
.te_short = 400,
.te_long = 800,
.te_delta = 150,
.min_count_bit = 64,
},
// Kia V6: Manchester 200/400µs
{
.name = "Kia V6",
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit = 144,
},
// Kia V7: Manchester 250/500µs
{
.name = KIA_PROTOCOL_V7_NAME,
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 64,
},
// Ford V0: Manchester 250/500µs
{
.name = "Ford V0",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 64,
},
// Chrysler V0: PWM short/long
{
.name = "Chrysler V0",
.te_short = 300,
.te_long = 3700,
.te_delta = 400,
.min_count_bit = 80,
},
// Ford V1: Manchester 65/130us
{
.name = FORD_PROTOCOL_V1_NAME,
.te_short = 65,
.te_long = 130,
.te_delta = 39,
.min_count_bit = 136,
},
// Ford V2: Manchester 200/400us
{
.name = FORD_PROTOCOL_V2_NAME,
.te_short = 200,
.te_long = 400,
.te_delta = 260,
.min_count_bit = 104,
},
// Ford V3: Manchester 240/480us
{
.name = FORD_PROTOCOL_V3_NAME,
.te_short = 240,
.te_long = 480,
.te_delta = 60,
.min_count_bit = 104,
},
// Fiat V0: Manchester 200/400µs
{
.name = "Fiat V0",
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit = 64,
},
// Fiat V1: Manchester dynamic (baseline Type A 260/520us)
{
.name = "Fiat V1",
.te_short = 260,
.te_long = 520,
.te_delta = 80,
.min_count_bit = 80,
},
// Mazda V0: 250/500us
{
.name = "Mazda V0",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 64,
},
// Land Rover V0: Differential PWM 250/500us + sync 750us
{
.name = LAND_ROVER_PROTOCOL_V0_NAME,
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 81,
},
// Porsche Touareg: 1680/3370us
{
.name = "Porsche Touareg",
.te_short = 1680,
.te_long = 3370,
.te_delta = 500,
.min_count_bit = 64,
},
// Subaru: PPM 800/1600µs
{
.name = "Subaru",
.te_short = 800,
.te_long = 1600,
.te_delta = 200,
.min_count_bit = 64,
},
// VW: Manchester 500/1000µs
{
.name = "VW",
.te_short = 500,
.te_long = 1000,
.te_delta = 120,
.min_count_bit = 80,
},
// Scher-Khan: PWM 750/1100µs
{
.name = "Scher-Khan",
.te_short = 750,
.te_long = 1100,
.te_delta = 180,
.min_count_bit = 35,
},
// Star Line: PWM 250/500µs
{
.name = "Star Line",
.te_short = 250,
.te_long = 500,
.te_delta = 120,
.min_count_bit = 64,
},
// PSA: Manchester 250/500µs (Pattern 1) or 125/250µs (Pattern 2)
{
.name = "PSA",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 128,
},
};
static const size_t protocol_timings_count = COUNT_OF(protocol_timings);
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name) {
if(!protocol_name) return NULL;
for(size_t i = 0; i < protocol_timings_count; i++) {
// Check for exact match or if the protocol name contains our timing name
if(strcmp(protocol_name, protocol_timings[i].name) == 0 ||
strstr(protocol_name, protocol_timings[i].name) != NULL) {
return &protocol_timings[i];
}
protopirate_get_protocol_registry_route_name(ProtoPirateProtocolRegistryRoute route) {
switch(route) {
case ProtoPirateProtocolRegistryRouteAMVag:
return "AM_VAG";
case ProtoPirateProtocolRegistryRouteFMDefault:
return "FM_DEFAULT";
case ProtoPirateProtocolRegistryRouteFMF4:
return "FM_F4";
case ProtoPirateProtocolRegistryRouteFMHonda1:
return "FM_HONDA1";
case ProtoPirateProtocolRegistryRouteAMDefault:
default:
return "AM_DEFAULT";
}
static const struct {
const char* alias;
const char* canonical;
} aliases[] = {
{"Honda V0", "Kia V0"},
{"Suzuki", "Kia V0"},
{"V3", "Kia V3/V4"},
{"V4", "Kia V3/V4"},
};
for(size_t a = 0; a < COUNT_OF(aliases); a++) {
if(strstr(protocol_name, aliases[a].alias) == NULL) continue;
for(size_t i = 0; i < protocol_timings_count; i++) {
if(strstr(protocol_timings[i].name, aliases[a].canonical) != NULL) {
return &protocol_timings[i];
}
}
}
return NULL;
}
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index) {
if(index >= protocol_timings_count) return NULL;
return &protocol_timings[index];
}
size_t protopirate_get_protocol_timing_count(void) {
return protocol_timings_count;
}
#endif
@@ -1,62 +1,57 @@
// protocols/protocol_items.h
#pragma once
#include <lib/subghz/types.h>
#include "kia_generic.h"
#include "scher_khan.h"
#include "kia_v0.h"
#include "kia_v1.h"
#include "kia_v2.h"
#include "kia_v3_v4.h"
#include "kia_v5.h"
#include "kia_v6.h"
#include "kia_v7.h"
#include "ford_v0.h"
#include "ford_v1.h"
#include "ford_v2.h"
#include "ford_v3.h"
#include "chrysler_v0.h"
#include "fiat_v0.h"
#include "fiat_v1.h"
#include "land_rover_v0.h"
#include "mazda_v0.h"
#include "porsche_touareg.h"
#include "subaru.h"
#include "vag.h"
#include "star_line.h"
#include "psa.h"
#include "honda_static.h"
#include "honda_v1.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum {
ProtoPirateProtocolRegistryFilterAM = 0,
ProtoPirateProtocolRegistryFilterFM,
} ProtoPirateProtocolRegistryFilter;
ProtoPirateProtocolRegistryRouteAMDefault = 0,
ProtoPirateProtocolRegistryRouteAMVag,
ProtoPirateProtocolRegistryRouteFMDefault,
ProtoPirateProtocolRegistryRouteFMF4,
ProtoPirateProtocolRegistryRouteFMHonda1,
} ProtoPirateProtocolRegistryRoute;
ProtoPirateProtocolRegistryFilter protopirate_get_protocol_registry_filter_for_preset(
const uint8_t* preset_data,
size_t preset_data_size);
typedef enum {
ProtoPirateProtocolCatalogRouteAMDefault = 0,
ProtoPirateProtocolCatalogRouteAMVag,
ProtoPirateProtocolCatalogRouteFMDefault,
ProtoPirateProtocolCatalogRouteFMF4,
ProtoPirateProtocolCatalogRouteFMHonda1,
ProtoPirateProtocolCatalogRouteByModulation,
} ProtoPirateProtocolCatalogRoutePolicy;
typedef struct {
const char* canonical_name;
ProtoPirateProtocolCatalogRoutePolicy route_policy;
const char* tx_key;
} ProtoPirateProtocolCatalogEntry;
const ProtoPirateProtocolCatalogEntry*
protopirate_protocol_catalog_find(const char* protocol_name);
const char* protopirate_protocol_catalog_canonical_name(const char* protocol_name);
bool protopirate_protocol_catalog_can_tx(const char* protocol_name);
const char* protopirate_protocol_catalog_tx_key(const char* protocol_name);
const char*
protopirate_get_protocol_registry_filter_name(ProtoPirateProtocolRegistryFilter filter);
protopirate_protocol_catalog_display_name(const char* protocol_name, uint32_t protocol_type);
#ifdef ENABLE_TIMING_TUNER_SCENE
// Timing information for protocol analysis
typedef struct {
const char* name;
uint32_t te_short;
uint32_t te_long;
uint32_t te_delta;
uint32_t min_count_bit;
} ProtoPirateProtocolTiming;
ProtoPirateProtocolRegistryRoute protopirate_protocol_catalog_get_route(
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name);
// Get timing info for a protocol by name (returns NULL if not found)
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name);
ProtoPirateProtocolRegistryRoute protopirate_get_protocol_registry_route(
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name);
// Get timing info by index (for iteration)
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index);
// Get number of protocols with timing info
size_t protopirate_get_protocol_timing_count(void);
#endif
const char*
protopirate_get_protocol_registry_route_name(ProtoPirateProtocolRegistryRoute route);
@@ -0,0 +1,232 @@
#include "protocol_timings.h"
#include <furi.h>
#include <string.h>
static const ProtoPirateProtocolTiming protocol_timings[] = {
{
.name = "Honda Static",
.te_short = 63,
.te_long = 700,
.te_delta = 120,
.min_count_bit = 64,
},
{
.name = "Honda V1",
.te_short = 1000,
.te_long = 2000,
.te_delta = 400,
.min_count_bit = 64,
},
{
.name = "Kia V0",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 61,
},
{
.name = "Kia V1",
.te_short = 800,
.te_long = 1600,
.te_delta = 200,
.min_count_bit = 56,
},
{
.name = "Kia V2",
.te_short = 500,
.te_long = 1000,
.te_delta = 150,
.min_count_bit = 51,
},
{
.name = "Kia V3/V4",
.te_short = 400,
.te_long = 800,
.te_delta = 150,
.min_count_bit = 64,
},
{
.name = "Kia V5",
.te_short = 400,
.te_long = 800,
.te_delta = 150,
.min_count_bit = 64,
},
{
.name = "Kia V6",
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit = 144,
},
{
.name = "Kia V7",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 64,
},
{
.name = "Ford V0",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 64,
},
{
.name = "Chrysler V0",
.te_short = 300,
.te_long = 3700,
.te_delta = 400,
.min_count_bit = 80,
},
{
.name = "Ford V1",
.te_short = 65,
.te_long = 130,
.te_delta = 39,
.min_count_bit = 136,
},
{
.name = "Ford V2",
.te_short = 200,
.te_long = 400,
.te_delta = 260,
.min_count_bit = 104,
},
{
.name = "Ford V3",
.te_short = 240,
.te_long = 480,
.te_delta = 60,
.min_count_bit = 104,
},
{
.name = "Fiat V0",
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit = 64,
},
{
.name = "Fiat V1",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 102,
},
{
.name = "Fiat V2",
.te_short = 210,
.te_long = 420,
.te_delta = 100,
.min_count_bit = 112,
},
{
.name = "Renault V0",
.te_short = 125,
.te_long = 250,
.te_delta = 60,
.min_count_bit = 82,
},
{
.name = "Mazda V0",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 64,
},
{
.name = "Honda V2",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 81,
},
{
.name = "Porsche Touareg",
.te_short = 1680,
.te_long = 3370,
.te_delta = 500,
.min_count_bit = 64,
},
{
.name = "Subaru",
.te_short = 800,
.te_long = 1600,
.te_delta = 200,
.min_count_bit = 64,
},
{
.name = "VW",
.te_short = 500,
.te_long = 1000,
.te_delta = 120,
.min_count_bit = 80,
},
{
.name = "Scher-Khan",
.te_short = 750,
.te_long = 1100,
.te_delta = 180,
.min_count_bit = 35,
},
{
.name = "Star Line",
.te_short = 250,
.te_long = 500,
.te_delta = 120,
.min_count_bit = 64,
},
{
.name = "PSA",
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 128,
},
};
static const size_t protocol_timings_count = COUNT_OF(protocol_timings);
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name) {
if(!protocol_name) return NULL;
for(size_t i = 0; i < protocol_timings_count; i++) {
if(strcmp(protocol_name, protocol_timings[i].name) == 0 ||
strstr(protocol_name, protocol_timings[i].name) != NULL) {
return &protocol_timings[i];
}
}
static const struct {
const char* alias;
const char* canonical;
} aliases[] = {
{"Honda V0", "Kia V0"},
{"Land Rover V0", "Honda V2"},
{"Suzuki", "Kia V0"},
{"V3", "Kia V3/V4"},
{"V4", "Kia V3/V4"},
};
for(size_t a = 0; a < COUNT_OF(aliases); a++) {
if(strstr(protocol_name, aliases[a].alias) == NULL) continue;
for(size_t i = 0; i < protocol_timings_count; i++) {
if(strstr(protocol_timings[i].name, aliases[a].canonical) != NULL) {
return &protocol_timings[i];
}
}
}
return NULL;
}
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index) {
if(index >= protocol_timings_count) return NULL;
return &protocol_timings[index];
}
size_t protopirate_get_protocol_timing_count(void) {
return protocol_timings_count;
}
@@ -0,0 +1,16 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef struct {
const char* name;
uint32_t te_short;
uint32_t te_long;
uint32_t te_delta;
uint32_t min_count_bit;
} ProtoPirateProtocolTiming;
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name);
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index);
size_t protopirate_get_protocol_timing_count(void);
@@ -174,7 +174,17 @@ uint32_t pp_encoder_read_repeat(FlipperFormat* ff, uint32_t default_repeat) {
if(!ff) return default_repeat;
flipper_format_rewind(ff);
uint32_t tmp = 0;
return flipper_format_read_uint32(ff, FF_REPEAT, &tmp, 1) ? tmp : default_repeat;
if(!flipper_format_read_uint32(ff, FF_REPEAT, &tmp, 1)) {
return default_repeat;
}
if(tmp == 0) {
return default_repeat;
}
if(tmp > PP_ENCODER_REPEAT_MAX) {
tmp = PP_ENCODER_REPEAT_MAX;
}
return tmp;
}
SubGhzProtocolStatus pp_serialize_fields(
@@ -247,6 +257,8 @@ size_t
return i;
}
#if PROTOPIRATE_WITH_ENCODER
void pp_encoder_free(void* context) {
furi_check(context);
ProtoPirateEncoderHeader* hdr = context;
@@ -304,6 +316,8 @@ void pp_encoder_buffer_ensure(void* context, size_t capacity) {
hdr->encoder.size_upload = capacity;
}
#endif
uint8_t pp_decoder_hash_blocks(void* context) {
furi_check(context);
ProtoPirateDecoderHeader* hdr = context;
@@ -4,12 +4,13 @@
#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 <lib/subghz/protocols/base.h>
#include <lib/toolbox/manchester_decoder.h>
#include "../defines.h"
extern const char FF_BIT[];
extern const char FF_KEY[];
extern const char FF_SERIAL[];
@@ -52,6 +53,8 @@ void pp_encoder_read_fields(
uint32_t* cnt_out,
uint32_t* type_out);
#define PP_ENCODER_REPEAT_MAX 50U
uint32_t pp_encoder_read_repeat(FlipperFormat* ff, uint32_t default_repeat);
SubGhzProtocolStatus pp_serialize_fields(
@@ -118,6 +121,12 @@ uint8_t pp_decoder_hash_blocks(void* context);
void pp_decoder_free_default(void* context);
#define PP_SHARED_UPLOAD_CAPACITY 2048U
#if PROTOPIRATE_WITH_ENCODER
#include <lib/subghz/blocks/encoder.h>
typedef struct {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -127,10 +136,10 @@ void pp_encoder_free(void* context);
void pp_encoder_stop(void* context);
LevelDuration pp_encoder_yield(void* context);
#define PP_SHARED_UPLOAD_CAPACITY 2048U
void pp_encoder_buffer_ensure(void* context, size_t capacity);
LevelDuration* pp_shared_upload_buffer(void);
size_t pp_shared_upload_capacity(void);
void pp_shared_upload_release(void);
#endif
@@ -5,10 +5,18 @@
#include "protocol_items.h"
#define PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID "protopirate_protocol_plugins"
#define PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION 1U
#define PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION 2U
typedef enum {
ProtoPirateProtocolPluginKindRx = 0,
ProtoPirateProtocolPluginKindTx,
} ProtoPirateProtocolPluginKind;
typedef struct {
const char* plugin_name;
ProtoPirateProtocolRegistryFilter filter;
ProtoPirateProtocolPluginKind kind;
ProtoPirateProtocolRegistryRoute route;
const char* protocol_name;
const SubGhzProtocolRegistry* registry;
void (*release)(void);
} ProtoPirateProtocolPlugin;
@@ -72,7 +72,7 @@ struct SubGhzProtocolDecoderPSA {
uint8_t decrypted_type;
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
struct SubGhzProtocolEncoderPSA {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -102,7 +102,7 @@ const SubGhzProtocolDecoder subghz_protocol_psa_decoder = {
.deserialize = subghz_protocol_decoder_psa_deserialize,
.get_string = subghz_protocol_decoder_psa_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_psa_encoder = {
.alloc = subghz_protocol_encoder_psa_alloc,
.free = subghz_protocol_encoder_psa_free,
@@ -125,13 +125,21 @@ const SubGhzProtocol psa_protocol = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Load,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_psa_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_psa_encoder,
#else
.encoder = NULL,
#endif
};
static void psa_calculate_checksum(uint8_t* buffer);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void psa_second_stage_xor_encrypt(uint8_t* buffer) {
uint8_t E6 = buffer[8];
uint8_t E7 = buffer[9];
@@ -19,7 +19,7 @@ const uint32_t psa_crypto_bf2_key_schedule[4] = {
0x02192A04U,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void psa_crypto_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key) {
uint32_t sum = 0;
for(int i = 0; i < TEA_ROUNDS; i++) {
@@ -1,6 +1,7 @@
#pragma once
#include "psa_bf_types.h"
#include "../defines.h"
#include <stdint.h>
#define PSA_CRYPTO_BF1_CONST_U4 0x0E0F5C41U
@@ -24,6 +25,6 @@ uint8_t psa_crypto_tea_crc(uint32_t v0, uint32_t v1);
uint16_t psa_crypto_crc16_bf2(uint8_t* buffer, int length);
void psa_crypto_unpack_tea_result_to_buffer(uint8_t* buffer, uint32_t v0, uint32_t v1);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void psa_crypto_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
#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 "../defines.h"
#define RENAULT_PROTOCOL_V0_NAME "Renault V0"
extern const SubGhzProtocol renault_v0_protocol;
bool renault_v0_flipper_is_rolling(FlipperFormat* flipper_format);
void* subghz_protocol_decoder_renault_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_renault_v0_reset(void* context);
void subghz_protocol_decoder_renault_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_renault_v0_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_renault_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_renault_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_renault_v0_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_renault_v0_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_renault_v0_deserialize(void* context, FlipperFormat* flipper_format);
@@ -61,13 +61,28 @@ const SubGhzProtocol subghz_protocol_scher_khan = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_scher_khan_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_scher_khan_encoder,
#else
.encoder = NULL,
#endif
};
void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderScherKhan* instance = malloc(sizeof(SubGhzProtocolDecoderScherKhan));
SubGhzProtocolDecoderScherKhan* instance = calloc(1, sizeof(SubGhzProtocolDecoderScherKhan));
if(!instance) {
return NULL;
}
instance->base.protocol = &subghz_protocol_scher_khan;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -314,7 +329,17 @@ SubGhzProtocolStatus
subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
return subghz_block_generic_deserialize(&instance->generic, flipper_format);
SubGhzProtocolStatus status =
subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(status != SubGhzProtocolStatusOk) {
return status;
}
if(instance->generic.data_count_bit <
subghz_protocol_scher_khan_const.min_count_bit_for_found ||
instance->generic.data_count_bit > 64U) {
return SubGhzProtocolStatusErrorValueBitCount;
}
return status;
}
void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output) {
@@ -66,7 +66,7 @@ const SubGhzProtocolDecoder subghz_protocol_star_line_decoder = {
.get_string = subghz_protocol_decoder_star_line_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_star_line_encoder = {
.alloc = subghz_protocol_encoder_star_line_alloc,
.free = subghz_protocol_encoder_star_line_free,
@@ -91,8 +91,20 @@ const SubGhzProtocol subghz_protocol_star_line = {
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_star_line_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_star_line_encoder,
#else
.encoder = NULL,
#endif
};
/**
@@ -105,10 +117,10 @@ static void subghz_protocol_star_line_check_remote_controller(
SubGhzBlockGeneric* instance,
SubGhzKeystore* keystore,
const char** manufacture_name);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderStarLine* instance = malloc(sizeof(SubGhzProtocolEncoderStarLine));
SubGhzProtocolEncoderStarLine* instance = calloc(1, sizeof(SubGhzProtocolEncoderStarLine));
furi_check(instance);
instance->base.protocol = &subghz_protocol_star_line;
@@ -116,6 +128,7 @@ void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment) {
instance->keystore = subghz_environment_get_keystore(environment);
instance->manufacture_from_file = furi_string_alloc();
furi_check(instance->manufacture_from_file);
instance->encoder.repeat = 40;
pp_encoder_buffer_ensure(instance, STAR_LINE_UPLOAD_CAPACITY);
@@ -125,7 +138,7 @@ void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void subghz_protocol_encoder_star_line_free(void* context) {
furi_check(context);
@@ -241,7 +254,7 @@ bool subghz_protocol_star_line_create_data(
* @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance
* @return true On success
*/
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static bool subghz_protocol_encoder_star_line_get_upload(
SubGhzProtocolEncoderStarLine* instance,
uint8_t btn) {
@@ -278,7 +291,7 @@ static bool subghz_protocol_encoder_star_line_get_upload(
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static SubGhzProtocolStatus subghz_protocol_encoder_star_line_serialize(
SubGhzProtocolEncoderStarLine* instance,
@@ -333,7 +346,7 @@ static SubGhzProtocolStatus subghz_protocol_encoder_star_line_serialize(
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -69,7 +69,7 @@ const SubGhzProtocolDecoder subghz_protocol_subaru_decoder = {
.get_string = subghz_protocol_decoder_subaru_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_subaru_encoder = {
.alloc = subghz_protocol_encoder_subaru_alloc,
.free = pp_encoder_free,
@@ -93,8 +93,16 @@ const SubGhzProtocol subaru_protocol = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_subaru_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_subaru_encoder,
#else
.encoder = NULL,
#endif
};
// ============================================================================
@@ -195,8 +203,11 @@ static uint8_t subghz_protocol_decoder_subaru_get_hash_data(void* context) {
SubGhzProtocolDecoderSubaru* instance = context;
const uint8_t* p = (const uint8_t*)&instance->decoder.decode_data;
uint8_t hash = 0;
const uint8_t bytes = (uint8_t)(instance->decoder.decode_count_bit >> 3);
for(uint8_t i = 0; i <= bytes; i++) {
size_t bytes = (size_t)(instance->decoder.decode_count_bit >> 3) + 1U;
if(bytes > sizeof(instance->decoder.decode_data)) {
bytes = sizeof(instance->decoder.decode_data);
}
for(size_t i = 0; i < bytes; i++) {
hash ^= p[i];
}
return hash;
@@ -205,7 +216,7 @@ static uint8_t subghz_protocol_decoder_subaru_get_hash_data(void* context) {
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void subaru_encode_count(uint8_t* KB, uint16_t count) {
uint8_t lo = count & 0xFF;
@@ -272,7 +283,10 @@ static void subaru_encode_count(uint8_t* KB, uint16_t count) {
void* subghz_protocol_encoder_subaru_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderSubaru* instance = malloc(sizeof(SubGhzProtocolEncoderSubaru));
SubGhzProtocolEncoderSubaru* instance = calloc(1, sizeof(SubGhzProtocolEncoderSubaru));
if(!instance) {
return NULL;
}
instance->base.protocol = &subaru_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -291,7 +305,7 @@ void* subghz_protocol_encoder_subaru_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void subghz_protocol_encoder_subaru_get_upload(SubGhzProtocolEncoderSubaru* instance) {
furi_check(instance);
@@ -342,7 +356,7 @@ static void subghz_protocol_encoder_subaru_get_upload(SubGhzProtocolEncoderSubar
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
subghz_protocol_encoder_subaru_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -423,7 +437,10 @@ SubGhzProtocolStatus
void* subghz_protocol_decoder_subaru_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderSubaru* instance = malloc(sizeof(SubGhzProtocolDecoderSubaru));
SubGhzProtocolDecoderSubaru* instance = calloc(1, sizeof(SubGhzProtocolDecoderSubaru));
if(!instance) {
return NULL;
}
instance->base.protocol = &subaru_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
+20 -19
View File
@@ -13,13 +13,6 @@ _Static_assert(
VAG_ENCODER_UPLOAD_MAX_SIZE <= PP_SHARED_UPLOAD_CAPACITY,
"VAG_ENCODER_UPLOAD_MAX_SIZE exceeds shared upload slab");
static inline size_t
vag_emit_manchester_inv(LevelDuration* up, size_t i, size_t cap, bool bit_value, uint32_t te) {
i = pp_emit(up, i, cap, bit_value, te);
i = pp_emit(up, i, cap, !bit_value, te);
return i;
}
static const SubGhzBlockConst subghz_protocol_vag_const = {
.te_short = 500,
.te_long = 1000,
@@ -178,7 +171,7 @@ static void vag_tea_decrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key_sche
*v0 -= (((*v1 << 4) ^ (*v1 >> 5)) + *v1) ^ (sum + key_schedule[sum & 3]);
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void vag_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key_schedule) {
uint32_t sum = 0;
for(int i = 0; i < VAG_TEA_ROUNDS; i++) {
@@ -511,7 +504,7 @@ const SubGhzProtocolDecoder subghz_protocol_vag_decoder = {
.deserialize = subghz_protocol_decoder_vag_deserialize,
.get_string = subghz_protocol_decoder_vag_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_vag_encoder = {
.alloc = subghz_protocol_encoder_vag_alloc,
.free = subghz_protocol_encoder_vag_free,
@@ -534,8 +527,16 @@ const SubGhzProtocol vag_protocol = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_vag_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_vag_encoder,
#else
.encoder = NULL,
#endif
};
void* subghz_protocol_decoder_vag_alloc(SubGhzEnvironment* environment) {
@@ -957,7 +958,7 @@ SubGhzProtocolStatus
return ret;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderVAG {
SubGhzProtocolEncoderBase base;
@@ -1132,7 +1133,7 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) {
#endif
for(int i = 15; i >= 0; i--) {
bool bit = (prefix >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 300);
index = pp_emit_manchester_bit(upload, index, cap, bit, 300);
}
FURI_LOG_D(TAG, "Prefix 0x%04X: %zu pulses", prefix, index - prefix_start);
@@ -1151,7 +1152,7 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) {
#endif
for(int i = 63; i >= 0; i--) {
bool bit = (key1_inv >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 300);
index = pp_emit_manchester_bit(upload, index, cap, bit, 300);
}
FURI_LOG_D(TAG, "Key1: %zu pulses (64 bits)", index - key1_start);
@@ -1163,7 +1164,7 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) {
#endif
for(int i = 15; i >= 0; i--) {
bool bit = (key2_inv >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 300);
index = pp_emit_manchester_bit(upload, index, cap, bit, 300);
}
FURI_LOG_D(TAG, "Key2: %zu pulses (16 bits)", index - key2_start);
@@ -1272,7 +1273,7 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) {
#endif
for(int i = 15; i >= 0; i--) {
bool bit = (prefix >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 300);
index = pp_emit_manchester_bit(upload, index, cap, bit, 300);
}
FURI_LOG_D(TAG, "Prefix 0x%04X: %zu pulses", prefix, index - prefix_start);
@@ -1290,7 +1291,7 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) {
#endif
for(int i = 63; i >= 0; i--) {
bool bit = (key1_inv >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 300);
index = pp_emit_manchester_bit(upload, index, cap, bit, 300);
}
FURI_LOG_D(TAG, "Key1: %zu pulses", index - key1_start);
@@ -1302,7 +1303,7 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) {
#endif
for(int i = 15; i >= 0; i--) {
bool bit = (key2_inv >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 300);
index = pp_emit_manchester_bit(upload, index, cap, bit, 300);
}
FURI_LOG_D(TAG, "Key2: %zu pulses", index - key2_start);
@@ -1438,7 +1439,7 @@ static void vag_encoder_build_type3_4(SubGhzProtocolEncoderVAG* instance) {
for(int i = 63; i >= 0; i--) {
bool bit = (key1 >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 500);
index = pp_emit_manchester_bit(upload, index, cap, bit, 500);
}
FURI_LOG_D(TAG, "Repeat %d: Key1 %zu pulses (64 bits)", repeat + 1, index - key1_start);
@@ -1447,7 +1448,7 @@ static void vag_encoder_build_type3_4(SubGhzProtocolEncoderVAG* instance) {
#endif
for(int i = 15; i >= 0; i--) {
bool bit = (key2 >> i) & 1;
index = vag_emit_manchester_inv(upload, index, cap, bit, 500);
index = pp_emit_manchester_bit(upload, index, cap, bit, 500);
}
FURI_LOG_D(TAG, "Repeat %d: Key2 %zu pulses (16 bits)", repeat + 1, index - key2_start);
@@ -1528,7 +1529,7 @@ void subghz_protocol_decoder_vag_get_string(void* context, FuriString* output) {
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_vag_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
FURI_LOG_I(TAG, "VAG encoder alloc");
+12 -369
View File
@@ -3,12 +3,11 @@
#include <furi.h>
#include <furi_hal.h>
#include "protocols/protocol_items.h"
#include "protocols/protocols_common.h"
#include "helpers/protopirate_settings.h"
#include "helpers/protopirate_storage.h"
#include "helpers/protopirate_psa_bf_host.h"
#include "protocols/keys.h"
#include "helpers/protopirate_views.h"
#include "helpers/protopirate_radio.h"
#include <string.h>
#define TAG "ProtoPirateApp"
@@ -31,132 +30,6 @@ static void protopirate_app_tick_event_callback(void* context) {
scene_manager_handle_tick_event(app->scene_manager);
}
bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) {
furi_check(app);
if(app->variable_item_list) {
return true;
}
app->variable_item_list = variable_item_list_alloc();
if(!app->variable_item_list) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewVariableItemList,
variable_item_list_get_view(app->variable_item_list));
return true;
}
bool protopirate_ensure_widget(ProtoPirateApp* app) {
furi_check(app);
if(app->widget) {
return true;
}
app->widget = widget_alloc();
if(!app->widget) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewWidget, widget_get_view(app->widget));
return true;
}
bool protopirate_ensure_text_input(ProtoPirateApp* app) {
furi_check(app);
if(app->text_input) {
return true;
}
app->text_input = text_input_alloc();
if(!app->text_input) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewTextInput, text_input_get_view(app->text_input));
return true;
}
bool protopirate_ensure_view_about(ProtoPirateApp* app) {
furi_check(app);
if(app->view_about) {
return true;
}
app->view_about = view_alloc();
if(!app->view_about) {
return false;
}
view_dispatcher_add_view(app->view_dispatcher, ProtoPirateViewAbout, app->view_about);
return true;
}
bool protopirate_ensure_receiver_view(ProtoPirateApp* app) {
furi_check(app);
if(app->protopirate_receiver) {
return true;
}
app->protopirate_receiver = protopirate_view_receiver_alloc(app->auto_save);
if(!app->protopirate_receiver) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewReceiver,
protopirate_view_receiver_get_view(app->protopirate_receiver));
return true;
}
static void protopirate_radio_init_cleanup(ProtoPirateApp* app, bool devices_initialized) {
furi_check(app);
furi_check(app->txrx);
if(app->txrx->receiver) {
subghz_receiver_free(app->txrx->receiver);
app->txrx->receiver = NULL;
}
if(app->txrx->radio_device) {
if(devices_initialized) {
subghz_devices_idle(app->txrx->radio_device);
}
radio_device_loader_end(app->txrx->radio_device);
app->txrx->radio_device = NULL;
}
if(app->txrx->environment) {
subghz_environment_free(app->txrx->environment);
app->txrx->environment = NULL;
}
if(app->txrx->protocol_plugin_manager) {
plugin_manager_free(app->txrx->protocol_plugin_manager);
app->txrx->protocol_plugin_manager = NULL;
}
if(app->txrx->plugin_resolver) {
composite_api_resolver_free(app->txrx->plugin_resolver);
app->txrx->plugin_resolver = NULL;
}
if(devices_initialized) {
subghz_devices_deinit();
}
app->txrx->protocol_registry = NULL;
app->txrx->protocol_plugin = NULL;
app->txrx->protocol_registry_filter = ProtoPirateProtocolRegistryFilterAM;
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->radio_initialized = false;
}
ProtoPirateApp* protopirate_app_alloc() {
protopirate_storage_purge_temp_history_at_startup();
ProtoPirateApp* app = malloc(sizeof(ProtoPirateApp));
@@ -215,6 +88,7 @@ ProtoPirateApp* protopirate_app_alloc() {
// Apply auto-save setting
app->auto_save = settings.auto_save;
app->check_saved = settings.check_saved;
app->tx_power = settings.tx_power;
app->emulate_feature_enabled = settings.emulate_feature_enabled;
@@ -259,7 +133,7 @@ ProtoPirateApp* protopirate_app_alloc() {
furi_check(app->txrx->preset->name);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->rx_key_state = ProtoPirateRxKeyStateIDLE;
app->txrx->protocol_registry_filter = ProtoPirateProtocolRegistryFilterAM;
app->txrx->protocol_registry_route = ProtoPirateProtocolRegistryRouteAMDefault;
// Get preset name and data
const char* preset_name = subghz_setting_get_preset_name(app->setting, preset_index);
@@ -288,190 +162,6 @@ ProtoPirateApp* protopirate_app_alloc() {
return app;
}
bool protopirate_radio_init(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
FURI_LOG_I(TAG, "=== protopirate_radio_init called ===");
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
if(app->radio_initialized) {
const bool radio_ready = (app->txrx->environment != NULL) &&
(app->txrx->radio_device != NULL);
if(radio_ready) {
FURI_LOG_D(TAG, "Radio already initialized, returning true");
return true;
}
FURI_LOG_W(
TAG,
"Radio marked initialized but resources missing (env=%p device=%p), repairing",
app->txrx->environment,
app->txrx->radio_device);
protopirate_radio_deinit(app);
}
// Fresh radio init - nothing was initialized before
FURI_LOG_I(TAG, "Fresh radio init - allocating all components");
// Create environment with our custom protocols
app->txrx->environment = subghz_environment_alloc();
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Failed to allocate environment!");
protopirate_radio_init_cleanup(app, false);
return false;
}
app->txrx->protocol_registry = NULL;
if(!protopirate_refresh_protocol_registry(app, false)) {
FURI_LOG_E(TAG, "Failed to configure protocol registry");
protopirate_radio_init_cleanup(app, false);
return false;
}
// Load keystores
subghz_environment_load_keystore(app->txrx->environment, PROTOPIRATE_KEYSTORE_DIR_NAME);
// Load ProtoPirate specific keys
protopirate_keys_load(app->txrx->environment);
FURI_LOG_I(TAG, "Loaded ProtoPirate secure keys");
// Initialize SubGhz devices
subghz_devices_init();
FURI_LOG_D(TAG, "SubGhz devices initialized");
// Try external CC1101 first
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101);
// if not loading, fallback to internal
if(!app->txrx->radio_device) {
FURI_LOG_W(TAG, "External CC1101 not found, trying internal radio");
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeInternal);
}
if(!app->txrx->radio_device) {
FURI_LOG_E(TAG, "Failed to initialize any radio device!");
protopirate_radio_init_cleanup(app, true);
return false;
}
#ifndef REMOVE_LOGS
const char* device_name = subghz_devices_get_name(app->txrx->radio_device);
bool is_external = device_name && strstr(device_name, "ext");
FURI_LOG_I(
TAG,
"Radio device initialized: %s (%s)",
device_name ? device_name : "unknown",
is_external ? "external" : "internal");
#endif
subghz_devices_reset(app->txrx->radio_device);
subghz_devices_idle(app->txrx->radio_device);
app->radio_initialized = true;
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
return true;
}
// Deinitialize radio subsystem
void protopirate_radio_deinit(ProtoPirateApp* app) {
FURI_LOG_I(TAG, "=== protopirate_radio_deinit called ===");
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
FURI_LOG_D(
TAG,
"Pointers: worker=%p, environment=%p, receiver=%p, history=%p, radio_device=%p",
app->txrx->worker,
app->txrx->environment,
app->txrx->receiver,
app->txrx->history,
app->txrx->radio_device);
bool has_radio_resources = app->radio_initialized || app->txrx->worker ||
app->txrx->environment || app->txrx->receiver ||
app->txrx->history || app->txrx->radio_device;
if(!has_radio_resources) {
FURI_LOG_D(TAG, "Radio resources were not initialized, returning");
return;
}
bool devices_initialized = app->radio_initialized || (app->txrx->radio_device != NULL);
// Make sure we're not receiving
if(app->txrx->worker && app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
FURI_LOG_D(TAG, "Stopping active RX, state=%d", app->txrx->txrx_state);
subghz_worker_stop(app->txrx->worker);
if(app->txrx->radio_device) {
subghz_devices_stop_async_rx(app->txrx->radio_device);
}
}
if(app->txrx->radio_device) {
FURI_LOG_D(TAG, "Putting radio device to sleep and ending: %p", app->txrx->radio_device);
subghz_devices_sleep(app->txrx->radio_device);
radio_device_loader_end(app->txrx->radio_device);
app->txrx->radio_device = NULL;
} else {
FURI_LOG_D(TAG, "Radio device was NULL, skipping sleep/end");
}
if(devices_initialized) {
FURI_LOG_D(TAG, "Calling subghz_devices_deinit");
subghz_devices_deinit();
}
if(app->txrx->receiver) {
FURI_LOG_D(TAG, "Freeing receiver %p", app->txrx->receiver);
subghz_receiver_free(app->txrx->receiver);
app->txrx->receiver = NULL;
} else {
FURI_LOG_D(TAG, "Receiver was NULL, skipping free");
}
if(app->txrx->environment) {
FURI_LOG_D(TAG, "Freeing environment %p", app->txrx->environment);
subghz_environment_free(app->txrx->environment);
app->txrx->environment = NULL;
app->txrx->protocol_registry = NULL;
} else {
FURI_LOG_D(TAG, "Environment was NULL, skipping free");
}
if(app->txrx->protocol_plugin_manager) {
FURI_LOG_D(TAG, "Freeing protocol plugin manager %p", app->txrx->protocol_plugin_manager);
plugin_manager_free(app->txrx->protocol_plugin_manager);
app->txrx->protocol_plugin_manager = NULL;
}
if(app->txrx->plugin_resolver) {
FURI_LOG_D(TAG, "Freeing plugin resolver %p", app->txrx->plugin_resolver);
composite_api_resolver_free(app->txrx->plugin_resolver);
app->txrx->plugin_resolver = NULL;
}
app->txrx->protocol_plugin = NULL;
if(app->txrx->history) {
FURI_LOG_D(TAG, "Freeing history %p", app->txrx->history);
protopirate_history_free(app->txrx->history);
app->txrx->history = NULL;
} else {
FURI_LOG_D(TAG, "History was NULL, skipping free");
}
if(app->txrx->worker) {
FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker);
subghz_worker_free(app->txrx->worker);
app->txrx->worker = NULL;
} else {
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
}
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->radio_initialized = false;
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
}
void protopirate_app_free(ProtoPirateApp* app) {
furi_check(app);
@@ -482,6 +172,7 @@ void protopirate_app_free(ProtoPirateApp* app) {
ProtoPirateSettings settings;
settings.frequency = app->txrx->preset->frequency;
settings.auto_save = app->auto_save;
settings.check_saved = app->check_saved;
settings.tx_power = app->tx_power;
settings.hopping_enabled = (app->txrx->hopper_state != ProtoPirateHopperStateOFF);
settings.emulate_feature_enabled = app->emulate_feature_enabled;
@@ -507,7 +198,11 @@ void protopirate_app_free(ProtoPirateApp* app) {
protopirate_settings_save(&settings);
// Deinitialize whichever is active - NULL checks inside handle all cases
protopirate_tool_scene_plugin_release(app);
#ifdef ENABLE_EMULATE_FEATURE
protopirate_emulate_context_release(app);
#endif
FURI_LOG_D(TAG, "Calling radio_deinit");
protopirate_radio_deinit(app);
@@ -517,93 +212,42 @@ void protopirate_app_free(ProtoPirateApp* app) {
app->loaded_file_path = NULL;
}
// Submenu
if(app->submenu) {
FURI_LOG_D(TAG, "Removing submenu view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewSubmenu);
submenu_free(app->submenu);
}
protopirate_views_free(app);
// Variable Item List
if(app->variable_item_list) {
FURI_LOG_D(TAG, "Removing variable_item_list view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList);
variable_item_list_free(app->variable_item_list);
}
// About View
if(app->view_about) {
FURI_LOG_D(TAG, "Removing about view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewAbout);
view_free(app->view_about);
}
// File path
if(app->file_path) {
FURI_LOG_D(TAG, "Freeing file_path");
furi_string_free(app->file_path);
app->file_path = NULL;
}
// Widget
if(app->widget) {
FURI_LOG_D(TAG, "Removing widget view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewWidget);
widget_free(app->widget);
}
// Text Input
if(app->text_input) {
FURI_LOG_D(TAG, "Removing text_input view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewTextInput);
text_input_free(app->text_input);
}
if(app->save_protocol) {
furi_string_free(app->save_protocol);
app->save_protocol = NULL;
}
// Receiver
if(app->protopirate_receiver) {
FURI_LOG_D(TAG, "Removing receiver view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver);
protopirate_view_receiver_free(app->protopirate_receiver);
}
protopirate_psa_bf_context_release(app);
// Setting
FURI_LOG_D(TAG, "Freeing subghz_setting");
subghz_setting_free(app->setting);
// Free preset
FURI_LOG_D(TAG, "Freeing preset");
furi_string_free(app->txrx->preset->name);
free(app->txrx->preset);
free(app->txrx);
#ifdef ENABLE_EMULATE_FEATURE
protopirate_emulate_context_release(app);
#endif
pp_shared_upload_release();
// View dispatcher
FURI_LOG_D(TAG, "Freeing view_dispatcher and scene_manager");
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close Dialogs
FURI_LOG_D(TAG, "Closing dialogs record");
furi_record_close(RECORD_DIALOGS);
app->dialogs = NULL;
// Notifications
FURI_LOG_D(TAG, "Closing notifications record");
furi_record_close(RECORD_NOTIFICATION);
app->notifications = NULL;
// Close records
FURI_LOG_D(TAG, "Closing GUI record");
furi_record_close(RECORD_GUI);
@@ -616,7 +260,6 @@ int32_t protopirate_app(char* p) {
ProtoPirateApp* protopirate_app = protopirate_app_alloc();
if(!protopirate_app) {
// logging is already done in protopirate_app_alloc()
furi_hal_power_suppress_charge_exit();
return -1;
}
@@ -34,12 +34,17 @@
#include "scenes/plugins/protopirate_emulate_plugin.h"
#endif
#include "scenes/plugins/protopirate_psa_bf_plugin.h"
#include "scenes/plugins/protopirate_tool_scene_plugin.h"
#include "helpers/protopirate_views.h"
#include "helpers/protopirate_radio.h"
#include "helpers/protopirate_protocol_plugin_host.h"
#include "helpers/protopirate_txrx.h"
#define PROTOPIRATE_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted")
typedef struct ProtoPirateApp ProtoPirateApp;
typedef struct {
typedef struct ProtoPirateTxRx {
SubGhzWorker* worker;
SubGhzEnvironment* environment;
SubGhzReceiver* receiver;
@@ -48,7 +53,7 @@ typedef struct {
CompositeApiResolver* plugin_resolver;
PluginManager* protocol_plugin_manager;
const ProtoPirateProtocolPlugin* protocol_plugin;
ProtoPirateProtocolRegistryFilter protocol_registry_filter;
ProtoPirateProtocolRegistryRoute protocol_registry_route;
ProtoPirateHistory* history;
const SubGhzDevice* radio_device;
ProtoPirateTxRxState txrx_state;
@@ -77,8 +82,8 @@ struct ProtoPirateApp {
ProtoPirateLock lock;
FuriString* loaded_file_path;
bool auto_save;
bool check_saved;
bool radio_initialized;
ProtoPirateSettings settings;
uint32_t start_tx_time;
uint8_t tx_power;
char save_filename[64];
@@ -99,6 +104,17 @@ struct ProtoPirateApp {
CompositeApiResolver* psa_bf_plugin_resolver;
PluginManager* psa_bf_plugin_manager;
const ProtoPiratePsaBfPlugin* psa_bf_plugin;
CompositeApiResolver* tool_scene_plugin_resolver;
PluginManager* tool_scene_plugin_manager;
const ProtoPirateToolScenePlugin* tool_scene_plugin;
ProtoPirateToolScenePluginKind tool_scene_plugin_kind;
#define TOOL_SCENE_NAV_NONE 0U
#define TOOL_SCENE_NAV_POP 1U
#define TOOL_SCENE_NAV_NEXT 2U
#define TOOL_SCENE_NAV_SEARCH_PREVIOUS 3U
uint8_t tool_scene_nav_pending;
uint32_t tool_scene_nav_target;
};
#ifdef ENABLE_EMULATE_FEATURE
@@ -110,49 +126,10 @@ typedef enum {
ProtoPirateSetTypeMAX,
} ProtoPirateSetType;
void protopirate_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size);
void protopirate_get_frequency_modulation(
ProtoPirateApp* app,
FuriString* frequency,
FuriString* modulation);
void protopirate_get_frequency_modulation_str(
ProtoPirateApp* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size);
void protopirate_begin(ProtoPirateApp* app, uint8_t* preset_data);
uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency);
void protopirate_idle(ProtoPirateApp* app);
void protopirate_rx_end(ProtoPirateApp* app);
void protopirate_sleep(ProtoPirateApp* app);
void protopirate_hopper_update(ProtoPirateApp* app);
void protopirate_tx(ProtoPirateApp* app, uint32_t frequency);
void protopirate_tx_stop(ProtoPirateApp* app);
bool protopirate_radio_init(ProtoPirateApp* app);
void protopirate_radio_deinit(ProtoPirateApp* app);
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready);
bool protopirate_apply_protocol_registry_for_preset_data(
ProtoPirateApp* app,
const uint8_t* preset_data,
size_t preset_data_size);
bool protopirate_ensure_variable_item_list(ProtoPirateApp* app);
bool protopirate_ensure_widget(ProtoPirateApp* app);
bool protopirate_ensure_text_input(ProtoPirateApp* app);
bool protopirate_ensure_view_about(ProtoPirateApp* app);
bool protopirate_ensure_receiver_view(ProtoPirateApp* app);
void protopirate_release_shared_radio_state(ProtoPirateApp* app);
void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app);
void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app);
bool protopirate_tool_scene_on_enter(void* app, ProtoPirateToolScenePluginKind kind);
bool protopirate_tool_scene_on_event(void* app, SceneManagerEvent event);
void protopirate_tool_scene_on_exit(void* app);
void protopirate_tool_scene_plugin_release(ProtoPirateApp* app);
void protopirate_app_free(ProtoPirateApp* app);
@@ -12,12 +12,29 @@
#define HISTORY_SCRATCH_TEXT_RESERVE 256U
#define HISTORY_SCRATCH_PATH_RESERVE 128U
#define HISTORY_ARENA_RESERVE 1024U
#define HISTORY_DUPLICATE_WINDOW 500U
typedef enum {
ProtoPirateSavedMatchStateNone = 0,
ProtoPirateSavedMatchStatePending,
ProtoPirateSavedMatchStateDone,
} ProtoPirateSavedMatchState;
typedef enum {
ProtoPirateAutoSaveStateNone = 0,
ProtoPirateAutoSaveStatePending,
ProtoPirateAutoSaveStateDone,
} ProtoPirateAutoSaveState;
typedef struct {
uint32_t seq_id;
uint16_t text_offset;
uint16_t text_len;
uint8_t type;
uint8_t saved_match_state;
uint8_t auto_save_state;
FuriString* matched_saved_path;
FuriString* matched_name;
} ProtoPirateHistoryItem;
ARRAY_DEF(ProtoPirateHistoryItemArray, ProtoPirateHistoryItem, M_POD_OPLIST)
@@ -58,6 +75,26 @@ static void
protopirate_storage_delete_file(furi_string_get_cstr(instance->scratch_path));
}
static void protopirate_history_item_clear_matched(ProtoPirateHistoryItem* item) {
if(!item) return;
if(item->matched_saved_path) {
furi_string_free(item->matched_saved_path);
item->matched_saved_path = NULL;
}
if(item->matched_name) {
furi_string_free(item->matched_name);
item->matched_name = NULL;
}
}
static void protopirate_history_clear_all_matched(ProtoPirateHistory* instance) {
size_t n = ProtoPirateHistoryItemArray_size(instance->data);
for(size_t i = 0; i < n; i++) {
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i);
protopirate_history_item_clear_matched(item);
}
}
static void
protopirate_history_arena_remove(ProtoPirateHistory* instance, uint16_t offset, uint16_t len) {
if(len == 0) return;
@@ -116,6 +153,7 @@ ProtoPirateHistory* protopirate_history_alloc(void) {
void protopirate_history_free(ProtoPirateHistory* instance) {
furi_check(instance);
protopirate_history_release_scratch(instance);
protopirate_history_clear_all_matched(instance);
ProtoPirateHistoryItemArray_clear(instance->data);
protopirate_storage_wipe_history_cache();
@@ -142,6 +180,7 @@ void protopirate_history_free(ProtoPirateHistory* instance) {
void protopirate_history_reset(ProtoPirateHistory* instance) {
furi_check(instance);
protopirate_history_release_scratch(instance);
protopirate_history_clear_all_matched(instance);
ProtoPirateHistoryItemArray_reset(instance->data);
furi_string_reset(instance->text_arena);
instance->last_index = 0;
@@ -216,10 +255,11 @@ bool protopirate_history_capture_path_equals(
return strcmp(furi_string_get_cstr(instance->scratch_path), path) == 0;
}
bool protopirate_history_add_to_history(
bool protopirate_history_add_to_history_at(
ProtoPirateHistory* instance,
void* context,
SubGhzRadioPreset* preset) {
SubGhzRadioPreset* preset,
uint32_t update_timestamp) {
furi_check(instance);
furi_check(context);
@@ -229,10 +269,11 @@ bool protopirate_history_add_to_history(
SubGhzProtocolDecoderBase* decoder_base = context;
if((instance->code_last_hash_data ==
if((ProtoPirateHistoryItemArray_size(instance->data) > 0) &&
(instance->code_last_hash_data ==
subghz_protocol_decoder_base_get_hash_data(decoder_base)) &&
((furi_get_tick() - instance->last_update_timestamp) < 500)) {
instance->last_update_timestamp = furi_get_tick();
((update_timestamp - instance->last_update_timestamp) < HISTORY_DUPLICATE_WINDOW)) {
instance->last_update_timestamp = update_timestamp;
return false;
}
@@ -264,7 +305,7 @@ bool protopirate_history_add_to_history(
}
instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base);
instance->last_update_timestamp = furi_get_tick();
instance->last_update_timestamp = update_timestamp;
const char* text_cstr = furi_string_get_cstr(instance->scratch_text);
size_t text_len = furi_string_size(instance->scratch_text);
@@ -278,6 +319,10 @@ bool protopirate_history_add_to_history(
item->text_offset = (uint16_t)offset;
item->text_len = (uint16_t)text_len;
item->type = 0;
item->saved_match_state = ProtoPirateSavedMatchStateNone;
item->auto_save_state = ProtoPirateAutoSaveStateNone;
item->matched_saved_path = NULL;
item->matched_name = NULL;
instance->last_index++;
@@ -291,6 +336,13 @@ bool protopirate_history_add_to_history(
return true;
}
bool protopirate_history_add_to_history(
ProtoPirateHistory* instance,
void* context,
SubGhzRadioPreset* preset) {
return protopirate_history_add_to_history_at(instance, context, preset, furi_get_tick());
}
void protopirate_history_delete_item(ProtoPirateHistory* instance, uint16_t idx) {
furi_check(instance);
@@ -312,6 +364,7 @@ void protopirate_history_delete_item(ProtoPirateHistory* instance, uint16_t idx)
uint16_t text_offset = item->text_offset;
uint16_t text_len = item->text_len;
protopirate_history_item_clear_matched(item);
protopirate_history_delete_capture_file(instance, seq_id);
ProtoPirateHistoryItemArray_pop_at(NULL, instance->data, idx);
protopirate_history_arena_remove(instance, text_offset, text_len);
@@ -398,6 +451,135 @@ FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, ui
return instance->loaded_ff;
}
void protopirate_history_set_matched_saved(
ProtoPirateHistory* instance,
uint16_t idx,
const char* name,
const char* path) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
if(!item->matched_name) item->matched_name = furi_string_alloc();
if(!item->matched_saved_path) item->matched_saved_path = furi_string_alloc();
furi_string_set_str(item->matched_name, name ? name : "");
furi_string_set_str(item->matched_saved_path, path ? path : "");
item->saved_match_state = ProtoPirateSavedMatchStateDone;
}
const char* protopirate_history_get_matched_saved_path(
ProtoPirateHistory* instance,
uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return NULL;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
if(!item->matched_saved_path || furi_string_size(item->matched_saved_path) == 0) {
return NULL;
}
return furi_string_get_cstr(item->matched_saved_path);
}
const char* protopirate_history_get_matched_name(ProtoPirateHistory* instance, uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return NULL;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
if(!item->matched_name || furi_string_size(item->matched_name) == 0) {
return NULL;
}
return furi_string_get_cstr(item->matched_name);
}
bool protopirate_history_has_matched_saved(ProtoPirateHistory* instance, uint16_t idx) {
return protopirate_history_get_matched_saved_path(instance, idx) != NULL;
}
void protopirate_history_mark_auto_save_pending(ProtoPirateHistory* instance, uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
item->auto_save_state = ProtoPirateAutoSaveStatePending;
}
bool protopirate_history_find_pending_auto_save(ProtoPirateHistory* instance, uint16_t* idx) {
furi_check(instance);
furi_check(idx);
const size_t item_count = ProtoPirateHistoryItemArray_size(instance->data);
for(size_t i = 0; i < item_count; i++) {
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i);
if(item->auto_save_state == ProtoPirateAutoSaveStatePending) {
*idx = (uint16_t)i;
return true;
}
}
return false;
}
void protopirate_history_mark_auto_save_done(ProtoPirateHistory* instance, uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
item->auto_save_state = ProtoPirateAutoSaveStateDone;
}
void protopirate_history_mark_saved_match_pending(ProtoPirateHistory* instance, uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
item->saved_match_state = ProtoPirateSavedMatchStatePending;
}
bool protopirate_history_find_pending_saved_match(ProtoPirateHistory* instance, uint16_t* idx) {
furi_check(instance);
furi_check(idx);
const size_t item_count = ProtoPirateHistoryItemArray_size(instance->data);
for(size_t i = 0; i < item_count; i++) {
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i);
if(item->saved_match_state == ProtoPirateSavedMatchStatePending) {
*idx = (uint16_t)i;
return true;
}
}
return false;
}
void protopirate_history_mark_saved_match_done(ProtoPirateHistory* instance, uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
item->saved_match_state = ProtoPirateSavedMatchStateDone;
}
void protopirate_history_set_item_str(ProtoPirateHistory* instance, uint16_t idx, const char* str) {
furi_check(instance);
furi_check(str);
@@ -5,7 +5,7 @@
#include <lib/subghz/receiver.h>
#include <lib/subghz/protocols/base.h>
#define PROTOPIRATE_HISTORY_MAX 10
#define PROTOPIRATE_HISTORY_MAX 20
typedef struct SubGhzEnvironment SubGhzEnvironment;
typedef struct ProtoPirateHistory ProtoPirateHistory;
@@ -34,6 +34,11 @@ bool protopirate_history_add_to_history(
ProtoPirateHistory* instance,
void* context,
SubGhzRadioPreset* preset);
bool protopirate_history_add_to_history_at(
ProtoPirateHistory* instance,
void* context,
SubGhzRadioPreset* preset,
uint32_t update_timestamp);
void protopirate_history_delete_item(ProtoPirateHistory* instance, uint16_t idx);
void protopirate_history_get_text_item_menu(
ProtoPirateHistory* instance,
@@ -49,3 +54,22 @@ FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, ui
void protopirate_history_release_scratch(ProtoPirateHistory* instance);
void protopirate_history_set_item_str(ProtoPirateHistory* instance, uint16_t idx, const char* str);
void protopirate_history_set_matched_saved(
ProtoPirateHistory* instance,
uint16_t idx,
const char* name,
const char* path);
const char* protopirate_history_get_matched_saved_path(ProtoPirateHistory* instance, uint16_t idx);
const char* protopirate_history_get_matched_name(ProtoPirateHistory* instance, uint16_t idx);
bool protopirate_history_has_matched_saved(ProtoPirateHistory* instance, uint16_t idx);
void protopirate_history_mark_auto_save_pending(ProtoPirateHistory* instance, uint16_t idx);
bool protopirate_history_find_pending_auto_save(ProtoPirateHistory* instance, uint16_t* idx);
void protopirate_history_mark_auto_save_done(ProtoPirateHistory* instance, uint16_t idx);
void protopirate_history_mark_saved_match_pending(ProtoPirateHistory* instance, uint16_t idx);
bool protopirate_history_find_pending_saved_match(ProtoPirateHistory* instance, uint16_t* idx);
void protopirate_history_mark_saved_match_done(ProtoPirateHistory* instance, uint16_t idx);
@@ -8,6 +8,13 @@
#include "../../protocols/protocols_common.h"
#include "../../protocols/protocol_items.h"
#include "../../protocols/fiat_v1.h"
#include "../../protocols/ford_v1.h"
#include "../../protocols/kia_v0.h"
#include "../../protocols/kia_v3_v4.h"
#include "../../protocols/kia_v7.h"
#include "../../protocols/psa.h"
#include "../../protocols/renault_v0.h"
#include <input/input.h>
#include <gui/canvas.h>
@@ -27,6 +34,7 @@
#define MIN_TX_TIME 666U
#define MIN_TX_TIME_KIA_V3_V4 1600U
#define TX_PRESET_PATCH_MAX_SIZE 128U
#define EMU_PRESET_KEY_PROTOCOL "Protocol"
#define EMU_PRESET_KEY_FREQUENCY "Frequency"
@@ -35,7 +43,10 @@
#define EMU_PRESET_KEY_BTN "Btn"
#define EMU_PRESET_KEY_CNT "Cnt"
#define EMU_PRESET_KEY_TYPE "Type"
#define EMU_PRESET_KEY_HITAG2_KEY "Hitag2 Key"
#define EMU_PRESET_KEY_HITAG2_EPOCH "Hitag2 Epoch"
#define EMU_CUSTOM_PRESET_KEY "Custom_preset_data"
#define EMU_FIAT_V1_KEY_TEXT_LEN 12U
typedef struct {
uint32_t original_counter;
@@ -50,7 +61,9 @@ typedef struct {
SubGhzTransmitter* transmitter;
bool is_transmitting;
bool flag_stop_called;
bool replay_only;
Storage* storage;
char hitag2_key_text[EMU_FIAT_V1_KEY_TEXT_LEN + 1U];
} EmulateContext;
typedef struct {
@@ -62,6 +75,52 @@ typedef struct {
static EmulateContext* emulate_context = NULL;
static const ProtoPirateEmulateHostApi* g_host_api = NULL;
static bool emulate_hex_nibble(char c, uint8_t* nibble) {
if(c >= '0' && c <= '9') {
*nibble = (uint8_t)(c - '0');
return true;
}
if(c >= 'A' && c <= 'F') {
*nibble = (uint8_t)(c - 'A' + 10);
return true;
}
if(c >= 'a' && c <= 'f') {
*nibble = (uint8_t)(c - 'a' + 10);
return true;
}
return false;
}
static bool emulate_parse_hitag2_key_text(const char* text, uint8_t key[6]) {
if(!text || !key) return false;
uint8_t hex_count = 0U;
uint8_t high_nibble = 0U;
for(size_t i = 0U; text[i] != '\0'; i++) {
if(text[i] == ' ') continue;
uint8_t nibble = 0U;
if(!emulate_hex_nibble(text[i], &nibble) || hex_count >= EMU_FIAT_V1_KEY_TEXT_LEN) {
return false;
}
if((hex_count & 1U) == 0U) {
high_nibble = nibble;
} else {
key[hex_count >> 1U] = (uint8_t)((high_nibble << 4U) | nibble);
}
hex_count++;
}
return hex_count == EMU_FIAT_V1_KEY_TEXT_LEN;
}
static bool emulate_has_hitag2_key(FlipperFormat* flipper_format) {
if(!flipper_format) return false;
uint8_t key[6] = {0};
flipper_format_rewind(flipper_format);
return flipper_format_read_hex(flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, sizeof(key));
}
static void emulate_request_nav_pop(ProtoPirateApp* app) {
app->emulate_nav_pending = EMULATE_NAV_POP;
}
@@ -74,6 +133,68 @@ static void emulate_request_nav_after_exit(ProtoPirateApp* app) {
}
}
static bool emulate_prompt_fiat_v1_key(ProtoPirateApp* app, EmulateContext* ctx);
static void emulate_fiat_v1_key_input_callback(void* context) {
ProtoPirateApp* app = context;
EmulateContext* ctx = emulate_context;
uint8_t key[6] = {0};
if(!app || !ctx || !ctx->flipper_format ||
!emulate_parse_hitag2_key_text(ctx->hitag2_key_text, key)) {
if(app && app->notifications) {
notification_message(app->notifications, &sequence_error);
}
if(app && ctx) {
(void)emulate_prompt_fiat_v1_key(app, ctx);
}
return;
}
flipper_format_rewind(ctx->flipper_format);
if(!flipper_format_insert_or_update_hex(
ctx->flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, sizeof(key))) {
notification_message(app->notifications, &sequence_error);
emulate_request_nav_pop(app);
return;
}
uint32_t epoch = 0U;
flipper_format_rewind(ctx->flipper_format);
if(!flipper_format_read_uint32(ctx->flipper_format, EMU_PRESET_KEY_HITAG2_EPOCH, &epoch, 1U)) {
flipper_format_rewind(ctx->flipper_format);
flipper_format_insert_or_update_uint32(
ctx->flipper_format, EMU_PRESET_KEY_HITAG2_EPOCH, &epoch, 1U);
}
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewAbout);
if(app->view_about) {
view_commit_model(app->view_about, true);
}
}
static bool emulate_prompt_fiat_v1_key(ProtoPirateApp* app, EmulateContext* ctx) {
furi_check(app);
furi_check(ctx);
if(!g_host_api || !g_host_api->ensure_text_input || !g_host_api->ensure_text_input(app)) {
return false;
}
memset(ctx->hitag2_key_text, 0, sizeof(ctx->hitag2_key_text));
text_input_reset(app->text_input);
text_input_set_header_text(app->text_input, "HITAG2 key (12 hex):");
text_input_set_result_callback(
app->text_input,
emulate_fiat_v1_key_input_callback,
app,
ctx->hitag2_key_text,
sizeof(ctx->hitag2_key_text),
true);
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewTextInput);
return true;
}
static bool emu_preset_name_is_custom_marker(const char* preset_name) {
return preset_name && (!strcmp(preset_name, "Custom") || !strcmp(preset_name, "CUSTOM") ||
!strcmp(preset_name, "FuriHalSubGhzPresetCustom") ||
@@ -148,9 +269,8 @@ static bool emulate_radio_ready(ProtoPirateApp* app) {
static uint32_t emulate_min_tx_time(const EmulateContext* ctx) {
if(!ctx || !ctx->protocol_name) return MIN_TX_TIME;
const char* proto = furi_string_get_cstr(ctx->protocol_name);
if(strcmp(proto, "Kia V3/V4") == 0 || strcmp(proto, "Kia V3") == 0 ||
strcmp(proto, "Kia V4") == 0 || strcmp(proto, "KIA/HYU V3") == 0 ||
strcmp(proto, "KIA/HYU V4") == 0) {
proto = protopirate_protocol_catalog_canonical_name(proto);
if(proto && strcmp(proto, KIA_PROTOCOL_V3_V4_NAME) == 0) {
return MIN_TX_TIME_KIA_V3_V4;
}
return MIN_TX_TIME;
@@ -335,11 +455,13 @@ static bool emulate_context_try_init_transmitter(ProtoPirateApp* app, EmulateCon
if(!ctx->flipper_format || !ctx->protocol_name) return false;
const char* proto_name = furi_string_get_cstr(ctx->protocol_name);
const char* registry_name = proto_name;
if(strcmp(proto_name, "Kia V3") == 0 || strcmp(proto_name, "Kia V4") == 0 ||
strcmp(proto_name, "KIA/HYU V3") == 0 || strcmp(proto_name, "KIA/HYU V4") == 0) {
registry_name = "Kia V3/V4";
FURI_LOG_I(TAG, "Protocol name %s mapped to Kia V3/V4 for registry", proto_name);
const char* registry_name = protopirate_protocol_catalog_canonical_name(proto_name);
if(!registry_name || !protopirate_protocol_catalog_can_tx(proto_name)) {
FURI_LOG_E(TAG, "Protocol %s has no TX catalog entry", proto_name ? proto_name : "?");
return false;
}
if(strcmp(proto_name, registry_name) != 0) {
FURI_LOG_I(TAG, "Protocol name %s mapped to %s for registry", proto_name, registry_name);
}
EmulateResolvedPreset resolved_preset;
@@ -348,9 +470,14 @@ static bool emulate_context_try_init_transmitter(ProtoPirateApp* app, EmulateCon
return false;
}
bool registry_ready = g_host_api && g_host_api->apply_protocol_registry_for_preset_data &&
g_host_api->apply_protocol_registry_for_preset_data(
app, resolved_preset.data, resolved_preset.size);
bool registry_ready = g_host_api && g_host_api->apply_protocol_registry_for_context &&
g_host_api->apply_protocol_registry_for_context(
app,
ctx->preset,
ctx->freq,
resolved_preset.data,
resolved_preset.size,
registry_name);
emulate_resolved_preset_release(&resolved_preset);
if(!registry_ready) {
FURI_LOG_E(TAG, "Failed to apply protocol registry for emulate preset");
@@ -402,6 +529,12 @@ static uint8_t emu_button_for_protocol(
InputKey key,
uint8_t original,
FlipperFormat* ff) {
if(!protocol) {
return original;
}
protocol = protopirate_protocol_catalog_canonical_name(protocol);
if(strcmp(protocol, KIA_PROTOCOL_V7_NAME) == 0) {
switch(key) {
case InputKeyUp:
@@ -416,9 +549,7 @@ static uint8_t emu_button_for_protocol(
return original;
}
}
if(strcmp(protocol, "Kia V3/V4") == 0 || strcmp(protocol, "Kia V3") == 0 ||
strcmp(protocol, "Kia V4") == 0 || strcmp(protocol, "KIA/HYU V3") == 0 ||
strcmp(protocol, "KIA/HYU V4") == 0) {
if(strcmp(protocol, KIA_PROTOCOL_V3_V4_NAME) == 0) {
switch(key) {
case InputKeyUp:
return 0x01;
@@ -511,6 +642,23 @@ static uint8_t emu_button_for_protocol(
default:
return original;
}
} else if(strstr(protocol, RENAULT_PROTOCOL_V0_NAME)) {
uint32_t renault_type = 0U;
if(ff) {
flipper_format_rewind(ff);
flipper_format_read_uint32(ff, EMU_PRESET_KEY_TYPE, &renault_type, 1);
}
const bool type_13 = renault_type == 0x13U;
const bool type_high = (renault_type == 0x1AU) || (renault_type == 0x3BU);
switch(key) {
case InputKeyUp:
return type_13 ? 0x06 : (type_high ? 0x44 : 0x04);
case InputKeyOk:
return type_13 ? 0x0A : (type_high ? 0x48 : 0x08);
default:
return original;
}
} else if(strstr(protocol, "Honda V1")) {
switch(key) {
case InputKeyUp:
@@ -552,7 +700,7 @@ static uint8_t emu_button_for_protocol(
default:
return original;
}
} else if(strstr(protocol, "Land Rover")) {
} else if(strstr(protocol, "Honda V2")) {
switch(key) {
case InputKeyUp:
return 0x02; // Lock
@@ -627,14 +775,16 @@ static uint8_t emu_button_for_protocol(
default:
return original;
}
} else if(strstr(protocol, "Fiat V1")) {
} else if(strstr(protocol, FIAT_V1_PROTOCOL_NAME)) {
switch(key) {
case InputKeyUp:
return 0x8; // Lock
return 0x4; // Lock
case InputKeyOk:
return 0x0; // Unlock
return 0x8; // Unlock
case InputKeyDown:
return 0xD; // Trunk
return 0x2; // Trunk
case InputKeyLeft:
return 0x1; // Close
default:
return original;
}
@@ -650,6 +800,19 @@ static uint8_t emu_button_for_protocol(
return original;
}
static bool emulate_renault_is_rolling(FlipperFormat* ff) {
if(!ff) {
return false;
}
uint32_t rolling = 0U;
flipper_format_rewind(ff);
if(flipper_format_read_uint32(ff, "Rolling", &rolling, 1)) {
return rolling != 0U;
}
return false;
}
static bool emulate_update_data(EmulateContext* ctx, uint8_t button) {
if(!ctx || !ctx->flipper_format) return false;
@@ -719,7 +882,7 @@ static void emulate_draw_callback(Canvas* canvas, void* model) {
canvas_set_font(canvas, FontSecondary);
char* unlock_text = "UNLOCK";
const char* unlock_text = ctx->replay_only ? "REPLAY" : "UNLOCK";
uint16_t width_button = canvas_string_width(canvas, unlock_text) + 8;
uint16_t height_button = canvas_current_font_height(canvas);
canvas_draw_rbox(
@@ -780,14 +943,20 @@ static bool emulate_input_callback(InputEvent* event, void* context) {
return true;
}
uint8_t button = emu_button_for_protocol(
furi_string_get_cstr(ctx->protocol_name),
event->key,
ctx->original_button,
ctx->flipper_format);
if(!ctx->replay_only) {
uint8_t button = emu_button_for_protocol(
furi_string_get_cstr(ctx->protocol_name),
event->key,
ctx->original_button,
ctx->flipper_format);
ctx->current_counter++;
emulate_update_data(ctx, button);
if(furi_string_equal(ctx->protocol_name, RENAULT_PROTOCOL_V0_NAME)) {
ctx->current_counter = (ctx->current_counter + 1U) & 0xFFU;
} else {
ctx->current_counter++;
}
emulate_update_data(ctx, button);
}
ctx->is_transmitting = true;
view_dispatcher_send_custom_event(
@@ -806,13 +975,23 @@ static bool emulate_input_callback(InputEvent* event, void* context) {
return false;
}
static uint8_t get_tx_preset_byte(uint8_t* preset_data) {
#define MAX_PRESET_SIZE 128
uint8_t offset = 0;
while(preset_data[offset] && (offset < MAX_PRESET_SIZE)) {
static bool get_tx_preset_byte(const uint8_t* preset_data, size_t preset_size, uint8_t* preset_offset) {
if(!preset_data || !preset_offset) return false;
size_t offset = 0;
size_t scan_limit = preset_size < TX_PRESET_PATCH_MAX_SIZE ? preset_size :
TX_PRESET_PATCH_MAX_SIZE;
while((offset < scan_limit) && preset_data[offset]) {
offset += 2;
}
return (!preset_data[offset] ? offset + 2 : 0);
if(offset >= scan_limit) return false;
size_t tx_offset = offset + 2U;
if((tx_offset + 1U) >= scan_limit || (tx_offset + 1U) >= preset_size) return false;
*preset_offset = (uint8_t)tx_offset;
return true;
}
static void plugin_on_enter(void* context) {
@@ -939,15 +1118,36 @@ static void plugin_on_enter(void* context) {
furi_string_set(ctx->protocol_name, "Unknown");
}
// Standalone Suzuki captures: merged into Kia V0 Type 2
if(furi_string_equal(ctx->protocol_name, "Suzuki")) {
uint32_t type_suzuki = 2;
ctx->replay_only = furi_string_equal(ctx->protocol_name, RENAULT_PROTOCOL_V0_NAME) &&
!emulate_renault_is_rolling(ctx->flipper_format);
// Standalone Suzuki/Honda V0 captures: merged into Kia V0
if(furi_string_equal(ctx->protocol_name, "Suzuki") ||
furi_string_equal(ctx->protocol_name, "Suzuki V0") ||
furi_string_equal(ctx->protocol_name, "Honda V0")) {
uint32_t kia_v0_type = furi_string_equal(ctx->protocol_name, "Honda V0") ? 3U : 2U;
furi_string_set(ctx->protocol_name, KIA_PROTOCOL_V0_NAME);
flipper_format_rewind(ctx->flipper_format);
flipper_format_insert_or_update_string_cstr(
ctx->flipper_format, EMU_PRESET_KEY_PROTOCOL, KIA_PROTOCOL_V0_NAME);
flipper_format_insert_or_update_uint32(
ctx->flipper_format, EMU_PRESET_KEY_TYPE, &type_suzuki, 1);
ctx->flipper_format, EMU_PRESET_KEY_TYPE, &kia_v0_type, 1);
}
if(furi_string_equal(ctx->protocol_name, "Land Rover V0")) {
furi_string_set(ctx->protocol_name, "Honda V2");
flipper_format_rewind(ctx->flipper_format);
flipper_format_insert_or_update_string_cstr(
ctx->flipper_format, EMU_PRESET_KEY_PROTOCOL, "Honda V2");
}
const char* canonical_protocol =
protopirate_protocol_catalog_canonical_name(furi_string_get_cstr(ctx->protocol_name));
if(canonical_protocol && strcmp(furi_string_get_cstr(ctx->protocol_name), canonical_protocol) != 0) {
furi_string_set(ctx->protocol_name, canonical_protocol);
flipper_format_rewind(ctx->flipper_format);
flipper_format_insert_or_update_string_cstr(
ctx->flipper_format, EMU_PRESET_KEY_PROTOCOL, canonical_protocol);
}
flipper_format_rewind(ctx->flipper_format);
@@ -973,6 +1173,17 @@ static void plugin_on_enter(void* context) {
view_set_context(app->view_about, app);
view_set_previous_callback(app->view_about, NULL);
if(furi_string_equal(ctx->protocol_name, FIAT_V1_PROTOCOL_NAME) &&
!emulate_has_hitag2_key(ctx->flipper_format)) {
if(!emulate_prompt_fiat_v1_key(app, ctx)) {
FURI_LOG_E(TAG, "Failed to show Fiat V1 HITAG2 key input");
notification_message(app->notifications, &sequence_error);
emulate_context_free();
emulate_request_nav_pop(app);
}
return;
}
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewAbout);
}
@@ -1020,6 +1231,7 @@ static bool plugin_on_event(void* context, SceneManagerEvent event) {
}
uint8_t* preset_data = resolved_preset.data;
uint8_t preset_patch_buffer[TX_PRESET_PATCH_MAX_SIZE];
if(preset_data) {
if(!emulate_radio_ready(app)) {
@@ -1035,21 +1247,35 @@ static bool plugin_on_event(void* context, SceneManagerEvent event) {
}
if(app->tx_power) {
uint8_t preset_offset = get_tx_preset_byte(preset_data);
uint8_t fm_byte = preset_data[preset_offset];
uint8_t am_byte = preset_data[preset_offset + 1];
bool patchable = true;
if(!resolved_preset.should_free) {
patchable = resolved_preset.size <= sizeof(preset_patch_buffer);
if(patchable) {
memcpy(preset_patch_buffer, preset_data, resolved_preset.size);
preset_data = preset_patch_buffer;
}
}
if(fm_byte && am_byte) {
uint8_t preset_offset = 0;
if(!patchable ||
!get_tx_preset_byte(preset_data, resolved_preset.size, &preset_offset)) {
FURI_LOG_I(TAG, INVALID_PRESET);
} else if(fm_byte) {
FURI_LOG_I(TAG, "FM PA table found.");
preset_data[preset_offset] = tx_power_value[app->tx_power];
} else if(am_byte) {
FURI_LOG_I(TAG, "AM PA table found.");
preset_data[preset_offset + 1] =
tx_power_value[TX_PRESET_VALUES_AM + app->tx_power];
} else {
FURI_LOG_I(TAG, INVALID_PRESET);
uint8_t fm_byte = preset_data[preset_offset];
uint8_t am_byte = preset_data[preset_offset + 1];
if(fm_byte && am_byte) {
FURI_LOG_I(TAG, INVALID_PRESET);
} else if(fm_byte) {
FURI_LOG_I(TAG, "FM PA table found.");
preset_data[preset_offset] = tx_power_value[app->tx_power];
} else if(am_byte) {
FURI_LOG_I(TAG, "AM PA table found.");
preset_data[preset_offset + 1] =
tx_power_value[TX_PRESET_VALUES_AM + app->tx_power];
} else {
FURI_LOG_I(TAG, INVALID_PRESET);
}
}
}
@@ -1144,6 +1370,11 @@ static bool plugin_on_event(void* context, SceneManagerEvent event) {
static void plugin_on_exit(void* context) {
ProtoPirateApp* app = context;
if(!app) {
emulate_context_free();
return;
}
// Stop any active transmission before tearing down callbacks.
if(app->txrx && app->txrx->txrx_state == ProtoPirateTxRxStateTx) {
FURI_LOG_I(TAG, "Stopping transmission on exit");
@@ -1169,11 +1400,19 @@ static void plugin_on_exit(void* context) {
if(g_host_api && g_host_api->storage_delete_temp) g_host_api->storage_delete_temp();
if(app->radio_initialized && app->txrx && app->txrx->environment && app->txrx->preset &&
app->txrx->preset->data && g_host_api &&
g_host_api->apply_protocol_registry_for_preset_data) {
if(!g_host_api->apply_protocol_registry_for_preset_data(
app, app->txrx->preset->data, app->txrx->preset->data_size)) {
FURI_LOG_W(TAG, "Failed to restore session protocol registry on emulate exit");
app->txrx->preset->data && app->txrx->preset->name &&
g_host_api && g_host_api->apply_protocol_registry_for_context) {
const char* preset_name = furi_string_get_cstr(app->txrx->preset->name);
if(preset_name) {
if(!g_host_api->apply_protocol_registry_for_context(
app,
preset_name,
app->txrx->preset->frequency,
app->txrx->preset->data,
app->txrx->preset->data_size,
NULL)) {
FURI_LOG_W(TAG, "Failed to restore session protocol registry on emulate exit");
}
}
}
@@ -1183,7 +1422,9 @@ static void plugin_on_exit(void* context) {
view_set_context(app->view_about, NULL);
}
notification_message_block(app->notifications, &sequence_blink_stop);
if(app->notifications) {
notification_message_block(app->notifications, &sequence_blink_stop);
}
}
static void plugin_context_release(void* context) {
@@ -7,16 +7,20 @@
#include <gui/scene_manager.h>
#define PROTOPIRATE_EMULATE_PLUGIN_APP_ID "protopirate_emulate_plugin"
#define PROTOPIRATE_EMULATE_PLUGIN_API_VERSION 1U
#define PROTOPIRATE_EMULATE_PLUGIN_API_VERSION 2U
typedef struct {
bool (*radio_init)(void* app);
bool (*apply_protocol_registry_for_preset_data)(
bool (*apply_protocol_registry_for_context)(
void* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size);
size_t preset_data_size,
const char* protocol_name);
void (*rx_stack_suspend_for_tx)(void* app);
bool (*ensure_view_about)(void* app);
bool (*ensure_text_input)(void* app);
void (*idle)(void* app);
void (*history_release_scratch)(void* app);
void (*storage_delete_temp)(void);
@@ -27,12 +27,6 @@ static ProtoPiratePsaBfContext g_active_ctx = ProtoPiratePsaBfContextReceiverInf
static void show_bf_result(void* app, uint8_t status, ButtonCallback callback);
static void bf_finish_and_show_result(void* app, ButtonCallback result_callback);
static void psa_bf_done_cb(void* context) {
if(g_host_api && g_host_api->send_custom_event) {
g_host_api->send_custom_event(context, ProtoPirateCustomEventPsaBruteforceComplete);
}
}
static bool item_needs_bruteforce_from_ff(FlipperFormat* ff, bool require_psa_protocol) {
if(!ff) return false;
FuriString* s = furi_string_alloc();
@@ -237,8 +231,8 @@ static bool start_bruteforce(void* app) {
g_host_api->notification_error(app);
return false;
}
state->on_done = psa_bf_done_cb;
state->on_done_ctx = app;
state->on_done = NULL;
state->on_done_ctx = NULL;
g_bf_state = state;
g_bf_thread = furi_thread_alloc_ex("PsaBf", 2048, psa_brute_force_thread_entry, state);
if(!g_bf_thread) {
@@ -278,7 +272,11 @@ static bool
if(bfst == PSA_BF_STATUS_IDLE || bfst == PSA_BF_STATUS_RUNNING) {
show_bf_progress(app);
} else {
bf_finish_and_show_result(app, NULL);
if(ctx == ProtoPiratePsaBfContextSubDecode) {
g_host_api->send_custom_event(app, ProtoPirateCustomEventPsaBruteforceComplete);
} else {
bf_finish_and_show_result(app, NULL);
}
}
return true;
}
@@ -335,6 +333,7 @@ static bool
static void plugin_on_scene_exit(void* app, ProtoPiratePsaBfContext ctx) {
UNUSED(app);
UNUSED(ctx);
bf_cancel_thread();
}
static bool plugin_widget_left_should_bruteforce(void* app, ProtoPiratePsaBfContext ctx) {
@@ -0,0 +1,89 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <gui/scene_manager.h>
#include <lib/flipper_application/flipper_application.h>
#include <lib/subghz/devices/devices.h>
#include "../../defines.h"
#include "../../protopirate_history.h"
#include "../../views/protopirate_receiver.h"
#define PROTOPIRATE_TOOL_SCENE_PLUGIN_APP_ID "protopirate_tool_scene_plugins"
#define PROTOPIRATE_TOOL_SCENE_PLUGIN_API_VERSION 1U
typedef enum {
ProtoPirateToolScenePluginKindSubDecode = 0,
#ifdef ENABLE_TIMING_TUNER_SCENE
ProtoPirateToolScenePluginKindTimingTuner,
#endif
} ProtoPirateToolScenePluginKind;
typedef struct {
bool (*ensure_receiver_view)(void* app);
bool (*ensure_widget)(void* app);
bool (*ensure_view_about)(void* app);
bool (*radio_init)(void* app);
void (*rx_stack_resume_after_tx)(void* app);
void (*preset_init)(
void* app,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size);
bool (*refresh_protocol_registry)(void* app, bool ensure_receiver_ready);
bool (*apply_protocol_registry_for_context)(
void* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name);
void (*begin)(void* app, uint8_t* preset_data);
uint32_t (*rx)(void* app, uint32_t frequency);
void (*rx_end)(void* app);
void (*get_frequency_modulation_str)(
void* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size);
void (*history_release_scratch)(ProtoPirateHistory* history);
bool (*radio_device_is_external)(const SubGhzDevice* radio_device);
void (*receiver_add_data_statusbar)(
ProtoPirateReceiver* receiver,
const char* frequency_str,
const char* preset_str,
const char* history_stat_str,
bool external_radio);
uint16_t (*receiver_get_idx_menu)(ProtoPirateReceiver* receiver);
void (*receiver_set_idx_menu)(ProtoPirateReceiver* receiver, uint16_t idx);
void (*receiver_set_callback)(
ProtoPirateReceiver* receiver,
ProtoPirateReceiverCallback callback,
void* context);
void (*receiver_set_sub_decode_mode)(ProtoPirateReceiver* receiver, bool sub_decode_mode);
void (*receiver_set_sub_decode_progress)(ProtoPirateReceiver* receiver, uint8_t progress);
void (*receiver_reset_menu)(ProtoPirateReceiver* receiver);
void (*receiver_sync_menu_from_history)(
ProtoPirateReceiver* receiver,
ProtoPirateHistory* history);
bool (*psa_bf_plugin_ensure_loaded)(void* app);
void (*psa_bf_context_release)(void* app);
} ProtoPirateToolSceneHostApi;
typedef struct {
const char* plugin_name;
ProtoPirateToolScenePluginKind kind;
void (*set_host_api)(const ProtoPirateToolSceneHostApi* host_api);
void (*on_enter)(void* app);
bool (*on_event)(void* app, SceneManagerEvent event);
void (*on_exit)(void* app);
void (*release)(void* app);
} ProtoPirateToolScenePlugin;
@@ -272,4 +272,5 @@ void protopirate_scene_about_on_exit(void* context) {
view_set_draw_callback(app->view_about, NULL);
view_set_input_callback(app->view_about, NULL);
view_set_context(app->view_about, NULL);
}
@@ -19,12 +19,15 @@ static bool host_radio_init(void* app) {
return protopirate_radio_init((ProtoPirateApp*)app);
}
static bool host_apply_protocol_registry_for_preset_data(
static bool host_apply_protocol_registry_for_context(
void* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size) {
return protopirate_apply_protocol_registry_for_preset_data(
(ProtoPirateApp*)app, preset_data, preset_data_size);
size_t preset_data_size,
const char* protocol_name) {
return protopirate_apply_protocol_registry_for_context(
(ProtoPirateApp*)app, preset_name, frequency, preset_data, preset_data_size, protocol_name);
}
static void host_rx_stack_suspend_for_tx(void* app) {
@@ -35,6 +38,10 @@ static bool host_ensure_view_about(void* app) {
return protopirate_ensure_view_about((ProtoPirateApp*)app);
}
static bool host_ensure_text_input(void* app) {
return protopirate_ensure_text_input((ProtoPirateApp*)app);
}
static void host_idle(void* app) {
protopirate_idle((ProtoPirateApp*)app);
}
@@ -70,9 +77,10 @@ static void protopirate_emulate_apply_pending_nav(ProtoPirateApp* app) {
static const ProtoPirateEmulateHostApi protopirate_emulate_host_api = {
.radio_init = host_radio_init,
.apply_protocol_registry_for_preset_data = host_apply_protocol_registry_for_preset_data,
.apply_protocol_registry_for_context = host_apply_protocol_registry_for_context,
.rx_stack_suspend_for_tx = host_rx_stack_suspend_for_tx,
.ensure_view_about = host_ensure_view_about,
.ensure_text_input = host_ensure_text_input,
.idle = host_idle,
.history_release_scratch = host_history_release_scratch,
.storage_delete_temp = host_storage_delete_temp,
@@ -1,6 +1,7 @@
// scenes/protopirate_scene_receiver.c
#include "../protopirate_app_i.h"
#include "../helpers/protopirate_storage.h"
#include "../helpers/protopirate_saved_match.h"
#include "views/protopirate_receiver.h"
#include <notification/notification_messages.h>
#include <stdio.h>
@@ -72,43 +73,12 @@ static void protopirate_scene_receiver_callback(
uint16_t last_index = protopirate_history_get_item(app->txrx->history) - 1;
protopirate_view_receiver_set_idx_menu(app->protopirate_receiver, last_index);
uint16_t new_idx = protopirate_history_get_item(app->txrx->history) - 1;
if(app->auto_save) {
FlipperFormat* ff = protopirate_history_get_raw_data(
app->txrx->history, protopirate_history_get_item(app->txrx->history) - 1);
if(ff) {
FuriString* protocol = furi_string_alloc();
if(!protocol) {
FURI_LOG_E(TAG, "protocol allocation failed");
return;
}
flipper_format_rewind(ff);
if(!flipper_format_read_string(ff, FF_PROTOCOL, protocol)) {
furi_string_set_str(protocol, "Unknown");
}
furi_string_replace_all(protocol, "/", "_");
furi_string_replace_all(protocol, " ", "_");
FuriString* saved_path = furi_string_alloc();
if(!saved_path) {
FURI_LOG_E(TAG, "saved_path allocation failed");
furi_string_free(protocol);
return;
}
if(protopirate_storage_save_capture(
ff, furi_string_get_cstr(protocol), saved_path)) {
FURI_LOG_I(TAG, "Auto-saved: %s", furi_string_get_cstr(saved_path));
notification_message(app->notifications, &sequence_double_vibro);
} else {
FURI_LOG_E(TAG, "Auto-save failed");
}
furi_string_free(protocol);
furi_string_free(saved_path);
}
protopirate_history_mark_auto_save_pending(app->txrx->history, new_idx);
}
if(app->check_saved) {
protopirate_history_mark_saved_match_pending(app->txrx->history, new_idx);
}
view_dispatcher_send_custom_event(
@@ -123,17 +93,108 @@ static void protopirate_scene_receiver_callback(
}
}
static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) {
static bool protopirate_scene_receiver_process_auto_save(ProtoPirateApp* app) {
furi_check(app);
if(!app->radio_initialized) {
if(!app->txrx || !app->txrx->history) {
return false;
}
uint16_t idx = 0;
if(!protopirate_history_find_pending_auto_save(app->txrx->history, &idx)) {
return false;
}
FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, idx);
if(ff) {
FuriString* protocol = furi_string_alloc();
FuriString* saved_path = furi_string_alloc();
if(protocol && saved_path) {
flipper_format_rewind(ff);
if(!flipper_format_read_string(ff, FF_PROTOCOL, protocol)) {
furi_string_set_str(protocol, "Unknown");
}
furi_string_replace_all(protocol, "/", "_");
furi_string_replace_all(protocol, " ", "_");
if(protopirate_storage_save_capture(ff, furi_string_get_cstr(protocol), saved_path)) {
FURI_LOG_I(TAG, "Auto-saved: %s", furi_string_get_cstr(saved_path));
notification_message(app->notifications, &sequence_double_vibro);
} else {
FURI_LOG_E(TAG, "Auto-save failed");
notification_message(app->notifications, &sequence_error);
}
} else {
FURI_LOG_E(TAG, "Auto-save allocation failed");
notification_message(app->notifications, &sequence_error);
}
if(protocol) furi_string_free(protocol);
if(saved_path) furi_string_free(saved_path);
protopirate_history_release_scratch(app->txrx->history);
} else {
FURI_LOG_E(TAG, "Auto-save skipped: history capture unavailable");
notification_message(app->notifications, &sequence_error);
}
protopirate_history_mark_auto_save_done(app->txrx->history, idx);
return true;
}
static void protopirate_scene_receiver_process_saved_match(ProtoPirateApp* app) {
furi_check(app);
if(!app->check_saved || !app->txrx || !app->txrx->history) {
return;
}
protopirate_rx_stack_resume_after_tx(app);
uint16_t idx = 0;
if(!protopirate_history_find_pending_saved_match(app->txrx->history, &idx)) {
return;
}
FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, idx);
if(ff) {
FuriString* matched_name = furi_string_alloc();
FuriString* matched_path = furi_string_alloc();
if(matched_name && matched_path) {
flipper_format_rewind(ff);
if(protopirate_saved_match_signal(ff, matched_name, matched_path)) {
protopirate_history_set_matched_saved(
app->txrx->history,
idx,
furi_string_get_cstr(matched_name),
furi_string_get_cstr(matched_path));
FURI_LOG_I(TAG, "Matched saved signal: %s", furi_string_get_cstr(matched_name));
}
} else {
FURI_LOG_E(TAG, "Failed to allocate saved-match strings");
}
if(matched_name) furi_string_free(matched_name);
if(matched_path) furi_string_free(matched_path);
protopirate_history_release_scratch(app->txrx->history);
}
protopirate_history_mark_saved_match_done(app->txrx->history, idx);
}
static void protopirate_scene_receiver_process_deferred_storage(ProtoPirateApp* app) {
if(protopirate_scene_receiver_process_auto_save(app)) {
return;
}
protopirate_scene_receiver_process_saved_match(app);
}
static bool protopirate_scene_receiver_bind_rx_stack(ProtoPirateApp* app) {
furi_check(app);
if(!app->txrx->receiver) {
FURI_LOG_E(TAG, "SubGhz receiver unavailable — staying on receiver in degraded mode");
notification_message(app->notifications, &sequence_error);
return;
return false;
}
if(!app->txrx->worker) {
@@ -141,7 +202,7 @@ static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) {
if(!app->txrx->worker) {
FURI_LOG_E(TAG, "Failed to allocate worker — staying on receiver in degraded mode");
notification_message(app->notifications, &sequence_error);
return;
return false;
}
subghz_worker_set_overrun_callback(
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
@@ -150,9 +211,16 @@ static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) {
}
subghz_receiver_reset(app->txrx->receiver);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
subghz_receiver_set_rx_callback(app->txrx->receiver, protopirate_scene_receiver_callback, app);
return true;
}
static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) {
furi_check(app);
if(!app->radio_initialized) {
return;
}
if(app->txrx->hopper_state != ProtoPirateHopperStateOFF) {
app->txrx->hopper_state = ProtoPirateHopperStateRunning;
@@ -172,6 +240,12 @@ static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) {
if(app->txrx->hopper_state == ProtoPirateHopperStateRunning) {
frequency = subghz_setting_get_hopper_frequency(app->setting, 0);
app->txrx->hopper_idx_frequency = 0;
app->txrx->preset->frequency = frequency;
}
protopirate_rx_stack_resume_after_tx(app);
if(!protopirate_scene_receiver_bind_rx_stack(app)) {
return;
}
FURI_LOG_I(TAG, "Starting RX on %lu Hz", frequency);
@@ -205,6 +279,8 @@ void protopirate_scene_receiver_on_enter(void* context) {
app->txrx->history = protopirate_history_alloc();
if(!app->txrx->history) {
FURI_LOG_E(TAG, "Failed to allocate history!");
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
return;
}
}
@@ -328,8 +404,12 @@ bool protopirate_scene_receiver_on_event(void* context, SceneManagerEvent event)
break;
}
} else if(event.type == SceneManagerEventTypeTick) {
protopirate_scene_receiver_process_deferred_storage(app);
if(app->txrx->hopper_state != ProtoPirateHopperStateOFF) {
protopirate_hopper_update(app);
if(protopirate_hopper_update(app) && protopirate_scene_receiver_bind_rx_stack(app)) {
protopirate_rx(app, app->txrx->preset->frequency);
}
static uint8_t hopper_statusbar_tick = 0;
if(++hopper_statusbar_tick >= 8) {
hopper_statusbar_tick = 0;
@@ -9,6 +9,7 @@ enum ProtoPirateSettingIndex {
ProtoPirateSettingIndexTXPower,
#endif
ProtoPirateSettingIndexAutoSave,
ProtoPirateSettingIndexCheckSaved,
ProtoPirateSettingIndexLock,
};
@@ -29,6 +30,12 @@ const char* const auto_save_text[AUTO_SAVE_COUNT] = {
"ON",
};
#define CHECK_SAVED_COUNT 2
const char* const check_saved_text[CHECK_SAVED_COUNT] = {
"OFF",
"ON",
};
#ifdef ENABLE_EMULATE_FEATURE
#define TX_POWER_COUNT 9
const char* const tx_power_text[TX_POWER_COUNT] = {
@@ -173,6 +180,14 @@ static void protopirate_scene_receiver_config_set_auto_save(VariableItem* item)
variable_item_set_current_value_text(item, auto_save_text[index]);
}
static void protopirate_scene_receiver_config_set_check_saved(VariableItem* item) {
ProtoPirateApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->check_saved = (index == 1);
variable_item_set_current_value_text(item, check_saved_text[index]);
}
#ifdef ENABLE_EMULATE_FEATURE
static void protopirate_scene_receiver_config_set_tx_power(VariableItem* item) {
ProtoPirateApp* app = variable_item_get_context(item);
@@ -268,6 +283,15 @@ void protopirate_scene_receiver_config_on_enter(void* context) {
variable_item_set_current_value_index(item, app->auto_save ? 1 : 0);
variable_item_set_current_value_text(item, auto_save_text[app->auto_save ? 1 : 0]);
item = variable_item_list_add(
app->variable_item_list,
"Check Saved:",
CHECK_SAVED_COUNT,
protopirate_scene_receiver_config_set_check_saved,
app);
variable_item_set_current_value_index(item, app->check_saved ? 1 : 0);
variable_item_set_current_value_text(item, check_saved_text[app->check_saved ? 1 : 0]);
variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL);
variable_item_list_set_enter_callback(
app->variable_item_list, protopirate_scene_receiver_config_var_list_enter_callback, app);
@@ -2,7 +2,9 @@
#include "../protopirate_app_i.h"
#include "../helpers/protopirate_storage.h"
#include "../helpers/protopirate_psa_bf_host.h"
#include "../protocols/protocol_items.h"
#include "proto_pirate_icons.h"
#include <storage/storage.h>
#define TAG "ProtoPirateReceiverInfo"
@@ -11,6 +13,25 @@ static void protopirate_scene_receiver_info_widget_callback(
InputType type,
void* context);
static bool
protopirate_receiver_info_selected_protocol_is(ProtoPirateApp* app, const char* protocol_name) {
if(!app || !app->txrx || !app->txrx->history || !protocol_name) return false;
FlipperFormat* ff =
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
if(!ff) return false;
bool match = false;
FuriString* protocol = furi_string_alloc();
if(protocol) {
flipper_format_rewind(ff);
match = flipper_format_read_string(ff, FF_PROTOCOL, protocol) &&
furi_string_cmp_str(protocol, protocol_name) == 0;
furi_string_free(protocol);
}
return match;
}
static void protopirate_scene_receiver_info_text_input_callback(void* context) {
ProtoPirateApp* app = context;
view_dispatcher_send_custom_event(
@@ -19,6 +40,7 @@ static void protopirate_scene_receiver_info_text_input_callback(void* context) {
static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) {
widget_reset(app->widget);
app->emulate_disabled_for_loaded = true;
FuriString* text = furi_string_alloc();
protopirate_history_get_text_item_menu(app->txrx->history, text, app->txrx->idx_menu_chosen);
@@ -36,8 +58,11 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) {
FuriString* protocol = furi_string_alloc();
flipper_format_rewind(ff);
if(flipper_format_read_string(ff, FF_PROTOCOL, protocol)) {
if(furi_string_cmp_str(protocol, "PSA") == 0) is_psa = true;
app->emulate_disabled_for_loaded = (furi_string_cmp_str(protocol, "Scher-Khan") == 0);
const char* protocol_name = furi_string_get_cstr(protocol);
if(strcmp(protopirate_protocol_catalog_canonical_name(protocol_name), "PSA") == 0)
is_psa = true;
app->emulate_disabled_for_loaded =
!protopirate_protocol_catalog_can_tx(protocol_name);
}
furi_string_free(protocol);
}
@@ -129,7 +154,9 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) {
widget_add_button_element(
app->widget,
GuiButtonTypeRight,
"Save",
protopirate_history_has_matched_saved(app->txrx->history, app->txrx->idx_menu_chosen) ?
"Update" :
"Save",
protopirate_scene_receiver_info_widget_callback,
app);
@@ -147,10 +174,15 @@ static void protopirate_scene_receiver_info_widget_callback(
ProtoPirateApp* app = context;
if(type == InputTypeShort || type == InputTypeLong) {
if(result == GuiButtonTypeRight) {
bool has_match = protopirate_history_has_matched_saved(
app->txrx->history, app->txrx->idx_menu_chosen);
view_dispatcher_send_custom_event(
app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave);
app->view_dispatcher,
has_match ? ProtoPirateCustomEventReceiverInfoUpdate :
ProtoPirateCustomEventReceiverInfoSave);
} else if(result == GuiButtonTypeLeft) {
if(protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin &&
if(protopirate_receiver_info_selected_protocol_is(app, "PSA") &&
protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin &&
app->psa_bf_plugin->widget_left_should_bruteforce(
app, ProtoPiratePsaBfContextReceiverInfo)) {
view_dispatcher_send_custom_event(
@@ -181,7 +213,7 @@ void protopirate_scene_receiver_info_on_enter(void* context) {
app->emulate_disabled_for_loaded = false;
if(protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin) {
if(app->psa_bf_plugin) {
if(app->psa_bf_plugin->is_running(app)) {
app->psa_bf_plugin->on_scene_enter(app, ProtoPiratePsaBfContextReceiverInfo);
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
@@ -197,7 +229,12 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e
ProtoPirateApp* app = context;
bool consumed = false;
if(protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin) {
if((event.type == SceneManagerEventTypeCustom) &&
(event.event == ProtoPirateCustomEventReceiverInfoBruteforceStart)) {
protopirate_psa_bf_plugin_ensure_loaded(app);
}
if(app->psa_bf_plugin) {
if(app->psa_bf_plugin->is_running(app) ||
event.event == ProtoPirateCustomEventPsaBruteforceComplete ||
event.event == ProtoPirateCustomEventReceiverInfoBruteforceStart ||
@@ -213,18 +250,36 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e
}
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == ProtoPirateCustomEventReceiverInfoUpdate) {
uint16_t idx = app->txrx->idx_menu_chosen;
const char* saved_path =
protopirate_history_get_matched_saved_path(app->txrx->history, idx);
if(saved_path) {
FlipperFormat* rx_ff = protopirate_history_get_raw_data(app->txrx->history, idx);
if(rx_ff) {
if(protopirate_storage_save_capture_to_path(rx_ff, saved_path)) {
notification_message(app->notifications, &sequence_success);
FURI_LOG_I(TAG, "Updated saved capture from received signal: %s", saved_path);
} else {
notification_message(app->notifications, &sequence_error);
FURI_LOG_E(TAG, "Failed to update saved capture: %s", saved_path);
}
protopirate_history_release_scratch(app->txrx->history);
} else {
notification_message(app->notifications, &sequence_error);
FURI_LOG_E(TAG, "No received capture available for update");
}
}
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
consumed = true;
}
if(event.event == ProtoPirateCustomEventReceiverInfoSave) {
FlipperFormat* ff =
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
if(ff) {
FuriString* protocol = furi_string_alloc();
flipper_format_rewind(ff);
if(!flipper_format_read_string(ff, FF_PROTOCOL, protocol)) {
furi_string_set_str(protocol, "Unknown");
}
furi_string_replace_all(protocol, "/", "_");
furi_string_replace_all(protocol, " ", "_");
protopirate_storage_get_capture_display_protocol(ff, protocol);
FuriString* auto_path = furi_string_alloc();
if(protopirate_storage_get_next_filename(
@@ -326,9 +381,9 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e
void protopirate_scene_receiver_info_on_exit(void* context) {
furi_check(context);
ProtoPirateApp* app = context;
protopirate_psa_bf_context_release(app);
widget_reset(app->widget);
if(app->txrx && app->txrx->history) {
protopirate_history_release_scratch(app->txrx->history);
}
protopirate_psa_bf_plugin_unload_if_idle(app);
}
@@ -1,11 +1,67 @@
// scenes/protopirate_scene_saved.c
#include "../protopirate_app_i.h"
#include "../helpers/protopirate_storage.h"
#include "proto_pirate_icons.h"
#define TAG "ProtoPirateSceneSaved"
void protopirate_scene_saved_on_enter(void* context) {
furi_check(context);
ProtoPirateApp* app = context;
scene_manager_previous_scene(app->scene_manager);
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage) {
if(!storage_dir_exists(storage, PROTOPIRATE_APP_FOLDER)) {
storage_simply_mkdir(storage, PROTOPIRATE_APP_FOLDER);
}
furi_record_close(RECORD_STORAGE);
}
if(!app->file_path) {
FURI_LOG_E(TAG, "file_path is NULL");
scene_manager_previous_scene(app->scene_manager);
return;
}
if(!app->dialogs) {
app->dialogs = furi_record_open(RECORD_DIALOGS);
if(!app->dialogs) {
FURI_LOG_E(TAG, "Failed to open dialogs");
scene_manager_previous_scene(app->scene_manager);
return;
}
}
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, ".psf", &I_subghz_10px);
browser_options.base_path = PROTOPIRATE_APP_FOLDER;
browser_options.skip_assets = true;
browser_options.hide_dot_files = true;
furi_string_set(app->file_path, PROTOPIRATE_APP_FOLDER);
FuriString* selection = furi_string_alloc();
if(app->loaded_file_path && !furi_string_empty(app->loaded_file_path)) {
furi_string_set(selection, app->loaded_file_path);
} else {
furi_string_set(selection, PROTOPIRATE_APP_FOLDER);
}
bool file_selected =
dialog_file_browser_show(app->dialogs, selection, app->file_path, &browser_options);
if(file_selected) {
if(app->loaded_file_path) {
furi_string_free(app->loaded_file_path);
}
app->loaded_file_path = furi_string_alloc_set(selection);
furi_string_free(selection);
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneSavedInfo);
} else {
furi_string_free(selection);
scene_manager_previous_scene(app->scene_manager);
}
}
bool protopirate_scene_saved_on_event(void* context, SceneManagerEvent event) {
@@ -1,6 +1,7 @@
// scenes/protopirate_scene_saved_info.c
#include "../protopirate_app_i.h"
#include "../helpers/protopirate_storage.h"
#include "../protocols/protocol_items.h"
#include "../protocols/protocols_common.h"
#include "proto_pirate_icons.h"
@@ -107,14 +108,14 @@ void protopirate_scene_saved_info_on_enter(void* context) {
// Read fields
uint32_t temp_data = 0;
app->emulate_disabled_for_loaded = false;
app->emulate_disabled_for_loaded = true;
flipper_format_rewind(ff);
if(flipper_format_read_string(ff, FF_PROTOCOL, temp_str)) {
furi_string_cat_printf(info_str, "Protocol: %s\n", furi_string_get_cstr(temp_str));
}
if(furi_string_cmp_str(temp_str, "Scher-Khan") == 0) {
app->emulate_disabled_for_loaded = true;
const char* protocol_name = furi_string_get_cstr(temp_str);
furi_string_cat_printf(info_str, "Protocol: %s\n", protocol_name);
app->emulate_disabled_for_loaded =
!protopirate_protocol_catalog_can_tx(protocol_name);
}
flipper_format_rewind(ff);
@@ -19,113 +19,10 @@ typedef enum {
SubmenuIndexProtoPirateAbout,
} SubmenuIndex;
// Forward declaration
static void protopirate_scene_start_open_saved_captures(ProtoPirateApp* app);
static void protopirate_scene_start_submenu_callback(void* context, uint32_t index) {
furi_check(context);
ProtoPirateApp* app = context;
// Handle "Saved Captures" directly here, not via custom event
if(index == SubmenuIndexProtoPirateSaved) {
protopirate_scene_start_open_saved_captures(app);
} else {
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
}
static void protopirate_scene_start_open_saved_captures(ProtoPirateApp* app) {
FURI_LOG_I(TAG, "[1] Opening saved captures browser");
FURI_LOG_I(TAG, "[1a] PROTOPIRATE_APP_FOLDER = %s", PROTOPIRATE_APP_FOLDER);
// Check and create folder
FURI_LOG_D(TAG, "[2] Opening storage");
Storage* storage = furi_record_open(RECORD_STORAGE);
if(!storage) {
FURI_LOG_E(TAG, "[2a] Failed to open storage!");
return;
}
FURI_LOG_D(TAG, "[3] Checking folder exists");
if(!storage_dir_exists(storage, PROTOPIRATE_APP_FOLDER)) {
FURI_LOG_I(TAG, "[4] Creating folder");
storage_simply_mkdir(storage, PROTOPIRATE_APP_FOLDER);
}
#ifndef REMOVE_LOGS
bool folder_ok = storage_dir_exists(storage, PROTOPIRATE_APP_FOLDER);
FURI_LOG_D(TAG, "[5] Folder exists: %s", folder_ok ? "yes" : "no");
#endif
furi_record_close(RECORD_STORAGE);
FURI_LOG_D(TAG, "[6] Storage closed");
// Check file_path
FURI_LOG_D(TAG, "[7] Checking app->file_path");
if(!app->file_path) {
FURI_LOG_E(TAG, "[7a] app->file_path is NULL!");
return;
}
// Set starting path
FURI_LOG_D(TAG, "[8] Setting file_path");
furi_string_set(app->file_path, PROTOPIRATE_APP_FOLDER);
FURI_LOG_D(TAG, "[9] file_path set to: %s", furi_string_get_cstr(app->file_path));
// Configure file browser
FURI_LOG_D(TAG, "[10] Creating browser_options");
DialogsFileBrowserOptions browser_options;
FURI_LOG_D(TAG, "[11] Calling dialog_file_browser_set_basic_options");
dialog_file_browser_set_basic_options(&browser_options, ".psf", &I_subghz_10px);
FURI_LOG_D(TAG, "[12] Setting browser_options fields");
browser_options.base_path = PROTOPIRATE_APP_FOLDER;
browser_options.skip_assets = true;
browser_options.hide_dot_files = true;
FURI_LOG_D(TAG, "[13] Checking app->dialogs");
FURI_LOG_D(TAG, "[13a] app->dialogs = %p", (void*)app->dialogs);
if(!app->dialogs) {
FURI_LOG_E(TAG, "[13b] dialogs is NULL! Trying to open...");
app->dialogs = furi_record_open(RECORD_DIALOGS);
if(!app->dialogs) {
FURI_LOG_E(TAG, "[13c] Still NULL after open attempt!");
return;
}
FURI_LOG_I(TAG, "[13d] dialogs opened successfully");
}
FURI_LOG_I(TAG, "[14] === CALLING dialog_file_browser_show ===");
FURI_LOG_D(TAG, "[14a] dialogs=%p, file_path=%p", (void*)app->dialogs, (void*)app->file_path);
bool file_selected =
dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
FURI_LOG_I(TAG, "[15] === RETURNED from dialog_file_browser_show ===");
FURI_LOG_D(TAG, "[15a] file_selected = %d", file_selected);
if(file_selected) {
FURI_LOG_I(TAG, "[16] File selected: %s", furi_string_get_cstr(app->file_path));
if(app->loaded_file_path) {
FURI_LOG_D(TAG, "[17] Freeing old loaded_file_path");
furi_string_free(app->loaded_file_path);
}
FURI_LOG_D(TAG, "[18] Allocating new loaded_file_path");
app->loaded_file_path = furi_string_alloc_set(app->file_path);
FURI_LOG_D(TAG, "[19] Navigating to SavedInfo scene");
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneSavedInfo);
} else {
FURI_LOG_I(TAG, "[16] File browser cancelled or empty");
}
FURI_LOG_I(TAG, "[20] open_saved_captures complete");
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void protopirate_scene_start_on_enter(void* context) {
@@ -196,6 +93,9 @@ bool protopirate_scene_start_on_event(void* context, SceneManagerEvent event) {
} else if(event.event == SubmenuIndexProtoPirateReceiver) {
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneReceiver);
consumed = true;
} else if(event.event == SubmenuIndexProtoPirateSaved) {
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneSaved);
consumed = true;
} else if(event.event == SubmenuIndexProtoPirateReceiverConfig) {
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneReceiverConfig);
consumed = true;
File diff suppressed because it is too large Load Diff
@@ -1,12 +1,42 @@
// scenes/protopirate_scene_timing_tuner.c
#include "../protopirate_app_i.h"
#ifdef ENABLE_TIMING_TUNER_SCENE
#include "../protocols/protocol_items.h"
#ifndef PROTOPIRATE_TIMING_TUNER_PLUGIN_BUILD
void protopirate_scene_timing_tuner_on_enter(void* context) {
protopirate_tool_scene_on_enter(context, ProtoPirateToolScenePluginKindTimingTuner);
}
bool protopirate_scene_timing_tuner_on_event(void* context, SceneManagerEvent event) {
return protopirate_tool_scene_on_event(context, event);
}
void protopirate_scene_timing_tuner_on_exit(void* context) {
protopirate_tool_scene_on_exit(context);
}
#else
#include "../protopirate_history.h"
#include "../protocols/protocol_timings.h"
#include <gui/elements.h>
#include <math.h>
#define TAG "ProtoPirateTimingTuner"
static const ProtoPirateToolSceneHostApi* g_tool_scene_host_api = NULL;
#define protopirate_ensure_view_about(app) g_tool_scene_host_api->ensure_view_about(app)
#define protopirate_radio_init(app) g_tool_scene_host_api->radio_init(app)
#define protopirate_rx_stack_resume_after_tx(app) \
g_tool_scene_host_api->rx_stack_resume_after_tx(app)
#define protopirate_begin(app, preset_data) g_tool_scene_host_api->begin(app, preset_data)
#define protopirate_rx(app, frequency) g_tool_scene_host_api->rx(app, frequency)
#define protopirate_rx_end(app) g_tool_scene_host_api->rx_end(app)
#define protopirate_history_release_scratch(history) \
g_tool_scene_host_api->history_release_scratch(history)
#define MAX_TIMING_SAMPLES 512
#define VISIBLE_LINES 6
#define LINE_HEIGHT 9
@@ -49,6 +79,13 @@ typedef struct {
static TimingTunerContext* g_timing_ctx = NULL;
static void timing_tuner_context_free(void) {
if(g_timing_ctx) {
free(g_timing_ctx);
g_timing_ctx = NULL;
}
}
static void calculate_timing_stats(TimingTunerContext* ctx) {
size_t num_samples = ctx->buffer_wrapped ? MAX_TIMING_SAMPLES : ctx->write_idx;
@@ -614,22 +651,26 @@ void protopirate_scene_timing_tuner_on_enter(void* context) {
if(!protopirate_ensure_view_about(app)) {
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_POP;
return;
}
if(!app->radio_initialized && !protopirate_radio_init(app)) {
FURI_LOG_E(TAG, "Failed to initialize radio for timing tuner");
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_POP;
return;
}
if(app->txrx && app->txrx->history) {
protopirate_history_release_scratch(app->txrx->history);
}
protopirate_rx_stack_resume_after_tx(app);
if(!app->txrx->receiver) {
FURI_LOG_E(TAG, "Failed to allocate receiver for timing tuner");
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_POP;
return;
}
@@ -637,7 +678,7 @@ void protopirate_scene_timing_tuner_on_enter(void* context) {
if(!g_timing_ctx) {
FURI_LOG_E(TAG, "Failed to allocate timing tuner context");
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_POP;
return;
}
memset(g_timing_ctx, 0, sizeof(TimingTunerContext));
@@ -661,6 +702,11 @@ void protopirate_scene_timing_tuner_on_enter(void* context) {
app->txrx->worker = subghz_worker_alloc();
if(!app->txrx->worker) {
FURI_LOG_E(TAG, "Failed to allocate worker!");
view_set_draw_callback(app->view_about, NULL);
view_set_input_callback(app->view_about, NULL);
timing_tuner_context_free();
notification_message(app->notifications, &sequence_error);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_POP;
return;
}
// Set up worker callbacks
@@ -689,14 +735,15 @@ bool protopirate_scene_timing_tuner_on_event(void* context, SceneManagerEvent ev
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == 0) {
scene_manager_previous_scene(app->scene_manager);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_POP;
consumed = true;
} else if(event.event == 1) {
if(g_timing_ctx && g_timing_ctx->is_receiving) {
protopirate_rx_end(app);
g_timing_ctx->is_receiving = false;
}
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneReceiverConfig);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_NEXT;
app->tool_scene_nav_target = ProtoPirateSceneReceiverConfig;
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
@@ -723,10 +770,16 @@ void protopirate_scene_timing_tuner_on_exit(void* context) {
protopirate_rx_end(app);
}
subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
if(app->txrx && app->txrx->receiver) {
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
}
if(app->txrx->worker) {
if(app->txrx && app->txrx->worker) {
subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
}
if(app->txrx && app->txrx->worker) {
FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker);
subghz_worker_free(app->txrx->worker);
app->txrx->worker = NULL;
@@ -734,12 +787,38 @@ void protopirate_scene_timing_tuner_on_exit(void* context) {
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
}
view_set_draw_callback(app->view_about, NULL);
view_set_input_callback(app->view_about, NULL);
if(g_timing_ctx) {
free(g_timing_ctx);
g_timing_ctx = NULL;
if(app->view_about) {
view_set_draw_callback(app->view_about, NULL);
view_set_input_callback(app->view_about, NULL);
view_set_context(app->view_about, NULL);
}
timing_tuner_context_free();
}
static void timing_tuner_plugin_set_host_api(const ProtoPirateToolSceneHostApi* host_api) {
g_tool_scene_host_api = host_api;
}
static const ProtoPirateToolScenePlugin protopirate_timing_tuner_plugin = {
.plugin_name = "ProtoPirate Timing Tuner",
.kind = ProtoPirateToolScenePluginKindTimingTuner,
.set_host_api = timing_tuner_plugin_set_host_api,
.on_enter = protopirate_scene_timing_tuner_on_enter,
.on_event = protopirate_scene_timing_tuner_on_event,
.on_exit = protopirate_scene_timing_tuner_on_exit,
.release = NULL,
};
static const FlipperAppPluginDescriptor protopirate_timing_tuner_plugin_descriptor = {
.appid = PROTOPIRATE_TOOL_SCENE_PLUGIN_APP_ID,
.ep_api_version = PROTOPIRATE_TOOL_SCENE_PLUGIN_API_VERSION,
.entry_point = &protopirate_timing_tuner_plugin,
};
const FlipperAppPluginDescriptor* protopirate_timing_tuner_plugin_ep(void) {
return &protopirate_timing_tuner_plugin_descriptor;
}
#endif // PROTOPIRATE_TIMING_TUNER_PLUGIN_BUILD
#endif
@@ -5,6 +5,7 @@
#include <input/input.h>
#include <gui/elements.h>
#include <furi.h>
#include <stdio.h>
#include "proto_pirate_icons.h"
@@ -34,6 +35,7 @@ typedef struct {
ProtoPirateLock lock;
uint8_t lock_count;
uint8_t animation_frame;
uint8_t sub_decode_progress;
bool dolphin_view;
bool sub_decode_mode;
} ProtoPirateReceiverModel;
@@ -110,7 +112,24 @@ void protopirate_view_receiver_set_sub_decode_mode(
with_view_model(
receiver->view,
ProtoPirateReceiverModel * model,
{ model->sub_decode_mode = sub_decode_mode; },
{
model->sub_decode_mode = sub_decode_mode;
if(!sub_decode_mode) {
model->sub_decode_progress = 0;
}
},
true);
}
void protopirate_view_receiver_set_sub_decode_progress(
ProtoPirateReceiver* receiver,
uint8_t progress) {
furi_check(receiver);
if(progress > 100) progress = 100;
with_view_model(
receiver->view,
ProtoPirateReceiverModel * model,
{ model->sub_decode_progress = progress; },
true);
}
@@ -202,6 +221,23 @@ static void protopirate_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, b
canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11);
}
static void
protopirate_view_receiver_draw_progress_badge(Canvas* canvas, uint8_t progress) {
char progress_text[8];
snprintf(progress_text, sizeof(progress_text), "%u%%", progress > 100 ? 100 : progress);
canvas_set_font(canvas, FontSecondary);
uint8_t width = canvas_string_width(canvas, progress_text) + 8;
if(width < 30) width = 30;
if(width > 42) width = 42;
canvas_set_color(canvas, ColorBlack);
canvas_draw_rbox(canvas, 0, 52, width, 12, 2);
canvas_set_color(canvas, ColorWhite);
canvas_draw_str_aligned(canvas, width / 2, 62, AlignCenter, AlignBottom, progress_text);
canvas_set_color(canvas, ColorBlack);
}
void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* model) {
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
@@ -220,13 +256,20 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
// Draw RSSI
protopirate_view_rssi_draw(canvas, model);
} else {
protopirate_view_receiver_draw_progress_badge(canvas, model->sub_decode_progress);
}
//Draw To Unlock, Locked etc...
if(model->lock_count) {
canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str));
if(model->sub_decode_mode) {
canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str));
} else {
canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str));
}
canvas_set_font(canvas, FontSecondary);
elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
@@ -240,9 +283,14 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8);
canvas_draw_str(canvas, 74, 62, "Locked");
} else {
canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str));
if(model->sub_decode_mode) {
canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str));
} else {
canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str));
canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str));
canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str));
}
}
}
@@ -280,7 +328,7 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
}
} else {
//Are we in Radar View or FLipper View Mode?
if(!model->dolphin_view) {
if(!model->sub_decode_mode && !model->dolphin_view) {
const uint8_t center_x = 64;
const uint8_t center_y = 22;
for(uint8_t wave = 0; wave < 3; wave++) {
@@ -345,16 +393,20 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
//canvas_draw_str(canvas, 44, 10, model->external_radio ? "Ext" : "Int"); //FOR EXACT FLIPPER CLONE
}
// Draw EXT/INT indicator in upper right corner
canvas_set_font(canvas, FontSecondary);
if(model->external_radio) {
canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, "Ext");
if(model->sub_decode_mode) {
canvas_draw_str_aligned(
canvas, 127, 0, AlignRight, AlignTop, furi_string_get_cstr(model->preset_str));
} else {
canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, "Int");
if(model->external_radio) {
canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, "Ext");
} else {
canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, "Int");
}
}
//Draw the Auto-save Indicator
if(model->auto_save) {
if(!model->sub_decode_mode && model->auto_save) {
const char* auto_save_text = "Save";
canvas_draw_str(
canvas, 110 - canvas_string_width(canvas, auto_save_text), 7, auto_save_text);
@@ -369,8 +421,15 @@ bool protopirate_view_receiver_input(InputEvent* event, void* context) {
bool consumed = false;
ProtoPirateLock lock;
bool sub_decode_mode = false;
with_view_model(
receiver->view, ProtoPirateReceiverModel * model, { lock = model->lock; }, false);
receiver->view,
ProtoPirateReceiverModel * model,
{
lock = model->lock;
sub_decode_mode = model->sub_decode_mode;
},
false);
if(lock == ProtoPirateLockOn) {
bool do_unlock_cb = false;
@@ -433,7 +492,7 @@ bool protopirate_view_receiver_input(InputEvent* event, void* context) {
consumed = true;
break;
case InputKeyLeft:
if(receiver->callback) {
if(!sub_decode_mode && receiver->callback) {
receiver->callback(ProtoPirateCustomEventViewReceiverConfig, receiver->context);
}
consumed = true;
@@ -465,7 +524,7 @@ bool protopirate_view_receiver_input(InputEvent* event, void* context) {
if(item_count > 0) {
do_ok_cb = true;
} else if(event->type == InputTypeLong) {
} else if(!sub_decode_mode && event->type == InputTypeLong) {
do_toggle = true;
}
},
@@ -538,6 +597,7 @@ ProtoPirateReceiver* protopirate_view_receiver_alloc(bool auto_save) {
model->lock_count = 0;
model->auto_save = auto_save;
model->animation_frame = 0;
model->sub_decode_progress = 0;
model->dolphin_view = true;
model->sub_decode_mode = false;
},
@@ -33,6 +33,9 @@ void protopirate_view_receiver_set_autosave(ProtoPirateReceiver* receiver, bool
void protopirate_view_receiver_set_sub_decode_mode(
ProtoPirateReceiver* receiver,
bool sub_decode_mode);
void protopirate_view_receiver_set_sub_decode_progress(
ProtoPirateReceiver* receiver,
uint8_t progress);
void protopirate_view_receiver_reset_menu(ProtoPirateReceiver* receiver);
void protopirate_view_receiver_sync_menu_from_history(