diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h index 3b93f60ad..f1f5d21b8 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -71,4 +71,4 @@ void subghz_frequency_analyzer_worker_set_trigger_level( * @param instance SubGhzFrequencyAnalyzerWorker instance * @return RSSI trigger level */ -float subghz_frequency_analyzer_worker_get_trigger_level(SubGhzFrequencyAnalyzerWorker* instance); \ No newline at end of file +float subghz_frequency_analyzer_worker_get_trigger_level(SubGhzFrequencyAnalyzerWorker* instance); diff --git a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c index f067f9859..7e3965ac9 100644 --- a/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c +++ b/applications/main/subghz/scenes/subghz_scene_frequency_analyzer.c @@ -17,8 +17,18 @@ void subghz_scene_frequency_analyzer_on_enter(void* context) { } bool subghz_scene_frequency_analyzer_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); + SubGhz* subghz = context; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzCustomEventSceneAnalyzerLock) { + notification_message(subghz->notifications, &sequence_set_green_255); + notification_message(subghz->notifications, &sequence_set_vibro_on); + return true; + } else if(event.event == SubGhzCustomEventSceneAnalyzerUnlock) { + notification_message(subghz->notifications, &sequence_reset_rgb); + notification_message(subghz->notifications, &sequence_reset_vibro); + return true; + } + } return false; } diff --git a/applications/subghz/views/subghz_frequency_analyzer.c b/applications/subghz/views/subghz_frequency_analyzer.c new file mode 100644 index 000000000..b9360ea01 --- /dev/null +++ b/applications/subghz/views/subghz_frequency_analyzer.c @@ -0,0 +1,314 @@ +#include "subghz_frequency_analyzer.h" +#include "../subghz_i.h" + +#include +#include +#include +#include +#include +#include +#include "../helpers/subghz_frequency_analyzer_worker.h" + +#include + +#define TAG "frequency_analyzer" + +#define RSSI_MIN -97 +#define RSSI_MAX -60 +#define RSSI_SCALE 2 +#define TRIGGER_STEP 1 + +typedef enum { + SubGhzFrequencyAnalyzerStatusIDLE, +} SubGhzFrequencyAnalyzerStatus; + +struct SubGhzFrequencyAnalyzer { + View* view; + SubGhzFrequencyAnalyzerWorker* worker; + SubGhzFrequencyAnalyzerCallback callback; + void* context; + bool locked; + float rssi_last; + uint32_t frequency_last; + uint32_t frequency_last_vis; +}; + +typedef struct { + uint32_t frequency; + uint32_t frequency_last; + float rssi; + float rssi_last; + float trigger; +} 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(rssi) { + 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 - column_number, 2, 4 + column_number); + } + } + } + + // Last RSSI + if(rssi_last) { + 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) + 4; + 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 = 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); +} + +void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { + char buffer[64]; + + // Title + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); + + // RSSI + canvas_draw_str(canvas, 33, 62, "RSSI"); + subghz_frequency_analyzer_draw_rssi( + canvas, model->rssi, model->rssi_last, model->trigger, 57, 58); + + // Frequency + canvas_set_font(canvas, FontBigNumbers); + snprintf( + buffer, + sizeof(buffer), + "%03ld.%03ld", + model->frequency / 1000000 % 1000, + model->frequency / 1000 % 1000); + canvas_draw_str(canvas, 8, 30, buffer); + canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); + + // Last detected frequency + canvas_set_font(canvas, FontSecondary); + if(model->frequency_last) { + snprintf( + buffer, + sizeof(buffer), + "Last: %03ld.%03ld MHz", + model->frequency_last / 1000000 % 1000, + model->frequency_last / 1000 % 1000); + } else { + strcpy(buffer, "Last: ---.--- MHz"); + } + canvas_draw_str(canvas, 9, 42, buffer); + + // Buttons hint + elements_button_left(canvas, "T-"); + elements_button_right(canvas, "T+"); +} + +bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { + furi_assert(context); + SubGhzFrequencyAnalyzer* instance = context; + + bool need_redraw = false; + + if(event->key == InputKeyBack) return false; + + if(((event->type == InputTypePress) || (event->type == InputTypeRepeat)) && + ((event->key == InputKeyLeft) || (event->key == InputKeyRight))) { + // Trigger setup + float trigger_level = subghz_frequency_analyzer_worker_get_trigger_level(instance->worker); + switch(event->key) { + case InputKeyLeft: + trigger_level -= TRIGGER_STEP; + if(trigger_level < RSSI_MIN) trigger_level = RSSI_MIN; + break; + default: + case InputKeyRight: + trigger_level += TRIGGER_STEP; + if(trigger_level > RSSI_MAX) trigger_level = RSSI_MAX; + break; + } + subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, trigger_level); + FURI_LOG_I(TAG, "trigger = %.1f", (double)trigger_level); + need_redraw = true; + } + + if(need_redraw) { + SubGhzFrequencyAnalyzer* instance = context; + with_view_model( + instance->view, (SubGhzFrequencyAnalyzerModel * model) { + model->rssi_last = instance->rssi_last; + model->frequency_last = instance->frequency_last; + model->trigger = + subghz_frequency_analyzer_worker_get_trigger_level(instance->worker); + return 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) { + furi_assert(context); + SubGhzFrequencyAnalyzer* instance = context; + + if((rssi == 0.f) && (instance->locked)) { + if(instance->callback) { + instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context); + } + instance->frequency_last_vis = instance->frequency_last; + } else if((rssi != 0.f) && (!instance->locked)) { + if(instance->callback) { + instance->callback(SubGhzCustomEventSceneAnalyzerLock, instance->context); + } + } + + if((rssi != 0.f) && (frequency != 0)) { + // Threre is some signal + FURI_LOG_I(TAG, "rssi = %.2f, frequency = %d Hz", (double)rssi, frequency); + frequency = round_int(frequency, 3); // Round 299999990Hz to 300000000Hz + if(!instance->locked) { + // Triggered! + instance->rssi_last = rssi; + FURI_LOG_D(TAG, "triggered"); + } + // Update values + if(rssi >= instance->rssi_last) { + instance->rssi_last = rssi; + instance->frequency_last = frequency; + } + } + + instance->locked = (rssi != 0.f); + with_view_model( + instance->view, (SubGhzFrequencyAnalyzerModel * model) { + model->rssi = rssi; + model->rssi_last = instance->rssi_last; + model->frequency = frequency; + model->frequency_last = instance->frequency_last_vis; + model->trigger = subghz_frequency_analyzer_worker_get_trigger_level(instance->worker); + return true; + }); +} + +void subghz_frequency_analyzer_enter(void* context) { + furi_assert(context); + SubGhzFrequencyAnalyzer* instance = 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->rssi_last = 0; + instance->frequency_last = 0; + instance->frequency_last_vis = 0; + subghz_frequency_analyzer_worker_set_trigger_level(instance->worker, RSSI_MIN); + + with_view_model( + instance->view, (SubGhzFrequencyAnalyzerModel * model) { + model->rssi = 0; + model->rssi_last = 0; + model->frequency = 0; + model->frequency_last = 0; + model->trigger = RSSI_MIN; + return true; + }); +} + +void subghz_frequency_analyzer_exit(void* context) { + furi_assert(context); + SubGhzFrequencyAnalyzer* instance = 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); + + with_view_model( + instance->view, (SubGhzFrequencyAnalyzerModel * model) { + model->rssi = 0; + return true; + }); +} + +SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { + SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer)); + furi_assert(instance); + + // 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); + + with_view_model( + instance->view, (SubGhzFrequencyAnalyzerModel * model) { + model->rssi = 0; + return true; + }); + + 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; +}