diff --git a/base_pack/spectrum_analyzer/CHANGELOG.md b/base_pack/spectrum_analyzer/CHANGELOG.md new file mode 100644 index 00000000..e814c074 --- /dev/null +++ b/base_pack/spectrum_analyzer/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +## 1.5 + +- Add peak hold: the last detected peak stays on screen ("Peak:" while the signal is live, "Last:" once it is gone) until a new peak replaces it, so short bursts can be read after the fact; the label stores the frequency measured at sweep time, so retuning or zooming cannot mislabel it +- Save the current view (frequency, spectrum width, modulation and vertical scale) on exit and restore it on the next launch, including applying the restored modulation to the radio +- Hide the peak label while the mode/modulation overlay is visible (the two used to overlap in the top-right corner) + +## 1.4 + +- Version bump for catalog compatibility (no functional changes) + +## 1.3 + +- Version bump for catalog compatibility (no functional changes) + +## 1.2 + +- Version bump for catalog compatibility (no functional changes) + +## 1.1 + +- Initial catalog release: CC1101-based spectrum analyzer with five zoom widths, adjustable vertical scale, two modulation presets and external radio module support (original app by @jolcese) diff --git a/base_pack/spectrum_analyzer/README.md b/base_pack/spectrum_analyzer/README.md index c1cf6d5d..2d459f2d 100644 --- a/base_pack/spectrum_analyzer/README.md +++ b/base_pack/spectrum_analyzer/README.md @@ -3,5 +3,10 @@ This application allows you to plot a chart showing the relationship between amp The app has the following controls: - The OK button adjusts the width of the spectrum. +- A long press of the OK button toggles modulation (default/narrow). - The Up and Down buttons zoom in and out. -- The Left and Right buttons switch between different frequency bands. \ No newline at end of file +- The Left and Right buttons switch between different frequency bands. + +The last detected peak stays on screen (prefixed "Last:" once the signal is gone) until a new peak is found, so short bursts are easy to read after the fact. + +The app saves the current view (frequency, spectrum width, modulation and zoom) on exit and restores it on the next launch. diff --git a/base_pack/spectrum_analyzer/application.fam b/base_pack/spectrum_analyzer/application.fam index 1227c5b6..eaffe429 100644 --- a/base_pack/spectrum_analyzer/application.fam +++ b/base_pack/spectrum_analyzer/application.fam @@ -9,6 +9,6 @@ App( fap_icon="spectrum_10px.png", fap_category="Sub-GHz", fap_author="@xMasterX & @theY4Kman & @ALEEF02 (original by @jolcese)", - fap_version="1.4", + fap_version="1.5", fap_description="Displays a spectrogram chart to visually represent RF signals around you.", ) diff --git a/base_pack/spectrum_analyzer/spectrum_analyzer.c b/base_pack/spectrum_analyzer/spectrum_analyzer.c index ed6e8686..37645662 100644 --- a/base_pack/spectrum_analyzer/spectrum_analyzer.c +++ b/base_pack/spectrum_analyzer/spectrum_analyzer.c @@ -4,11 +4,24 @@ #include #include #include +#include +#include #include "spectrum_analyzer.h" #include #include "spectrum_analyzer_worker.h" +#define SPECTRUM_ANALYZER_SETTINGS_PATH APP_DATA_PATH("spectrum_analyzer.save") +#define SPECTRUM_ANALYZER_SETTINGS_MAGIC (0x5A) +#define SPECTRUM_ANALYZER_SETTINGS_VERSION (1) + +typedef struct { + uint32_t center_freq; + uint8_t width; + uint8_t modulation; + uint8_t vscroll; +} SpectrumAnalyzerSettings; + typedef struct { uint32_t center_freq; uint8_t width; @@ -25,6 +38,10 @@ typedef struct { float max_rssi; uint8_t max_rssi_dec; uint8_t max_rssi_channel; + + float last_peak_rssi; + uint32_t last_peak_frequency; + uint8_t channel_ss[NUM_CHANNELS]; } SpectrumAnalyzerModel; @@ -167,7 +184,7 @@ static void spectrum_analyzer_render_callback(Canvas* const canvas, void* ctx) { canvas_draw_str_aligned(canvas, 127, 4, AlignRight, AlignTop, tmp_str); } - // Draw cross and label + // Draw cross on the current peak if(model->max_rssi > PEAK_THRESHOLD) { // Compress height to max of 64 values (255>>2) uint8_t max_y = MAX((model->max_rssi_dec - model->vscroll) >> 2, 0); @@ -194,16 +211,20 @@ static void spectrum_analyzer_render_callback(Canvas* const canvas, void* ctx) { y2 = max_y + 2; if(y2 > 63) y2 = 63; // SHOULD NOT HAPPEN CHECK! canvas_draw_line(canvas, (uint8_t)x1, (uint8_t)y1, (uint8_t)x2, (uint8_t)y2); + } - // Label + // Peak label: latched, so it survives after the signal is gone; hidden + // while the mode/modulation overlay occupies the top-right corner + if((model->last_peak_rssi > PEAK_THRESHOLD) && !model->mode_change && + !model->modulation_change) { char temp_str[36]; snprintf( temp_str, 36, - "Peak: %3.2f Mhz %3.1f dbm", - ((double)(model->channel0_frequency + (model->max_rssi_channel * model->spacing)) / - 1000000), - (double)model->max_rssi); + "%s: %3.2f Mhz %3.1f dbm", + (model->max_rssi > PEAK_THRESHOLD) ? "Peak" : "Last", + ((double)model->last_peak_frequency / 1000000), + (double)model->last_peak_rssi); canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, temp_str); } @@ -225,6 +246,7 @@ static void spectrum_analyzer_worker_callback( float max_rssi, uint8_t max_rssi_dec, uint8_t max_rssi_channel, + uint32_t max_rssi_frequency, void* context) { SpectrumAnalyzer* spectrum_analyzer = context; furi_check( @@ -236,6 +258,13 @@ static void spectrum_analyzer_worker_callback( model->max_rssi_dec = max_rssi_dec; model->max_rssi_channel = max_rssi_channel; + // Latch the peak with the absolute frequency (Hz) the worker measured + // it at, so the held label stays correct after the view is retuned + if(max_rssi > PEAK_THRESHOLD) { + model->last_peak_rssi = max_rssi; + model->last_peak_frequency = max_rssi_frequency; + } + furi_mutex_release(spectrum_analyzer->model_mutex); view_port_update(spectrum_analyzer->view_port); } @@ -381,6 +410,48 @@ void spectrum_analyzer_calculate_frequencies(SpectrumAnalyzerModel* model) { model->channel0_frequency + ((NUM_CHANNELS - 1) * model->spacing)); } +static void spectrum_analyzer_load_settings(SpectrumAnalyzerModel* model) { + SpectrumAnalyzerSettings settings = {0}; + if(!saved_struct_load( + SPECTRUM_ANALYZER_SETTINGS_PATH, + &settings, + sizeof(settings), + SPECTRUM_ANALYZER_SETTINGS_MAGIC, + SPECTRUM_ANALYZER_SETTINGS_VERSION)) { + return; + } + + // If any saved value is out of range, discard the whole file and keep + // all defaults + if((settings.center_freq < MIN_300) || (settings.center_freq > MAX_900) || + (settings.width > PRECISE) || (settings.modulation > NARROW_MODULATION) || + (settings.vscroll > MAX_VSCROLL)) { + FURI_LOG_W("Spectrum", "Discarding out-of-range saved settings"); + return; + } + + model->center_freq = settings.center_freq; + model->width = settings.width; + model->modulation = settings.modulation; + model->vscroll = settings.vscroll; +} + +static void spectrum_analyzer_save_settings(const SpectrumAnalyzerModel* model) { + SpectrumAnalyzerSettings settings = { + .center_freq = model->center_freq, + .width = model->width, + .modulation = model->modulation, + .vscroll = model->vscroll, + }; + + saved_struct_save( + SPECTRUM_ANALYZER_SETTINGS_PATH, + &settings, + sizeof(settings), + SPECTRUM_ANALYZER_SETTINGS_MAGIC, + SPECTRUM_ANALYZER_SETTINGS_VERSION); +} + SpectrumAnalyzer* spectrum_analyzer_alloc() { SpectrumAnalyzer* instance = malloc(sizeof(SpectrumAnalyzer)); instance->model = malloc(sizeof(SpectrumAnalyzerModel)); @@ -393,6 +464,8 @@ SpectrumAnalyzer* spectrum_analyzer_alloc() { model->max_rssi_dec = 0; model->max_rssi_channel = 0; model->max_rssi = PEAK_THRESHOLD - 1; // Should initializar to < PEAK_THRESHOLD + model->last_peak_rssi = PEAK_THRESHOLD - 1; + model->last_peak_frequency = 0; model->center_freq = DEFAULT_FREQ; model->width = WIDE; @@ -401,6 +474,8 @@ SpectrumAnalyzer* spectrum_analyzer_alloc() { model->vscroll = DEFAULT_VSCROLL; + spectrum_analyzer_load_settings(model); + instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); instance->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); @@ -455,6 +530,8 @@ int32_t spectrum_analyzer_app(void* p) { spectrum_analyzer->model->channel0_frequency, spectrum_analyzer->model->spacing, spectrum_analyzer->model->width); + spectrum_analyzer_worker_set_modulation( + spectrum_analyzer->worker, spectrum_analyzer->model->modulation); FURI_LOG_D("Spectrum", "Main Loop - Wait on queue"); furi_delay_ms(50); @@ -603,6 +680,8 @@ int32_t spectrum_analyzer_app(void* p) { spectrum_analyzer_worker_stop(spectrum_analyzer->worker); + spectrum_analyzer_save_settings(spectrum_analyzer->model); + furi_hal_power_suppress_charge_exit(); spectrum_analyzer_free(spectrum_analyzer); diff --git a/base_pack/spectrum_analyzer/spectrum_analyzer_worker.c b/base_pack/spectrum_analyzer/spectrum_analyzer_worker.c index c425b2b3..7264781f 100644 --- a/base_pack/spectrum_analyzer/spectrum_analyzer_worker.c +++ b/base_pack/spectrum_analyzer/spectrum_analyzer_worker.c @@ -24,6 +24,7 @@ struct SpectrumAnalyzerWorker { float max_rssi; uint8_t max_rssi_dec; uint8_t max_rssi_channel; + uint32_t max_rssi_frequency; uint8_t channel_ss[NUM_CHANNELS]; }; @@ -211,13 +212,10 @@ static int32_t spectrum_analyzer_worker_thread(void* context) { for(uint8_t ch_offset = 0, chunk = 0; ch_offset < CHUNK_SIZE; ++chunk >= NUM_CHUNKS && ++ch_offset && (chunk = 0)) { uint8_t ch = chunk * CHUNK_SIZE + ch_offset; + uint32_t frequency = instance->channel0_frequency + (ch * instance->spacing); - if(subghz_devices_is_frequency_valid( - instance->radio_device, - instance->channel0_frequency + (ch * instance->spacing))) - subghz_devices_set_frequency( - instance->radio_device, - instance->channel0_frequency + (ch * instance->spacing)); + if(subghz_devices_is_frequency_valid(instance->radio_device, frequency)) + subghz_devices_set_frequency(instance->radio_device, frequency); subghz_devices_set_rx(instance->radio_device); furi_delay_ms(3); @@ -233,6 +231,9 @@ static int32_t spectrum_analyzer_worker_thread(void* context) { instance->max_rssi_dec = instance->channel_ss[ch]; instance->max_rssi = (instance->channel_ss[ch] / 2) - 138; instance->max_rssi_channel = ch; + // Capture the frequency this channel was measured at, so a + // concurrent retune cannot mislabel the peak + instance->max_rssi_frequency = frequency; } subghz_devices_idle(instance->radio_device); @@ -247,6 +248,7 @@ static int32_t spectrum_analyzer_worker_thread(void* context) { instance->max_rssi, instance->max_rssi_dec, instance->max_rssi_channel, + instance->max_rssi_frequency, instance->callback_context); } } diff --git a/base_pack/spectrum_analyzer/spectrum_analyzer_worker.h b/base_pack/spectrum_analyzer/spectrum_analyzer_worker.h index 796f532e..8544709b 100644 --- a/base_pack/spectrum_analyzer/spectrum_analyzer_worker.h +++ b/base_pack/spectrum_analyzer/spectrum_analyzer_worker.h @@ -7,6 +7,7 @@ typedef void (*SpectrumAnalyzerWorkerCallback)( float max_rssi, uint8_t max_rssi_dec, uint8_t max_rssi_channel, + uint32_t max_rssi_frequency, void* context); typedef struct SpectrumAnalyzerWorker SpectrumAnalyzerWorker; diff --git a/non_catalog_apps/freq_analyzer_ext/CHANGELOG.md b/non_catalog_apps/freq_analyzer_ext/CHANGELOG.md new file mode 100644 index 00000000..e4abef6c --- /dev/null +++ b/non_catalog_apps/freq_analyzer_ext/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## 1.2 + +- Fix the worker leaving the radio in a degraded state after exit (port of the firmware Frequency Analyzer fix): the active radio (internal or external CC1101) kept the analyzer's near-field AGC/bandwidth registers through sleep; the radio is now reset and the RF path re-parked, leaving the chip in the same state as after boot + +## 1.1 + +- Add support for the 2FSK 12 kHz deviation preset + +## 1.0 + +- Initial release: the firmware's Frequency Analyzer as a standalone app with external CC1101 module support diff --git a/non_catalog_apps/freq_analyzer_ext/application.fam b/non_catalog_apps/freq_analyzer_ext/application.fam index 7668beb4..0baccf68 100644 --- a/non_catalog_apps/freq_analyzer_ext/application.fam +++ b/non_catalog_apps/freq_analyzer_ext/application.fam @@ -13,4 +13,5 @@ App( fap_libs=["assets", "hwdrivers"], fap_icon="icon.png", fap_category="Sub-GHz", + fap_version="1.2", ) diff --git a/non_catalog_apps/freq_analyzer_ext/helpers/subghz_frequency_analyzer_worker.c b/non_catalog_apps/freq_analyzer_ext/helpers/subghz_frequency_analyzer_worker.c index cba8df36..a3bc6d61 100644 --- a/non_catalog_apps/freq_analyzer_ext/helpers/subghz_frequency_analyzer_worker.c +++ b/non_catalog_apps/freq_analyzer_ext/helpers/subghz_frequency_analyzer_worker.c @@ -275,6 +275,11 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // furi_hal_subghz_idle(); // furi_hal_subghz_sleep(); subghz_devices_idle(radio_device); + // Reset drops the analyzer's custom AGC/BW config, which SPWD would + // otherwise retain; SRES reverts the internal radio's IOCFG2, so re-park + // the RF switch - leaves the radio in the same state as after boot + subghz_devices_reset(radio_device); + furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); subghz_devices_sleep(radio_device); return 0;