diff --git a/applications/main/RollJam/helpers/rolljam_cc1101_ext.c b/applications/main/RollJam/helpers/rolljam_cc1101_ext.c index 3d3d8cc0..bfc29692 100644 --- a/applications/main/RollJam/helpers/rolljam_cc1101_ext.c +++ b/applications/main/RollJam/helpers/rolljam_cc1101_ext.c @@ -5,11 +5,10 @@ #include // ============================================================ -// 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 <<<"); } diff --git a/applications/main/RollJam/helpers/rolljam_receiver.c b/applications/main/RollJam/helpers/rolljam_receiver.c index e4bbec66..bac9ca56 100644 --- a/applications/main/RollJam/helpers/rolljam_receiver.c +++ b/applications/main/RollJam/helpers/rolljam_receiver.c @@ -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!"); } diff --git a/applications/main/RollJam/helpers/rolljam_receiver.h b/applications/main/RollJam/helpers/rolljam_receiver.h index 1ce6d483..5a253e1f 100644 --- a/applications/main/RollJam/helpers/rolljam_receiver.h +++ b/applications/main/RollJam/helpers/rolljam_receiver.h @@ -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); diff --git a/applications/main/RollJam/rolljam.c b/applications/main/RollJam/rolljam.c index b8455c79..1642a540 100644 --- a/applications/main/RollJam/rolljam.c +++ b/applications/main/RollJam/rolljam.c @@ -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); diff --git a/applications/main/RollJam/rolljam.h b/applications/main/RollJam/rolljam.h index d6b8320e..a7372281 100644 --- a/applications/main/RollJam/rolljam.h +++ b/applications/main/RollJam/rolljam.h @@ -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; diff --git a/applications/main/RollJam/scenes/rolljam_scene_attack_phase1.c b/applications/main/RollJam/scenes/rolljam_scene_attack_phase1.c index 111109a5..6362b243 100644 --- a/applications/main/RollJam/scenes/rolljam_scene_attack_phase1.c +++ b/applications/main/RollJam/scenes/rolljam_scene_attack_phase1.c @@ -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( diff --git a/applications/main/RollJam/scenes/rolljam_scene_attack_phase2.c b/applications/main/RollJam/scenes/rolljam_scene_attack_phase2.c index be2dbf4d..f57f2f04 100644 --- a/applications/main/RollJam/scenes/rolljam_scene_attack_phase2.c +++ b/applications/main/RollJam/scenes/rolljam_scene_attack_phase2.c @@ -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( diff --git a/applications/main/RollJam/scenes/rolljam_scene_attack_phase3.c b/applications/main/RollJam/scenes/rolljam_scene_attack_phase3.c index 3b3b25cd..09bf6f1c 100644 --- a/applications/main/RollJam/scenes/rolljam_scene_attack_phase3.c +++ b/applications/main/RollJam/scenes/rolljam_scene_attack_phase3.c @@ -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( diff --git a/applications/main/RollJam/scenes/rolljam_scene_menu.c b/applications/main/RollJam/scenes/rolljam_scene_menu.c index 59ffdf6a..6aeec6b6 100644 --- a/applications/main/RollJam/scenes/rolljam_scene_menu.c +++ b/applications/main/RollJam/scenes/rolljam_scene_menu.c @@ -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); } diff --git a/applications/main/RollJam/scenes/rolljam_scene_result.c b/applications/main/RollJam/scenes/rolljam_scene_result.c index fbb74641..2f3659cf 100644 --- a/applications/main/RollJam/scenes/rolljam_scene_result.c +++ b/applications/main/RollJam/scenes/rolljam_scene_result.c @@ -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);