Files
Momentum-Firmware/applications/main/subghz/views/subghz_frequency_analyzer.c
DerSkythe 23903e7e8d feat: Refactor frequency analyzer code for better readability
Refactor to improve structure and readability of the frequency analyzer code. This includes added comments, updating variable names, and reorganizing the frequency list for clarity. The changes also address initialization issues and clean up repetitive code blocks.
2024-07-24 18:12:50 +04:00

659 lines
21 KiB
C

#include "subghz_frequency_analyzer.h"
#include <furi.h>
#include <input/input.h>
#include <notification/notification_messages.h>
#include <gui/elements.h>
#include "../helpers/subghz_frequency_analyzer_worker.h"
#include <assets_icons.h>
#include <float_tools.h>
#define TAG "frequency_analyzer"
#define RSSI_MIN (-97.0f)
#define RSSI_MAX (-60.0f)
#define RSSI_SCALE 2.3f
#define TRIGGER_STEP 1
#define MAX_HISTORY 4
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#endif
static const uint32_t subghz_frequency_list[] = {
/* 300 - 348 */
300000000,
302757000,
303875000,
303900000,
304250000,
307000000,
307500000,
307800000,
309000000,
310000000,
312000000,
312100000,
312200000,
313000000,
313850000,
314000000,
314350000,
314980000,
315000000,
318000000,
330000000,
345000000,
348000000,
350000000,
/* 387 - 464 */
387000000,
390000000,
418000000,
430000000,
430500000,
431000000,
431500000,
433075000, /* LPD433 first */
433220000,
433420000,
433657070,
433889000,
433920000, /* LPD433 mid */
434075000,
434176948,
434190000,
434390000,
434420000,
434620000,
434775000, /* LPD433 last channels */
438900000,
440175000,
464000000,
467750000,
/* 779 - 928 */
779000000,
868350000,
868400000,
868800000,
868950000,
906400000,
915000000,
925000000,
928000000,
};
typedef enum {
SubGhzFrequencyAnalyzerStatusIDLE,
} SubGhzFrequencyAnalyzerStatus;
struct SubGhzFrequencyAnalyzer {
View* view;
SubGhzFrequencyAnalyzerWorker* worker;
SubGhzFrequencyAnalyzerCallback callback;
void* context;
SubGhzTxRx* txrx;
bool locked;
SubGHzFrequencyAnalyzerFeedbackLevel
feedback_level; // 0 - no feedback, 1 - vibro only, 2 - vibro and sound
float rssi_last;
uint8_t selected_index;
uint8_t max_index;
bool show_frame;
};
typedef struct {
uint32_t frequency;
uint32_t frequency_to_save;
float rssi;
uint32_t history_frequency[MAX_HISTORY];
uint8_t history_frequency_rx_count[MAX_HISTORY];
bool signal;
float rssi_last;
float trigger;
SubGHzFrequencyAnalyzerFeedbackLevel feedback_level;
uint8_t selected_index;
uint8_t max_index;
bool show_frame;
bool is_ext_radio;
} SubGhzFrequencyAnalyzerModel;
void subghz_frequency_analyzer_set_callback(
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer,
SubGhzFrequencyAnalyzerCallback callback,
void* context) {
furi_assert(subghz_frequency_analyzer);
furi_assert(callback);
subghz_frequency_analyzer->callback = callback;
subghz_frequency_analyzer->context = context;
}
void subghz_frequency_analyzer_draw_rssi(
Canvas* canvas,
float rssi,
float rssi_last,
float trigger,
uint8_t x,
uint8_t y) {
// Current RSSI
if(!float_is_equal(rssi, 0.f)) {
if(rssi > RSSI_MAX) {
rssi = RSSI_MAX;
}
rssi = (rssi - RSSI_MIN) / RSSI_SCALE;
uint8_t column_number = 0;
for(size_t i = 0; i <= (uint8_t)rssi; i++) {
if((i + 1) % 4) {
column_number++;
canvas_draw_box(canvas, x + 2 * i, (y + 4) - column_number, 2, column_number);
}
}
}
// Last RSSI
if(!float_is_equal(rssi_last, 0.f)) {
if(rssi_last > RSSI_MAX) {
rssi_last = RSSI_MAX;
}
int max_x = (int)((rssi_last - RSSI_MIN) / RSSI_SCALE) * 2;
//if(!(max_x % 8)) max_x -= 2;
int max_h = (int)((rssi_last - RSSI_MIN) / RSSI_SCALE) + 1;
max_h -= (max_h / 4) + 3;
canvas_draw_line(canvas, x + max_x + 1, y - max_h, x + max_x + 1, y + 3);
}
// Trigger cursor
trigger = (trigger - RSSI_MIN) / RSSI_SCALE;
uint8_t tr_x = (uint8_t)((float)x + (2 * trigger));
canvas_draw_dot(canvas, tr_x, y + 4);
canvas_draw_line(canvas, tr_x - 1, y + 5, tr_x + 1, y + 5);
canvas_draw_line(canvas, x, y + 3, x + (RSSI_MAX - RSSI_MIN) * 2 / RSSI_SCALE, y + 3);
}
static void subghz_frequency_analyzer_history_frequency_draw(
Canvas* canvas,
SubGhzFrequencyAnalyzerModel* model) {
char buffer[64] = {0};
const uint8_t x1 = 2;
const uint8_t x2 = 66;
const uint8_t y = 37;
canvas_set_font(canvas, FontSecondary);
uint8_t line = 0;
bool show_frame = model->show_frame && model->max_index > 0;
for(uint8_t i = 0; i < MAX_HISTORY; i++) {
uint8_t current_x;
uint8_t current_y = y + line * 11;
if(i % 2 == 0) {
current_x = x1;
} else {
current_x = x2;
line++;
}
if(model->history_frequency[i]) {
snprintf(
buffer,
sizeof(buffer),
"%03ld.%03ld",
model->history_frequency[i] / 1000000 % 1000,
model->history_frequency[i] / 1000 % 1000);
canvas_draw_str(canvas, current_x, current_y, buffer);
} else {
canvas_draw_str(canvas, current_x, current_y, "---.---");
}
if(model->history_frequency_rx_count[i] > 0) {
snprintf(buffer, sizeof(buffer), "x%d", model->history_frequency_rx_count[i]);
canvas_draw_str(canvas, current_x + 41, current_y, buffer);
} else {
canvas_draw_str(canvas, current_x + 41, current_y, "MHz");
}
if(show_frame && i == model->selected_index) {
elements_frame(canvas, current_x - 2, current_y - 9, 63, 11);
}
}
}
void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) {
char buffer[64] = {0};
// Title
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 0, 7, model->is_ext_radio ? "Ext" : "Int");
canvas_draw_str(canvas, 20, 7, "Frequency Analyzer");
// RSSI
canvas_draw_str(canvas, 33, 62, "RSSI");
subghz_frequency_analyzer_draw_rssi(
canvas, model->rssi, model->rssi_last, model->trigger, 56, 57);
// Last detected frequency
subghz_frequency_analyzer_history_frequency_draw(canvas, model);
// Frequency
canvas_set_font(canvas, FontBigNumbers);
snprintf(
buffer,
sizeof(buffer),
"%03ld.%03ld",
model->frequency / 1000000 % 1000,
model->frequency / 1000 % 1000);
if(model->signal) {
canvas_draw_box(canvas, 4, 10, 121, 19);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(canvas, 8, 26, buffer);
canvas_draw_icon(canvas, 96, 15, &I_MHz_25x11);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
const uint8_t icon_x = 119;
switch(model->feedback_level) {
case SubGHzFrequencyAnalyzerFeedbackLevelAll:
canvas_draw_icon(canvas, icon_x, 1, &I_Volup_8x6);
break;
case SubGHzFrequencyAnalyzerFeedbackLevelVibro:
canvas_draw_icon(canvas, icon_x, 1, &I_Voldwn_6x6);
break;
case SubGHzFrequencyAnalyzerFeedbackLevelMute:
canvas_draw_icon(canvas, icon_x, 1, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 123, 1, 2, 6);
canvas_set_color(canvas, ColorBlack);
break;
}
// Buttons hint
canvas_set_font(canvas, FontSecondary);
elements_button_left(canvas, "T-");
elements_button_right(canvas, "+T");
}
uint32_t subghz_frequency_find_correct(uint32_t input) {
uint32_t prev_freq = 0;
uint32_t result = 0;
uint32_t current;
for(size_t i = 0; i < ARRAY_SIZE(subghz_frequency_list) - 1; i++) {
current = subghz_frequency_list[i];
if(current == 0) {
continue;
}
if(current == input) {
result = current;
break;
}
if(current > input && prev_freq < input) {
if(current - input < input - prev_freq) {
result = current;
} else {
result = prev_freq;
}
break;
}
prev_freq = current;
}
return result;
}
bool subghz_frequency_analyzer_input(InputEvent* event, void* context) {
furi_assert(context);
SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
bool need_redraw = false;
if(event->key == InputKeyBack) {
return need_redraw;
}
bool is_press_or_repeat = (event->type == InputTypePress) || (event->type == InputTypeRepeat);
if(is_press_or_repeat && (event->key == InputKeyLeft || event->key == InputKeyRight)) {
// Trigger setup
float trigger_level = subghz_frequency_analyzer_worker_get_trigger_level(instance->worker);
if(event->key == InputKeyLeft) {
trigger_level -= TRIGGER_STEP;
if(trigger_level < RSSI_MIN) {
trigger_level = RSSI_MIN;
}
} else {
trigger_level += TRIGGER_STEP;
if(trigger_level > RSSI_MAX) {
trigger_level = RSSI_MAX;
}
}
subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, trigger_level);
FURI_LOG_D(TAG, "trigger = %.1f", (double)trigger_level);
need_redraw = true;
} else if(event->type == InputTypePress && event->key == InputKeyUp) {
if(instance->feedback_level == SubGHzFrequencyAnalyzerFeedbackLevelAll) {
instance->feedback_level = SubGHzFrequencyAnalyzerFeedbackLevelMute;
} else {
instance->feedback_level--;
}
need_redraw = true;
} else if(is_press_or_repeat && event->key == InputKeyDown) {
instance->show_frame = instance->max_index > 0;
if(instance->show_frame) {
instance->selected_index = (instance->selected_index + 1) % instance->max_index;
need_redraw = true;
}
} else if(event->key == InputKeyOk) {
need_redraw = true;
bool updated = false;
uint32_t frequency_to_save;
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{
frequency_to_save = model->frequency_to_save;
uint32_t prev_freq_to_save = model->frequency_to_save;
uint32_t frequency_candidate = 0;
if(model->show_frame && !model->signal) {
frequency_candidate = model->history_frequency[model->selected_index];
} else if(
(model->show_frame && model->signal) ||
(!model->show_frame && model->signal)) {
frequency_candidate = subghz_frequency_find_correct(model->frequency);
}
frequency_candidate = frequency_candidate == 0 ||
!subghz_txrx_radio_device_is_frequency_valid(
instance->txrx, frequency_candidate) ||
prev_freq_to_save == frequency_candidate ?
0 :
subghz_frequency_find_correct(frequency_candidate);
if(frequency_candidate > 0 && frequency_candidate != model->frequency_to_save) {
model->frequency_to_save = frequency_candidate;
updated = true;
}
},
true);
if(updated) {
instance->callback(SubGhzCustomEventViewFreqAnalOkShort, instance->context);
}
// First the device receives short, then when user release button we get long
if(event->type == InputTypeLong && frequency_to_save > 0) {
// Stop worker
if(subghz_frequency_analyzer_worker_is_running(instance->worker)) {
subghz_frequency_analyzer_worker_stop(instance->worker);
}
instance->callback(SubGhzCustomEventViewFreqAnalOkLong, instance->context);
}
}
if(need_redraw) {
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{
model->rssi_last = instance->rssi_last;
model->trigger =
subghz_frequency_analyzer_worker_get_trigger_level(instance->worker);
model->feedback_level = instance->feedback_level;
model->max_index = instance->max_index;
model->show_frame = instance->show_frame;
model->selected_index = instance->selected_index;
},
true);
}
return true;
}
uint32_t round_int(uint32_t value, uint8_t n) {
// Round value
uint8_t on = n;
while(n--) {
uint8_t i = value % 10;
value /= 10;
if(i >= 5) value++;
}
while(on--)
value *= 10;
return value;
}
void subghz_frequency_analyzer_pair_callback(
void* context,
uint32_t frequency,
float rssi,
bool signal) {
SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
if(float_is_equal(rssi, 0.f) && instance->locked) {
if(instance->callback) {
instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context);
}
//update history
instance->show_frame = true;
uint8_t max_index = instance->max_index;
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{
bool in_array = false;
uint32_t normal_frequency = subghz_frequency_find_correct(model->frequency);
for(size_t i = 0; i < MAX_HISTORY; i++) {
if(model->history_frequency[i] == normal_frequency) {
in_array = true;
if(model->history_frequency[i] > 0) {
if(model->history_frequency_rx_count[i] == 0) {
model->history_frequency_rx_count[i]++;
}
model->history_frequency_rx_count[i]++;
}
if(i > 0) {
size_t offset = 0;
uint8_t temp_rx_count = model->history_frequency_rx_count[i];
for(size_t j = MAX_HISTORY - 1; j > 0; j--) {
if(j == i) {
offset++;
}
model->history_frequency[j] = model->history_frequency[j - offset];
model->history_frequency_rx_count[j] =
model->history_frequency_rx_count[j - offset];
}
model->history_frequency[0] = normal_frequency;
model->history_frequency_rx_count[0] = temp_rx_count;
}
break;
}
}
if(!in_array) {
model->history_frequency[3] = model->history_frequency[2];
model->history_frequency[2] = model->history_frequency[1];
model->history_frequency[1] = model->history_frequency[0];
model->history_frequency[0] = normal_frequency;
model->history_frequency_rx_count[3] = model->history_frequency_rx_count[2];
model->history_frequency_rx_count[2] = model->history_frequency_rx_count[1];
model->history_frequency_rx_count[1] = model->history_frequency_rx_count[0];
model->history_frequency_rx_count[0] = 0;
}
if(max_index < MAX_HISTORY) {
for(size_t i = 0; i < MAX_HISTORY; i++) {
if(model->history_frequency[i] > 0) {
max_index = i + 1;
}
}
}
},
false);
instance->max_index = max_index;
} else if(!float_is_equal(rssi, 0.f) && !instance->locked) {
// There is some signal
FURI_LOG_I(TAG, "rssi = %.2f, frequency = %ld Hz", (double)rssi, frequency);
frequency = round_int(frequency, 3); // Round 299999990Hz to 300000000Hz
// Triggered!
instance->rssi_last = rssi;
if(instance->callback) {
instance->callback(SubGhzCustomEventSceneAnalyzerLock, instance->context);
}
}
// Update values
if(rssi >= instance->rssi_last && frequency != 0) {
instance->rssi_last = rssi;
}
instance->locked = !float_is_equal(rssi, 0.f);
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{
model->rssi = rssi;
model->rssi_last = instance->rssi_last;
model->frequency = frequency;
model->signal = signal;
model->trigger = subghz_frequency_analyzer_worker_get_trigger_level(instance->worker);
model->feedback_level = instance->feedback_level;
model->max_index = instance->max_index;
model->show_frame = instance->show_frame;
model->selected_index = instance->selected_index;
},
true);
}
void subghz_frequency_analyzer_enter(void* context) {
furi_assert(context);
SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
//Start worker
instance->worker = subghz_frequency_analyzer_worker_alloc(instance->context);
subghz_frequency_analyzer_worker_set_pair_callback(
instance->worker,
(SubGhzFrequencyAnalyzerWorkerPairCallback)subghz_frequency_analyzer_pair_callback,
instance);
subghz_frequency_analyzer_worker_start(instance->worker, instance->txrx);
instance->rssi_last = 0;
instance->selected_index = 0;
instance->max_index = 0;
instance->show_frame = false;
//subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, RSSI_MIN);
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{
model->selected_index = 0;
model->max_index = 0;
model->show_frame = false;
model->rssi = 0;
model->rssi_last = 0;
model->frequency = 0;
model->history_frequency[3] = 0;
model->history_frequency[2] = 0;
model->history_frequency[1] = 0;
model->history_frequency[0] = 0;
model->history_frequency_rx_count[3] = 0;
model->history_frequency_rx_count[2] = 0;
model->history_frequency_rx_count[1] = 0;
model->history_frequency_rx_count[0] = 0;
model->frequency_to_save = 0;
model->trigger = RSSI_MIN;
model->is_ext_radio =
(subghz_txrx_radio_device_get(instance->txrx) != SubGhzRadioDeviceTypeInternal);
},
true);
}
void subghz_frequency_analyzer_exit(void* context) {
furi_assert(context);
SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context;
// Stop worker
if(subghz_frequency_analyzer_worker_is_running(instance->worker)) {
subghz_frequency_analyzer_worker_stop(instance->worker);
}
subghz_frequency_analyzer_worker_free(instance->worker);
furi_record_close(RECORD_NOTIFICATION);
}
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc(SubGhzTxRx* txrx) {
SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer));
instance->feedback_level = SubGHzFrequencyAnalyzerFeedbackLevelMute;
// View allocation and configuration
instance->view = view_alloc();
view_allocate_model(
instance->view, ViewModelTypeLocking, sizeof(SubGhzFrequencyAnalyzerModel));
view_set_context(instance->view, instance);
view_set_draw_callback(instance->view, (ViewDrawCallback)subghz_frequency_analyzer_draw);
view_set_input_callback(instance->view, subghz_frequency_analyzer_input);
view_set_enter_callback(instance->view, subghz_frequency_analyzer_enter);
view_set_exit_callback(instance->view, subghz_frequency_analyzer_exit);
instance->txrx = txrx;
return instance;
}
void subghz_frequency_analyzer_free(SubGhzFrequencyAnalyzer* instance) {
furi_assert(instance);
view_free(instance->view);
free(instance);
}
View* subghz_frequency_analyzer_get_view(SubGhzFrequencyAnalyzer* instance) {
furi_assert(instance);
return instance->view;
}
uint32_t subghz_frequency_analyzer_get_frequency_to_save(SubGhzFrequencyAnalyzer* instance) {
furi_assert(instance);
uint32_t frequency;
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{ frequency = model->frequency_to_save; },
false);
return frequency;
}
SubGHzFrequencyAnalyzerFeedbackLevel subghz_frequency_analyzer_feedback_level(
SubGhzFrequencyAnalyzer* instance,
SubGHzFrequencyAnalyzerFeedbackLevel level,
bool update) {
furi_assert(instance);
if(update) {
instance->feedback_level = level;
with_view_model(
instance->view,
SubGhzFrequencyAnalyzerModel * model,
{ model->feedback_level = instance->feedback_level; },
true);
}
return instance->feedback_level;
}
float subghz_frequency_analyzer_get_trigger_level(SubGhzFrequencyAnalyzer* instance) {
furi_assert(instance);
return subghz_frequency_analyzer_worker_get_trigger_level(instance->worker);
}