Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions base_pack/spectrum_analyzer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
7 changes: 6 additions & 1 deletion base_pack/spectrum_analyzer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
- 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.
2 changes: 1 addition & 1 deletion base_pack/spectrum_analyzer/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
)
91 changes: 85 additions & 6 deletions base_pack/spectrum_analyzer/spectrum_analyzer.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,24 @@
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <storage/storage.h>
#include <lib/toolbox/saved_struct.h>
#include "spectrum_analyzer.h"

#include <lib/drivers/cc1101_regs.h>
#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;
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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);
}

Expand All @@ -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(
Expand All @@ -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);
}
Expand Down Expand Up @@ -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));
Expand All @@ -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;
Expand All @@ -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));

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 8 additions & 6 deletions base_pack/spectrum_analyzer/spectrum_analyzer_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
}
Expand Down
1 change: 1 addition & 0 deletions base_pack/spectrum_analyzer/spectrum_analyzer_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions non_catalog_apps/freq_analyzer_ext/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions non_catalog_apps/freq_analyzer_ext/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ App(
fap_libs=["assets", "hwdrivers"],
fap_icon="icon.png",
fap_category="Sub-GHz",
fap_version="1.2",
)
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading