Parcourir la source

Saving signal now works.

antirez il y a 3 ans
Parent
commit
10762482b6
5 fichiers modifiés avec 174 ajouts et 15 suppressions
  1. 8 3
      app.h
  2. 13 11
      app_subghz.c
  3. 1 1
      signal.c
  4. 143 0
      signal_file.c
  5. 9 0
      view_info.c

+ 8 - 3
app.h

@@ -56,9 +56,11 @@ typedef enum {
 } SwitchViewDirection;
 } SwitchViewDirection;
 
 
 typedef struct {
 typedef struct {
-    const char *name;
-    FuriHalSubGhzPreset preset;
-    uint8_t *custom;
+    const char *name;               // Name to show to the user.
+    const char *id;                 // Identifier in the Flipper API/file.
+    FuriHalSubGhzPreset preset;     // The preset ID.
+    uint8_t *custom;                // If not null, a set of registers for
+                                    // the CC1101, specifying a custom preset.
 } ProtoViewModulation;
 } ProtoViewModulation;
 
 
 extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
 extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
@@ -200,6 +202,9 @@ uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bi
 void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app);
 void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app);
 void free_msg_info(ProtoViewMsgInfo *i);
 void free_msg_info(ProtoViewMsgInfo *i);
 
 
+/* signal_file.c */
+bool save_signal(ProtoViewApp *app, const char *filename);
+
 /* view_*.c */
 /* view_*.c */
 void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app);
 void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app);
 void process_input_raw_pulses(ProtoViewApp *app, InputEvent input);
 void process_input_raw_pulses(ProtoViewApp *app, InputEvent input);

+ 13 - 11
app_subghz.c

@@ -13,17 +13,19 @@ void raw_sampling_worker_start(ProtoViewApp *app);
 void raw_sampling_worker_stop(ProtoViewApp *app);
 void raw_sampling_worker_stop(ProtoViewApp *app);
 
 
 ProtoViewModulation ProtoViewModulations[] = {
 ProtoViewModulation ProtoViewModulations[] = {
-    {"OOK 650Khz", FuriHalSubGhzPresetOok650Async, NULL},
-    {"OOK 270Khz", FuriHalSubGhzPresetOok270Async, NULL},
-    {"2FSK 2.38Khz", FuriHalSubGhzPreset2FSKDev238Async, NULL},
-    {"2FSK 47.6Khz", FuriHalSubGhzPreset2FSKDev476Async, NULL},
-    {"MSK", FuriHalSubGhzPresetMSK99_97KbAsync, NULL},
-    {"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync, NULL},
-    {"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs},
-    {"TPMS 2 (OOK)", 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs},
-    {"TPMS 3 (FSK)", 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs},
-    {"TPMS 4 (FSK)", 0, (uint8_t*)protoview_subghz_tpms4_fsk_async_regs},
-    {NULL, 0, NULL} /* End of list sentinel. */
+    {"OOK 650Khz", "FuriHalSubGhzPresetOok650Async",
+                    FuriHalSubGhzPresetOok650Async, NULL},
+    {"OOK 270Khz", "FuriHalSubGhzPresetOok270Async",
+                    FuriHalSubGhzPresetOok270Async, NULL},
+    {"2FSK 2.38Khz", "FuriHalSubGhzPreset2FSKDev238Async",
+                    FuriHalSubGhzPreset2FSKDev238Async, NULL},
+    {"2FSK 47.6Khz", "FuriHalSubGhzPreset2FSKDev476Async",
+                    FuriHalSubGhzPreset2FSKDev476Async, NULL},
+    {"TPMS 1 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs},
+    {"TPMS 2 (OOK)", NULL, 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs},
+    {"TPMS 3 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs},
+    {"TPMS 4 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms4_fsk_async_regs},
+    {NULL, NULL, 0, NULL} /* End of list sentinel. */
 };
 };
 
 
 /* Called after the application initialization in order to setup the
 /* Called after the application initialization in order to setup the

+ 1 - 1
signal.c

@@ -147,6 +147,7 @@ void scan_for_signal(ProtoViewApp *app) {
              * fill, in case it is able to decode a message. */
              * fill, in case it is able to decode a message. */
             ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
             ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
             init_msg_info(info,app);
             init_msg_info(info,app);
+            info->short_pulse_dur = copy->short_pulse_dur;
 
 
             uint32_t saved_idx = copy->idx; /* Save index, see later. */
             uint32_t saved_idx = copy->idx; /* Save index, see later. */
 
 
@@ -523,7 +524,6 @@ void free_msg_info(ProtoViewMsgInfo *i) {
 void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app) {
 void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app) {
     UNUSED(app);
     UNUSED(app);
     memset(i,0,sizeof(ProtoViewMsgInfo));
     memset(i,0,sizeof(ProtoViewMsgInfo));
-    i->short_pulse_dur = DetectedSamples->short_pulse_dur;
     i->bits = NULL;
     i->bits = NULL;
 }
 }
 
 

+ 143 - 0
signal_file.c

@@ -0,0 +1,143 @@
+/* Copyright (C) 2023 Salvatore Sanfilippo -- All Rights Reserved
+ * Copyright (C) 2023 Maciej Wojtasik -- All Rights Reserved
+ * See the LICENSE file for information about the license. */
+
+#include "app.h"
+#include <stream/stream.h>
+#include <flipper_format/flipper_format_i.h>
+
+/* ========================= Signal file operations ========================= */
+
+/* This function saves the current logical signal on disk. What is saved here
+ * is not the signal as level and duration as we received it from CC1101,
+ * but it's logical representation stored in the app->msg_info bitmap, where
+ * each 1 or 0 means a puls or gap for the specified short pulse duration time
+ * (te). */
+bool save_signal(ProtoViewApp *app, const char *filename) {
+    /* We have a message at all? */
+    if (app->msg_info == NULL || app->msg_info->pulses_count == 0) return false;
+    
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat *file = flipper_format_file_alloc(storage);
+    Stream *stream = flipper_format_get_raw_stream(file);
+    FuriString *file_content = NULL;
+    bool success = true;
+
+    if (flipper_format_file_open_always(file, filename)) {
+        /* Write the file header. */
+        FuriString *file_content = furi_string_alloc();
+        const char *preset_id = ProtoViewModulations[app->modulation].id;
+
+        furi_string_printf(file_content,
+             "Filetype: Flipper SubGhz RAW File\n"
+             "Version: 1\n"
+             "Frequency: %ld\n"
+             "Preset: %s\n",
+                app->frequency,
+                preset_id ? preset_id : "FuriHalSubGhzPresetCustom");
+
+        /* For custom modulations, we need to emit a set of registers. */
+        if (preset_id == NULL) {
+            FuriString *custom = furi_string_alloc();
+            uint8_t *regs = ProtoViewModulations[app->modulation].custom;
+            furi_string_printf(custom,
+                "Custom_preset_module: CC1101\n"
+                 "Custom_preset_data: ");
+            for (int j = 0; regs[j]; j += 2) {
+                furi_string_cat_printf(custom, "%02X %02X ",
+                    (int)regs[j], (int)regs[j+1]);
+            }
+            size_t len = furi_string_size(file_content);
+            furi_string_set_char(custom,len-1,'\n');
+            furi_string_cat(file_content,custom);
+            furi_string_free(custom);
+        }
+
+        /* We always save raw files. */
+        furi_string_cat_printf(file_content,
+             "Protocol: RAW\n"
+             "RAW_Data: -10000\n"); // Start with 10 ms of gap
+
+        /* Write header. */
+        size_t len = furi_string_size(file_content);
+        if (stream_write(stream,
+            (uint8_t*) furi_string_get_cstr(file_content), len)
+            != len)
+        {
+            FURI_LOG_W(TAG, "Short write to file");
+            success = false;
+            goto write_err;
+        }
+        furi_string_reset(file_content);
+
+        /* Write raw data sections. The Flipper subghz parser can't handle
+         * too much data on a single line, so we generate a new one
+         * every few samples. */
+        uint32_t this_line_samples = 0;
+        uint32_t max_line_samples = 100;
+        uint32_t idx = 0; // Iindex in the signal bitmap.
+        ProtoViewMsgInfo *i = app->msg_info;
+        FURI_LOG_W(TAG, "short dur:%d", (int)i->short_pulse_dur);
+        while(idx < i->pulses_count) {
+            bool level = bitmap_get(i->bits,i->bits_bytes,idx);
+            uint32_t te_times = 1;
+            idx++;
+            /* Count the duration of the current pulse/gap. */
+            while(idx < i->pulses_count &&
+                  bitmap_get(i->bits,i->bits_bytes,idx) == level)
+            {
+                te_times++;
+                idx++;
+            }
+            // Invariant: after the loop 'idx' is at the start of the
+            // next gap or pulse.
+
+            int32_t dur = (int32_t)i->short_pulse_dur * te_times;
+            if (level == 0) dur = -dur; /* Negative is gap in raw files. */
+
+            /* Emit the sample. If this is the first sample of the line,
+             * also emit the RAW_Data: field. */
+            if (this_line_samples == 0)
+                furi_string_cat_printf(file_content,"RAW_Data: ");
+            furi_string_cat_printf(file_content,"%d ",(int)dur);
+            FURI_LOG_W(TAG, "dur:%d/%d at idx %d", (int)dur, (int)i->pulses_count, (int)idx);
+            this_line_samples++;
+
+            /* Store the current set of samples on disk, when we reach a
+             * given number or the end of the signal. */
+            bool end_reached = (idx == i->pulses_count);
+            if (this_line_samples == max_line_samples || end_reached) {
+                /* If that's the end, terminate the signal with a long
+                 * gap. */
+                if (end_reached) furi_string_cat_printf(file_content,"-10000 ");
+
+                /* We always have a trailing space in the last sample. Make it
+                 * a newline. */
+                size_t len = furi_string_size(file_content);
+                furi_string_set_char(file_content,len-1,'\n');
+
+                if (stream_write(stream,
+                    (uint8_t*) furi_string_get_cstr(file_content),
+                    len) != len)
+                {
+                    FURI_LOG_W(TAG, "Short write to file");
+                    success = false;
+                    goto write_err;
+                }
+
+                /* Prepare for next line. */
+                furi_string_reset(file_content);
+                this_line_samples = 0;
+            }
+        }
+    } else {
+        success = false;
+        FURI_LOG_W(TAG, "Unable to open file");
+    }
+
+write_err:
+    furi_record_close(RECORD_STORAGE);
+    flipper_format_free(file);
+    if (file_content != NULL) furi_string_free(file_content);
+    return success;
+}

+ 9 - 0
view_info.c

@@ -94,9 +94,17 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
     }
     }
 }
 }
 
 
+/* The user typed the file name. Let's save it and remove the keyboard
+ * view. */
 void text_input_done_callback(void* context) {
 void text_input_done_callback(void* context) {
     ProtoViewApp *app = context;
     ProtoViewApp *app = context;
     InfoViewPrivData *privdata = app->view_privdata;
     InfoViewPrivData *privdata = app->view_privdata;
+
+    FuriString *save_path = furi_string_alloc_printf(
+        "%s/%s.sub", EXT_PATH("subghz"), privdata->filename);
+    save_signal(app, furi_string_get_cstr(save_path));
+    furi_string_free(save_path);
+
     free(privdata->filename);
     free(privdata->filename);
     dismiss_keyboard(app);
     dismiss_keyboard(app);
 }
 }
@@ -118,6 +126,7 @@ void set_signal_random_filename(ProtoViewApp *app, char *buf, size_t buflen) {
     snprintf(buf,buflen,"%.10s-%s-%d",app->msg_info->name,suffix,rand()%1000);
     snprintf(buf,buflen,"%.10s-%s-%d",app->msg_info->name,suffix,rand()%1000);
     str_replace(buf,' ','_');
     str_replace(buf,' ','_');
     str_replace(buf,'-','_');
     str_replace(buf,'-','_');
+    str_replace(buf,'/','_');
 }
 }
 
 
 /* Handle input for the info view. */
 /* Handle input for the info view. */