소스 검색

subghz: add analyzer frequency logs (#1914)

* subghz: add analyzer frequency logs
* SubGhz: switch to change on short press
* SubGhz: use full RSSI bar for history view

Co-authored-by: あく <alleteam@gmail.com>
Alexandre Díaz 3 년 전
부모
커밋
5882075114

+ 27 - 0
applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.c

@@ -0,0 +1,27 @@
+#include "subghz_frequency_analyzer_log_item_array.h"
+
+const char*
+    subghz_frequency_analyzer_log_get_order_name(SubGhzFrequencyAnalyzerLogOrderBy order_by) {
+    if(order_by == SubGhzFrequencyAnalyzerLogOrderBySeqAsc) {
+        return "Seq. A";
+    }
+    if(order_by == SubGhzFrequencyAnalyzerLogOrderByCountDesc) {
+        return "Count D";
+    }
+    if(order_by == SubGhzFrequencyAnalyzerLogOrderByCountAsc) {
+        return "Count A";
+    }
+    if(order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIDesc) {
+        return "RSSI D";
+    }
+    if(order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIAsc) {
+        return "RSSI A";
+    }
+    if(order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc) {
+        return "Freq. D";
+    }
+    if(order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) {
+        return "Freq. A";
+    }
+    return "Seq. D";
+}

+ 73 - 0
applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h

@@ -0,0 +1,73 @@
+#pragma once
+
+#include <m-tuple.h>
+#include <m-array.h>
+#include <m-algo.h>
+#include <m-funcobj.h>
+
+typedef enum {
+    SubGhzFrequencyAnalyzerLogOrderBySeqDesc,
+    SubGhzFrequencyAnalyzerLogOrderBySeqAsc,
+    SubGhzFrequencyAnalyzerLogOrderByCountDesc,
+    SubGhzFrequencyAnalyzerLogOrderByCountAsc,
+    SubGhzFrequencyAnalyzerLogOrderByRSSIDesc,
+    SubGhzFrequencyAnalyzerLogOrderByRSSIAsc,
+    SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc,
+    SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc,
+} SubGhzFrequencyAnalyzerLogOrderBy;
+
+const char*
+    subghz_frequency_analyzer_log_get_order_name(SubGhzFrequencyAnalyzerLogOrderBy order_by);
+
+TUPLE_DEF2(
+    SubGhzFrequencyAnalyzerLogItem,
+    (seq, uint8_t),
+    (frequency, uint32_t),
+    (count, uint8_t),
+    (rssi_max, uint8_t))
+/* Register globaly the oplist */
+#define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \
+    TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST)
+
+/* Define the array, register the oplist and define further algorithms on it */
+ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t)
+#define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_t() \
+    ARRAY_OPLIST(SubGhzFrequencyAnalyzerLogItemArray, M_OPL_SubGhzFrequencyAnalyzerLogItem_t())
+ALGO_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItemArray_t)
+
+FUNC_OBJ_INS_DEF(
+    SubGhzFrequencyAnalyzerLogItemArray_compare_by /* name of the instance */,
+    SubGhzFrequencyAnalyzerLogItemArray_cmp_obj /* name of the interface */,
+    (a,
+     b) /* name of the input parameters of the function like object. The type are inherited from the interface. */
+    ,
+    {
+        /* code of the function object */
+        if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) {
+            return a->frequency < b->frequency ? -1 : a->frequency > b->frequency;
+        }
+        if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc) {
+            return a->frequency > b->frequency ? -1 : a->frequency < b->frequency;
+        }
+        if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIAsc) {
+            return a->rssi_max < b->rssi_max ? -1 : a->rssi_max > b->rssi_max;
+        }
+        if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIDesc) {
+            return a->rssi_max > b->rssi_max ? -1 : a->rssi_max < b->rssi_max;
+        }
+        if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByCountAsc) {
+            return a->count < b->count ? -1 : a->count > b->count;
+        }
+        if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByCountDesc) {
+            return a->count > b->count ? -1 : a->count < b->count;
+        }
+        if(self->order_by == SubGhzFrequencyAnalyzerLogOrderBySeqAsc) {
+            return a->seq < b->seq ? -1 : a->seq > b->seq;
+        }
+
+        return a->seq > b->seq ? -1 : a->seq < b->seq;
+    },
+    /* Additional fields stored in the function object */
+    (order_by, SubGhzFrequencyAnalyzerLogOrderBy))
+#define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_compare_by_t() \
+    FUNC_OBJ_INS_OPLIST(SubGhzFrequencyAnalyzerLogItemArray_compare_by, M_DEFAULT_OPLIST)

+ 255 - 25
applications/main/subghz/views/subghz_frequency_analyzer.c

@@ -5,30 +5,53 @@
 #include <furi.h>
 #include <furi_hal.h>
 #include <input/input.h>
+#include <gui/elements.h>
 #include <notification/notification_messages.h>
 #include "../helpers/subghz_frequency_analyzer_worker.h"
+#include "../helpers/subghz_frequency_analyzer_log_item_array.h"
 
 #include <assets_icons.h>
 
+#define LOG_FREQUENCY_MAX_ITEMS 60 // uint8_t (limited by 'seq' of SubGhzFrequencyAnalyzerLogItem)
+#define RSSI_OFFSET 74
+#define RSSI_MAX 53 // 127 - RSSI_OFFSET
+
+#define SNPRINTF_FREQUENCY(buff, freq) \
+    snprintf(buff, sizeof(buff), "%03ld.%03ld", freq / 1000000 % 1000, freq / 1000 % 1000);
+
 typedef enum {
     SubGhzFrequencyAnalyzerStatusIDLE,
 } SubGhzFrequencyAnalyzerStatus;
 
+typedef enum {
+    SubGhzFrequencyAnalyzerFragmentBottomTypeMain,
+    SubGhzFrequencyAnalyzerFragmentBottomTypeLog,
+} SubGhzFrequencyAnalyzerFragmentBottomType;
+
 struct SubGhzFrequencyAnalyzer {
     View* view;
     SubGhzFrequencyAnalyzerWorker* worker;
     SubGhzFrequencyAnalyzerCallback callback;
     void* context;
     bool locked;
+    uint32_t last_frequency;
 };
 
 typedef struct {
     uint32_t frequency;
-    float rssi;
+    uint8_t rssi;
     uint32_t history_frequency[3];
     bool signal;
+    SubGhzFrequencyAnalyzerLogItemArray_t log_frequency;
+    SubGhzFrequencyAnalyzerFragmentBottomType fragment_bottom_type;
+    SubGhzFrequencyAnalyzerLogOrderBy log_frequency_order_by;
+    uint8_t log_frequency_scroll_offset;
 } SubGhzFrequencyAnalyzerModel;
 
+static inline uint8_t rssi_sanitize(float rssi) {
+    return (rssi * -1.0f) - RSSI_OFFSET;
+}
+
 void subghz_frequency_analyzer_set_callback(
     SubGhzFrequencyAnalyzer* subghz_frequency_analyzer,
     SubGhzFrequencyAnalyzerCallback callback,
@@ -39,13 +62,11 @@ void subghz_frequency_analyzer_set_callback(
     subghz_frequency_analyzer->context = context;
 }
 
-void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) {
-    uint8_t x = 20;
-    uint8_t y = 64;
+void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) {
     uint8_t column_number = 0;
     if(rssi) {
-        rssi = (rssi + 90) / 3;
-        for(size_t i = 1; i < (uint8_t)rssi; i++) {
+        rssi = rssi / 3;
+        for(uint8_t i = 1; i < rssi; i++) {
             if(i > 20) break;
             if(i % 4) {
                 column_number++;
@@ -55,6 +76,54 @@ void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) {
     }
 }
 
+static void subghz_frequency_analyzer_log_frequency_draw(
+    Canvas* canvas,
+    SubGhzFrequencyAnalyzerModel* model) {
+    char buffer[64];
+    const uint8_t offset_x = 0;
+    const uint8_t offset_y = 43;
+    canvas_set_font(canvas, FontKeyboard);
+
+    const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency);
+    if(items_count == 0) {
+        canvas_draw_rframe(canvas, offset_x + 27u, offset_y - 3u, 73u, 16u, 5u);
+        canvas_draw_str_aligned(
+            canvas, offset_x + 64u, offset_y + 8u, AlignCenter, AlignBottom, "No records");
+        return;
+    } else if(items_count > 3) {
+        elements_scrollbar_pos(
+            canvas,
+            offset_x + 127,
+            offset_y - 8,
+            29,
+            model->log_frequency_scroll_offset,
+            items_count - 2);
+    }
+
+    SubGhzFrequencyAnalyzerLogItem_t* log_frequency_item;
+    for(uint8_t i = 0; i < 3; ++i) {
+        const uint8_t item_pos = model->log_frequency_scroll_offset + i;
+        if(item_pos >= items_count) {
+            break;
+        }
+        log_frequency_item =
+            SubGhzFrequencyAnalyzerLogItemArray_get(model->log_frequency, item_pos);
+        // Frequency
+        SNPRINTF_FREQUENCY(buffer, (*log_frequency_item)->frequency)
+        canvas_draw_str(canvas, offset_x, offset_y + i * 10, buffer);
+
+        // Count
+        snprintf(buffer, sizeof(buffer), "%3d", (*log_frequency_item)->count);
+        canvas_draw_str(canvas, offset_x + 48, offset_y + i * 10, buffer);
+
+        // Max RSSI
+        subghz_frequency_analyzer_draw_rssi(
+            canvas, (*log_frequency_item)->rssi_max, offset_x + 69, (offset_y + i * 10));
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+}
+
 static void subghz_frequency_analyzer_history_frequency_draw(
     Canvas* canvas,
     SubGhzFrequencyAnalyzerModel* model) {
@@ -65,12 +134,7 @@ static void subghz_frequency_analyzer_history_frequency_draw(
     canvas_set_font(canvas, FontKeyboard);
     for(uint8_t i = 0; i < 3; i++) {
         if(model->history_frequency[i]) {
-            snprintf(
-                buffer,
-                sizeof(buffer),
-                "%03ld.%03ld",
-                model->history_frequency[i] / 1000000 % 1000,
-                model->history_frequency[i] / 1000 % 1000);
+            SNPRINTF_FREQUENCY(buffer, model->history_frequency[i])
             canvas_draw_str(canvas, x, y + i * 10, buffer);
         } else {
             canvas_draw_str(canvas, x, y + i * 10, "---.---");
@@ -81,18 +145,34 @@ static void subghz_frequency_analyzer_history_frequency_draw(
 }
 
 void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) {
+    furi_assert(canvas);
+    furi_assert(model);
     char buffer[64];
 
     canvas_set_color(canvas, ColorBlack);
     canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str(canvas, 20, 8, "Frequency Analyzer");
 
-    canvas_draw_str(canvas, 0, 64, "RSSI");
-    subghz_frequency_analyzer_draw_rssi(canvas, model->rssi);
+    if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) {
+        const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency);
+        const char* log_order_by_name =
+            subghz_frequency_analyzer_log_get_order_name(model->log_frequency_order_by);
+        if(items_count < LOG_FREQUENCY_MAX_ITEMS) {
+            snprintf(buffer, sizeof(buffer), "Frequency Analyzer [%s]", log_order_by_name);
+            canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignBottom, buffer);
+        } else {
+            snprintf(buffer, sizeof(buffer), "The log is full! [%s]", log_order_by_name);
+            canvas_draw_str(canvas, 2, 8, buffer);
+        }
+        subghz_frequency_analyzer_log_frequency_draw(canvas, model);
+    } else {
+        canvas_draw_str(canvas, 20, 8, "Frequency Analyzer");
+        canvas_draw_str(canvas, 0, 64, "RSSI");
+        subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20u, 64u);
 
-    subghz_frequency_analyzer_history_frequency_draw(canvas, model);
+        subghz_frequency_analyzer_history_frequency_draw(canvas, model);
+    }
 
-    //Frequency
+    // Frequency
     canvas_set_font(canvas, FontBigNumbers);
     snprintf(
         buffer,
@@ -103,23 +183,151 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel
     if(model->signal) {
         canvas_draw_box(canvas, 4, 12, 121, 22);
         canvas_set_color(canvas, ColorWhite);
-    } else {
     }
-
     canvas_draw_str(canvas, 8, 30, buffer);
     canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11);
 }
 
+static void subghz_frequency_analyzer_log_frequency_sort(SubGhzFrequencyAnalyzerModel* model) {
+    furi_assert(model);
+    M_LET((cmp, model->log_frequency_order_by), SubGhzFrequencyAnalyzerLogItemArray_compare_by_t)
+    SubGhzFrequencyAnalyzerLogItemArray_sort_fo(
+        model->log_frequency, SubGhzFrequencyAnalyzerLogItemArray_compare_by_as_interface(cmp));
+}
+
 bool subghz_frequency_analyzer_input(InputEvent* event, void* context) {
     furi_assert(context);
+    SubGhzFrequencyAnalyzer* instance = context;
 
     if(event->key == InputKeyBack) {
         return false;
     }
 
+    if((event->type == InputTypeShort) &&
+       ((event->key == InputKeyLeft) || (event->key == InputKeyRight))) {
+        with_view_model(
+            instance->view,
+            SubGhzFrequencyAnalyzerModel * model,
+            {
+                if(event->key == InputKeyLeft) {
+                    if(model->fragment_bottom_type == 0) {
+                        model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeLog;
+                    } else {
+                        --model->fragment_bottom_type;
+                    }
+                } else if(event->key == InputKeyRight) {
+                    if(model->fragment_bottom_type ==
+                       SubGhzFrequencyAnalyzerFragmentBottomTypeLog) {
+                        model->fragment_bottom_type = 0;
+                    } else {
+                        ++model->fragment_bottom_type;
+                    }
+                }
+            },
+            true);
+    } else if((event->type == InputTypeShort) && (event->key == InputKeyOk)) {
+        with_view_model(
+            instance->view,
+            SubGhzFrequencyAnalyzerModel * model,
+            {
+                if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) {
+                    ++model->log_frequency_order_by;
+                    if(model->log_frequency_order_by >
+                       SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) {
+                        model->log_frequency_order_by = 0;
+                    }
+                    subghz_frequency_analyzer_log_frequency_sort(model);
+                }
+            },
+            true);
+    } else if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
+        with_view_model(
+            instance->view,
+            SubGhzFrequencyAnalyzerModel * model,
+            {
+                if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) {
+                    if(event->key == InputKeyUp) {
+                        if(model->log_frequency_scroll_offset > 0) {
+                            --model->log_frequency_scroll_offset;
+                        }
+                    } else if(event->key == InputKeyDown) {
+                        const size_t items_count =
+                            SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency);
+                        if((model->log_frequency_scroll_offset + 3u) < items_count) {
+                            ++model->log_frequency_scroll_offset;
+                        }
+                    }
+                }
+            },
+            true);
+    }
+
     return true;
 }
 
+static void subghz_frequency_analyzer_log_frequency_search_it(
+    SubGhzFrequencyAnalyzerLogItemArray_it_t* itref,
+    SubGhzFrequencyAnalyzerLogItemArray_t* log_frequency,
+    uint32_t frequency) {
+    furi_assert(log_frequency);
+
+    SubGhzFrequencyAnalyzerLogItemArray_it(*itref, *log_frequency);
+    SubGhzFrequencyAnalyzerLogItem_t* item;
+    while(!SubGhzFrequencyAnalyzerLogItemArray_end_p(*itref)) {
+        item = SubGhzFrequencyAnalyzerLogItemArray_ref(*itref);
+        if((*item)->frequency == frequency) {
+            break;
+        }
+        SubGhzFrequencyAnalyzerLogItemArray_next(*itref);
+    }
+}
+
+static bool subghz_frequency_analyzer_log_frequency_insert(SubGhzFrequencyAnalyzerModel* model) {
+    furi_assert(model);
+    const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency);
+    if(items_count < LOG_FREQUENCY_MAX_ITEMS) {
+        SubGhzFrequencyAnalyzerLogItem_t* item =
+            SubGhzFrequencyAnalyzerLogItemArray_push_new(model->log_frequency);
+        if(item == NULL) {
+            return false;
+        }
+        (*item)->frequency = model->frequency;
+        (*item)->count = 1u;
+        (*item)->rssi_max = model->rssi;
+        (*item)->seq = items_count;
+        return true;
+    }
+    return false;
+}
+
+static void subghz_frequency_analyzer_log_frequency_update(
+    SubGhzFrequencyAnalyzerModel* model,
+    bool need_insert) {
+    furi_assert(model);
+    if(!model->frequency) {
+        return;
+    }
+
+    SubGhzFrequencyAnalyzerLogItemArray_it_t it;
+    subghz_frequency_analyzer_log_frequency_search_it(
+        &it, &model->log_frequency, model->frequency);
+    if(!SubGhzFrequencyAnalyzerLogItemArray_end_p(it)) {
+        SubGhzFrequencyAnalyzerLogItem_t* item = SubGhzFrequencyAnalyzerLogItemArray_ref(it);
+        if((*item)->rssi_max < model->rssi) {
+            (*item)->rssi_max = model->rssi;
+        }
+
+        if(need_insert && (*item)->count < UINT8_MAX) {
+            ++(*item)->count;
+            subghz_frequency_analyzer_log_frequency_sort(model);
+        }
+    } else if(need_insert) {
+        if(subghz_frequency_analyzer_log_frequency_insert(model)) {
+            subghz_frequency_analyzer_log_frequency_sort(model);
+        }
+    }
+}
+
 void subghz_frequency_analyzer_pair_callback(
     void* context,
     uint32_t frequency,
@@ -130,6 +338,7 @@ void subghz_frequency_analyzer_pair_callback(
         if(instance->callback) {
             instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context);
         }
+        instance->last_frequency = 0;
         //update history
         with_view_model(
             instance->view,
@@ -151,9 +360,14 @@ void subghz_frequency_analyzer_pair_callback(
         instance->view,
         SubGhzFrequencyAnalyzerModel * model,
         {
-            model->rssi = rssi;
+            model->rssi = rssi_sanitize(rssi);
             model->frequency = frequency;
             model->signal = signal;
+            if(frequency) {
+                subghz_frequency_analyzer_log_frequency_update(
+                    model, frequency != instance->last_frequency);
+                instance->last_frequency = frequency;
+            }
         },
         true);
 }
@@ -176,11 +390,14 @@ void subghz_frequency_analyzer_enter(void* context) {
         instance->view,
         SubGhzFrequencyAnalyzerModel * model,
         {
-            model->rssi = 0;
+            model->rssi = 0u;
             model->frequency = 0;
-            model->history_frequency[2] = 0;
-            model->history_frequency[1] = 0;
-            model->history_frequency[0] = 0;
+            model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain;
+            model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc;
+            model->log_frequency_scroll_offset = 0u;
+            model->history_frequency[0] = model->history_frequency[1] =
+                model->history_frequency[2] = 0u;
+            SubGhzFrequencyAnalyzerLogItemArray_init(model->log_frequency);
         },
         true);
 }
@@ -196,13 +413,26 @@ void subghz_frequency_analyzer_exit(void* context) {
     subghz_frequency_analyzer_worker_free(instance->worker);
 
     with_view_model(
-        instance->view, SubGhzFrequencyAnalyzerModel * model, { model->rssi = 0; }, true);
+        instance->view,
+        SubGhzFrequencyAnalyzerModel * model,
+        {
+            model->rssi = 0u;
+            model->frequency = 0;
+            model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain;
+            model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc;
+            model->log_frequency_scroll_offset = 0u;
+            model->history_frequency[0] = model->history_frequency[1] =
+                model->history_frequency[2] = 0u;
+            SubGhzFrequencyAnalyzerLogItemArray_clear(model->log_frequency);
+        },
+        true);
 }
 
 SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() {
     SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer));
 
     // View allocation and configuration
+    instance->last_frequency = 0;
     instance->view = view_alloc();
     view_allocate_model(
         instance->view, ViewModelTypeLocking, sizeof(SubGhzFrequencyAnalyzerModel));