Skip to content

Commit

Permalink
Saving signal now works.
Browse files Browse the repository at this point in the history
  • Loading branch information
antirez committed Jan 18, 2023
1 parent b9ece78 commit 1076248
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 15 deletions.
11 changes: 8 additions & 3 deletions app.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ typedef enum {
} SwitchViewDirection;

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;

extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
Expand Down Expand Up @@ -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 free_msg_info(ProtoViewMsgInfo *i);

/* signal_file.c */
bool save_signal(ProtoViewApp *app, const char *filename);

/* view_*.c */
void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app);
void process_input_raw_pulses(ProtoViewApp *app, InputEvent input);
Expand Down
24 changes: 13 additions & 11 deletions app_subghz.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app);

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
Expand Down
2 changes: 1 addition & 1 deletion signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ void scan_for_signal(ProtoViewApp *app) {
* fill, in case it is able to decode a message. */
ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
init_msg_info(info,app);
info->short_pulse_dur = copy->short_pulse_dur;

uint32_t saved_idx = copy->idx; /* Save index, see later. */

Expand Down Expand Up @@ -523,7 +524,6 @@ void free_msg_info(ProtoViewMsgInfo *i) {
void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app) {
UNUSED(app);
memset(i,0,sizeof(ProtoViewMsgInfo));
i->short_pulse_dur = DetectedSamples->short_pulse_dur;
i->bits = NULL;
}

Expand Down
143 changes: 143 additions & 0 deletions signal_file.c
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions view_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
ProtoViewApp *app = context;
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);
dismiss_keyboard(app);
}
Expand All @@ -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);
str_replace(buf,' ','_');
str_replace(buf,'-','_');
str_replace(buf,'/','_');
}

/* Handle input for the info view. */
Expand Down

0 comments on commit 1076248

Please sign in to comment.