mirror of
https://protopirate.net/ProtoPirate/ProtoPirate.git
synced 2026-03-29 12:09:51 +00:00
725 lines
23 KiB
C
725 lines
23 KiB
C
// scenes/protopirate_scene_timing_tuner.c
|
|
#include "../protopirate_app_i.h"
|
|
#ifdef ENABLE_TIMING_TUNER_SCENE
|
|
#include "../protocols/protocol_items.h"
|
|
#include <gui/elements.h>
|
|
#include <math.h>
|
|
|
|
#define TAG "ProtoPirateTimingTuner"
|
|
|
|
#define MAX_TIMING_SAMPLES 512
|
|
#define VISIBLE_LINES 6
|
|
#define LINE_HEIGHT 9
|
|
#define SUBGHZ_RAW_THRESHOLD_MIN -90.0f
|
|
|
|
typedef struct {
|
|
// Ring buffer for timing capture
|
|
int32_t samples[MAX_TIMING_SAMPLES];
|
|
size_t write_idx; // Next write position
|
|
size_t sample_count; // Total samples captured (may exceed MAX)
|
|
bool buffer_wrapped; // True if we've wrapped around
|
|
|
|
// Timing statistics
|
|
size_t short_count;
|
|
size_t long_count;
|
|
|
|
// Calculated stats
|
|
int32_t avg_short;
|
|
int32_t avg_long;
|
|
int32_t min_short;
|
|
int32_t max_short;
|
|
int32_t min_long;
|
|
int32_t max_long;
|
|
|
|
// Protocol match info
|
|
const char* matched_protocol;
|
|
const ProtoPirateProtocolTiming* timing_info;
|
|
|
|
// State
|
|
bool is_receiving;
|
|
bool has_match;
|
|
uint16_t animation_frame;
|
|
float rssi;
|
|
uint8_t scroll_offset;
|
|
uint8_t total_lines;
|
|
|
|
// App reference for callback
|
|
ProtoPirateApp* app;
|
|
} TimingTunerContext;
|
|
|
|
static TimingTunerContext* g_timing_ctx = NULL;
|
|
|
|
static void calculate_timing_stats(TimingTunerContext* ctx) {
|
|
size_t num_samples = ctx->buffer_wrapped ? MAX_TIMING_SAMPLES : ctx->write_idx;
|
|
|
|
if(num_samples < 10) {
|
|
FURI_LOG_W(TAG, "Not enough samples: %zu", num_samples);
|
|
return;
|
|
}
|
|
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"Analyzing %zu samples (total captured: %zu, wrapped: %d)",
|
|
num_samples,
|
|
ctx->sample_count,
|
|
ctx->buffer_wrapped);
|
|
|
|
ctx->short_count = 0;
|
|
ctx->long_count = 0;
|
|
ctx->min_short = INT32_MAX;
|
|
ctx->max_short = 0;
|
|
ctx->min_long = INT32_MAX;
|
|
ctx->max_long = 0;
|
|
|
|
int64_t short_sum = 0;
|
|
int64_t long_sum = 0;
|
|
|
|
int32_t threshold;
|
|
int32_t min_valid;
|
|
int32_t max_valid;
|
|
|
|
if(ctx->timing_info) {
|
|
int32_t te_short = (int32_t)ctx->timing_info->te_short;
|
|
int32_t te_long = (int32_t)ctx->timing_info->te_long;
|
|
int32_t te_delta = (int32_t)ctx->timing_info->te_delta;
|
|
|
|
// Threshold halfway between expected short and long
|
|
threshold = (te_short + te_long) / 2;
|
|
|
|
// Valid pulse range - use 2x delta as bounds
|
|
min_valid = te_short - (te_delta * 2);
|
|
if(min_valid < 100) min_valid = 100;
|
|
max_valid = te_long + (te_delta * 2);
|
|
|
|
FURI_LOG_I(
|
|
TAG, "Protocol timing: threshold=%ld valid=%ld-%ld", threshold, min_valid, max_valid);
|
|
|
|
} else {
|
|
// No protocol info - use reasonable defaults
|
|
threshold = 400;
|
|
min_valid = 100;
|
|
max_valid = 1200;
|
|
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"No protocol ref, using defaults: threshold=%ld valid=%ld-%ld",
|
|
threshold,
|
|
min_valid,
|
|
max_valid);
|
|
}
|
|
|
|
// Analyze all samples in the ring buffer
|
|
for(size_t i = 0; i < num_samples; i++) {
|
|
int32_t dur = ctx->samples[i];
|
|
if(dur < 0) dur = -dur;
|
|
|
|
// Filter out noise and gaps
|
|
if(dur < min_valid || dur > max_valid) continue;
|
|
|
|
if(dur < threshold) {
|
|
short_sum += dur;
|
|
ctx->short_count++;
|
|
if(dur < ctx->min_short) ctx->min_short = dur;
|
|
if(dur > ctx->max_short) ctx->max_short = dur;
|
|
} else {
|
|
long_sum += dur;
|
|
ctx->long_count++;
|
|
if(dur < ctx->min_long) ctx->min_long = dur;
|
|
if(dur > ctx->max_long) ctx->max_long = dur;
|
|
}
|
|
}
|
|
|
|
// Calculate averages
|
|
if(ctx->short_count > 0) {
|
|
ctx->avg_short = (int32_t)(short_sum / (int64_t)ctx->short_count);
|
|
} else {
|
|
ctx->avg_short = 0;
|
|
ctx->min_short = 0;
|
|
ctx->max_short = 0;
|
|
}
|
|
|
|
if(ctx->long_count > 0) {
|
|
ctx->avg_long = (int32_t)(long_sum / (int64_t)ctx->long_count);
|
|
} else {
|
|
ctx->avg_long = 0;
|
|
ctx->min_long = 0;
|
|
ctx->max_long = 0;
|
|
}
|
|
|
|
// Log results
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"MEASURED SHORT: avg=%ld min=%ld max=%ld n=%zu",
|
|
ctx->avg_short,
|
|
ctx->min_short,
|
|
ctx->max_short,
|
|
ctx->short_count);
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"MEASURED LONG: avg=%ld min=%ld max=%ld n=%zu",
|
|
ctx->avg_long,
|
|
ctx->min_long,
|
|
ctx->max_long,
|
|
ctx->long_count);
|
|
|
|
if(ctx->timing_info && ctx->short_count > 0 && ctx->long_count > 0) {
|
|
#ifndef REMOVE_LOGS
|
|
int32_t short_diff = ctx->avg_short - (int32_t)ctx->timing_info->te_short;
|
|
int32_t long_diff = ctx->avg_long - (int32_t)ctx->timing_info->te_long;
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"DIFFERENCE: short=%+ld long=%+ld (tolerance +/-%lu)",
|
|
short_diff,
|
|
long_diff,
|
|
ctx->timing_info->te_delta);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void timing_tuner_draw_listening(Canvas* canvas, TimingTunerContext* ctx) {
|
|
canvas_set_font(canvas, FontPrimary);
|
|
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Timing Tuner");
|
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignTop, "Listening for signals...");
|
|
|
|
int wave_y = 38;
|
|
ctx->animation_frame++;
|
|
for(int x = 0; x < 128; x++) {
|
|
float phase = (float)(x + ctx->animation_frame * 3) * 0.15f;
|
|
int y_offset = (int)(sinf(phase) * 8.0f);
|
|
canvas_draw_dot(canvas, x, wave_y + y_offset);
|
|
}
|
|
|
|
{
|
|
//RSSI Signal Bar
|
|
uint8_t spacer = 0;
|
|
for(uint8_t i = 1; i < (uint8_t)(ctx->rssi - SUBGHZ_RAW_THRESHOLD_MIN); i++) {
|
|
if(i % 5) {
|
|
uint8_t j = 46 + i + spacer;
|
|
canvas_draw_dot(canvas, j, 58);
|
|
canvas_draw_dot(canvas, j + 1, 59);
|
|
canvas_draw_dot(canvas, j, 60);
|
|
} else
|
|
spacer++;
|
|
}
|
|
}
|
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
char rssi_str[24];
|
|
snprintf(rssi_str, sizeof(rssi_str), "%.0f", (double)ctx->rssi);
|
|
canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, rssi_str);
|
|
elements_button_left(canvas, "Config");
|
|
}
|
|
|
|
// Get a specific line of content
|
|
static bool
|
|
get_result_line(TimingTunerContext* ctx, uint8_t line_idx, char* buf, size_t buf_size) {
|
|
int32_t short_diff = 0;
|
|
int32_t long_diff = 0;
|
|
bool short_ok = false;
|
|
bool long_ok = false;
|
|
bool short_exact = false;
|
|
bool long_exact = false;
|
|
int32_t short_jitter = 0;
|
|
int32_t long_jitter = 0;
|
|
|
|
if(ctx->timing_info) {
|
|
short_diff = ctx->avg_short - (int32_t)ctx->timing_info->te_short;
|
|
long_diff = ctx->avg_long - (int32_t)ctx->timing_info->te_long;
|
|
short_ok = (ctx->short_count > 0) &&
|
|
(abs(short_diff) <= (int32_t)ctx->timing_info->te_delta);
|
|
long_ok = (ctx->long_count > 0) && (abs(long_diff) <= (int32_t)ctx->timing_info->te_delta);
|
|
short_exact = (abs(short_diff) <= 15);
|
|
long_exact = (abs(long_diff) <= 15);
|
|
}
|
|
short_jitter = ctx->max_short - ctx->min_short;
|
|
long_jitter = ctx->max_long - ctx->min_long;
|
|
|
|
if(ctx->timing_info) {
|
|
switch(line_idx) {
|
|
case 0:
|
|
snprintf(buf, buf_size, "PROTOCOL DEFINITION:");
|
|
return true;
|
|
case 1:
|
|
snprintf(buf, buf_size, " Short: %lu us", ctx->timing_info->te_short);
|
|
return true;
|
|
case 2:
|
|
snprintf(buf, buf_size, " Long: %lu us", ctx->timing_info->te_long);
|
|
return true;
|
|
case 3:
|
|
snprintf(buf, buf_size, " Tolerance: +/-%lu us", ctx->timing_info->te_delta);
|
|
return true;
|
|
case 4:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 5:
|
|
snprintf(buf, buf_size, "RECEIVED SIGNAL:");
|
|
return true;
|
|
case 6:
|
|
snprintf(buf, buf_size, " Short Avg: %ld us", ctx->avg_short);
|
|
return true;
|
|
case 7:
|
|
snprintf(buf, buf_size, " Short Min: %ld us", ctx->min_short);
|
|
return true;
|
|
case 8:
|
|
snprintf(buf, buf_size, " Short Max: %ld us", ctx->max_short);
|
|
return true;
|
|
case 9:
|
|
snprintf(buf, buf_size, " Short Samples: %zu", ctx->short_count);
|
|
return true;
|
|
case 10:
|
|
snprintf(buf, buf_size, " Long Avg: %ld us", ctx->avg_long);
|
|
return true;
|
|
case 11:
|
|
snprintf(buf, buf_size, " Long Min: %ld us", ctx->min_long);
|
|
return true;
|
|
case 12:
|
|
snprintf(buf, buf_size, " Long Max: %ld us", ctx->max_long);
|
|
return true;
|
|
case 13:
|
|
snprintf(buf, buf_size, " Long Samples: %zu", ctx->long_count);
|
|
return true;
|
|
case 14:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 15:
|
|
snprintf(buf, buf_size, "ANALYSIS:");
|
|
return true;
|
|
case 16:
|
|
snprintf(buf, buf_size, " Short Diff: %+ld us", short_diff);
|
|
return true;
|
|
case 17:
|
|
snprintf(buf, buf_size, " Long Diff: %+ld us", long_diff);
|
|
return true;
|
|
case 18:
|
|
snprintf(buf, buf_size, " Short Jitter: %ld us", short_jitter);
|
|
return true;
|
|
case 19:
|
|
snprintf(buf, buf_size, " Long Jitter: %ld us", long_jitter);
|
|
return true;
|
|
case 20:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 21:
|
|
snprintf(buf, buf_size, "CONCLUSION:");
|
|
return true;
|
|
case 22:
|
|
if(short_exact) {
|
|
snprintf(buf, buf_size, " Short: EXCELLENT");
|
|
} else if(short_ok) {
|
|
snprintf(buf, buf_size, " Short: OK (%+ld)", short_diff);
|
|
} else if(short_diff > 0) {
|
|
snprintf(buf, buf_size, " Short: HIGH by %ld", short_diff);
|
|
} else {
|
|
snprintf(buf, buf_size, " Short: LOW by %ld", -short_diff);
|
|
}
|
|
return true;
|
|
case 23:
|
|
if(long_exact) {
|
|
snprintf(buf, buf_size, " Long: EXCELLENT");
|
|
} else if(long_ok) {
|
|
snprintf(buf, buf_size, " Long: OK (%+ld)", long_diff);
|
|
} else if(long_diff > 0) {
|
|
snprintf(buf, buf_size, " Long: HIGH by %ld", long_diff);
|
|
} else {
|
|
snprintf(buf, buf_size, " Long: LOW by %ld", -long_diff);
|
|
}
|
|
return true;
|
|
case 24:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 25:
|
|
if(short_exact && long_exact) {
|
|
snprintf(buf, buf_size, "Timing matches fob!");
|
|
} else if(short_ok && long_ok) {
|
|
snprintf(buf, buf_size, "Within tolerance.");
|
|
} else {
|
|
snprintf(buf, buf_size, "NEEDS ADJUSTMENT:");
|
|
}
|
|
return true;
|
|
case 26:
|
|
if(short_exact && long_exact) {
|
|
snprintf(buf, buf_size, "No changes needed.");
|
|
} else if(short_ok && long_ok) {
|
|
snprintf(buf, buf_size, "Consider fine-tuning.");
|
|
} else if(!short_ok && !long_ok) {
|
|
snprintf(buf, buf_size, "te_short=%ld", ctx->avg_short);
|
|
} else if(!short_ok) {
|
|
snprintf(buf, buf_size, "Set te_short=%ld", ctx->avg_short);
|
|
} else {
|
|
snprintf(buf, buf_size, "Set te_long=%ld", ctx->avg_long);
|
|
}
|
|
return true;
|
|
case 27:
|
|
if(!short_ok && !long_ok) {
|
|
snprintf(buf, buf_size, "te_long=%ld", ctx->avg_long);
|
|
} else {
|
|
buf[0] = '\0';
|
|
}
|
|
return true;
|
|
case 28:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 29:
|
|
snprintf(buf, buf_size, "OK:Retry <:Config");
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
} else {
|
|
// No timing reference
|
|
switch(line_idx) {
|
|
case 0:
|
|
snprintf(buf, buf_size, "NO PROTOCOL REFERENCE");
|
|
return true;
|
|
case 1:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 2:
|
|
snprintf(buf, buf_size, "RECEIVED SIGNAL:");
|
|
return true;
|
|
case 3:
|
|
snprintf(buf, buf_size, " Short Avg: %ld us", ctx->avg_short);
|
|
return true;
|
|
case 4:
|
|
snprintf(buf, buf_size, " Short Min: %ld us", ctx->min_short);
|
|
return true;
|
|
case 5:
|
|
snprintf(buf, buf_size, " Short Max: %ld us", ctx->max_short);
|
|
return true;
|
|
case 6:
|
|
snprintf(buf, buf_size, " Short Samples: %zu", ctx->short_count);
|
|
return true;
|
|
case 7:
|
|
snprintf(buf, buf_size, " Long Avg: %ld us", ctx->avg_long);
|
|
return true;
|
|
case 8:
|
|
snprintf(buf, buf_size, " Long Min: %ld us", ctx->min_long);
|
|
return true;
|
|
case 9:
|
|
snprintf(buf, buf_size, " Long Max: %ld us", ctx->max_long);
|
|
return true;
|
|
case 10:
|
|
snprintf(buf, buf_size, " Long Samples: %zu", ctx->long_count);
|
|
return true;
|
|
case 11:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 12:
|
|
snprintf(buf, buf_size, "Short Jitter: %ld us", short_jitter);
|
|
return true;
|
|
case 13:
|
|
snprintf(buf, buf_size, "Long Jitter: %ld us", long_jitter);
|
|
return true;
|
|
case 14:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 15:
|
|
snprintf(buf, buf_size, "Add to protocol_items.c");
|
|
return true;
|
|
case 16:
|
|
buf[0] = '\0';
|
|
return true;
|
|
case 17:
|
|
snprintf(buf, buf_size, "OK:Retry <:Config");
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint8_t count_result_lines(TimingTunerContext* ctx) {
|
|
if(ctx->timing_info) {
|
|
return 30; // Lines 0-29
|
|
} else {
|
|
return 18; // Lines 0-17
|
|
}
|
|
}
|
|
|
|
static void timing_tuner_draw_results(Canvas* canvas, TimingTunerContext* ctx) {
|
|
char line_buf[32];
|
|
|
|
uint8_t total_lines = count_result_lines(ctx);
|
|
ctx->total_lines = total_lines;
|
|
|
|
uint8_t max_scroll = 0;
|
|
if(total_lines > VISIBLE_LINES) {
|
|
max_scroll = total_lines - VISIBLE_LINES;
|
|
}
|
|
if(ctx->scroll_offset > max_scroll) {
|
|
ctx->scroll_offset = max_scroll;
|
|
}
|
|
|
|
// Draw header (protocol name)
|
|
canvas_set_font(canvas, FontPrimary);
|
|
canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, ctx->matched_protocol);
|
|
|
|
// Draw content lines
|
|
canvas_set_font(canvas, FontSecondary);
|
|
uint8_t y = 10;
|
|
for(uint8_t i = 0; i < VISIBLE_LINES; i++) {
|
|
uint8_t line_idx = ctx->scroll_offset + i;
|
|
if(get_result_line(ctx, line_idx, line_buf, sizeof(line_buf))) {
|
|
canvas_draw_str(canvas, 1, y + LINE_HEIGHT, line_buf);
|
|
}
|
|
y += LINE_HEIGHT;
|
|
}
|
|
|
|
// Scroll indicators
|
|
if(total_lines > VISIBLE_LINES) {
|
|
if(ctx->scroll_offset > 0) {
|
|
canvas_draw_str_aligned(canvas, 126, 14, AlignRight, AlignTop, "^");
|
|
}
|
|
if(ctx->scroll_offset < max_scroll) {
|
|
canvas_draw_str_aligned(canvas, 126, 62, AlignRight, AlignBottom, "v");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void timing_tuner_draw_callback(Canvas* canvas, void* context) {
|
|
UNUSED(context);
|
|
TimingTunerContext* ctx = g_timing_ctx;
|
|
if(!ctx) return;
|
|
|
|
canvas_clear(canvas);
|
|
canvas_set_color(canvas, ColorBlack);
|
|
|
|
if(!ctx->has_match) {
|
|
timing_tuner_draw_listening(canvas, ctx);
|
|
} else {
|
|
timing_tuner_draw_results(canvas, ctx);
|
|
}
|
|
}
|
|
|
|
static bool timing_tuner_input_callback(InputEvent* event, void* context) {
|
|
ProtoPirateApp* app = context;
|
|
bool consumed = false;
|
|
|
|
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
|
|
switch(event->key) {
|
|
case InputKeyBack:
|
|
if(event->type == InputTypeShort) {
|
|
view_dispatcher_send_custom_event(app->view_dispatcher, 0);
|
|
consumed = true;
|
|
}
|
|
break;
|
|
case InputKeyOk:
|
|
if(event->type == InputTypeShort && g_timing_ctx && g_timing_ctx->has_match) {
|
|
g_timing_ctx->has_match = false;
|
|
g_timing_ctx->write_idx = 0;
|
|
g_timing_ctx->sample_count = 0;
|
|
g_timing_ctx->buffer_wrapped = false;
|
|
g_timing_ctx->timing_info = NULL;
|
|
g_timing_ctx->scroll_offset = 0;
|
|
consumed = true;
|
|
}
|
|
break;
|
|
case InputKeyLeft:
|
|
if(event->type == InputTypeShort) {
|
|
view_dispatcher_send_custom_event(app->view_dispatcher, 1);
|
|
consumed = true;
|
|
}
|
|
break;
|
|
case InputKeyUp:
|
|
if(g_timing_ctx && g_timing_ctx->has_match && g_timing_ctx->scroll_offset > 0) {
|
|
g_timing_ctx->scroll_offset--;
|
|
consumed = true;
|
|
}
|
|
break;
|
|
case InputKeyDown:
|
|
if(g_timing_ctx && g_timing_ctx->has_match) {
|
|
uint8_t max_scroll = 0;
|
|
if(g_timing_ctx->total_lines > VISIBLE_LINES) {
|
|
max_scroll = g_timing_ctx->total_lines - VISIBLE_LINES;
|
|
}
|
|
if(g_timing_ctx->scroll_offset < max_scroll) {
|
|
g_timing_ctx->scroll_offset++;
|
|
}
|
|
consumed = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
static void timing_tuner_rx_callback(
|
|
SubGhzReceiver* receiver,
|
|
SubGhzProtocolDecoderBase* decoder_base,
|
|
void* context) {
|
|
UNUSED(receiver);
|
|
ProtoPirateApp* app = context;
|
|
TimingTunerContext* ctx = g_timing_ctx;
|
|
|
|
if(!ctx || ctx->has_match) return;
|
|
|
|
const char* protocol_name = decoder_base->protocol->name;
|
|
ctx->matched_protocol = protocol_name;
|
|
|
|
FURI_LOG_I(TAG, "Matched protocol: %s", protocol_name);
|
|
|
|
ctx->timing_info = protopirate_get_protocol_timing(protocol_name);
|
|
|
|
if(ctx->timing_info) {
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"Found timing for %s: short=%lu, long=%lu, delta=%lu",
|
|
ctx->timing_info->name,
|
|
ctx->timing_info->te_short,
|
|
ctx->timing_info->te_long,
|
|
ctx->timing_info->te_delta);
|
|
} else {
|
|
FURI_LOG_W(TAG, "No timing info found for protocol: %s", protocol_name);
|
|
}
|
|
|
|
calculate_timing_stats(ctx);
|
|
|
|
ctx->has_match = true;
|
|
ctx->scroll_offset = 0;
|
|
|
|
notification_message(app->notifications, &sequence_success);
|
|
}
|
|
|
|
static void timing_tuner_pair_callback(void* context, bool level, uint32_t duration) {
|
|
UNUSED(context);
|
|
TimingTunerContext* ctx = g_timing_ctx;
|
|
|
|
if(ctx && !ctx->has_match) {
|
|
// Ring buffer - always write, wrap around if needed
|
|
ctx->samples[ctx->write_idx] = level ? (int32_t)duration : -(int32_t)duration;
|
|
ctx->write_idx++;
|
|
ctx->sample_count++;
|
|
|
|
if(ctx->write_idx >= MAX_TIMING_SAMPLES) {
|
|
ctx->write_idx = 0;
|
|
ctx->buffer_wrapped = true;
|
|
}
|
|
}
|
|
|
|
if(ctx && ctx->app && ctx->app->txrx && ctx->app->txrx->receiver) {
|
|
subghz_receiver_decode(ctx->app->txrx->receiver, level, duration);
|
|
}
|
|
}
|
|
|
|
void protopirate_scene_timing_tuner_on_enter(void* context) {
|
|
furi_check(context);
|
|
ProtoPirateApp* app = context;
|
|
|
|
FURI_LOG_I(TAG, "Entering Timing Tuner");
|
|
|
|
g_timing_ctx = malloc(sizeof(TimingTunerContext));
|
|
if(!g_timing_ctx) {
|
|
FURI_LOG_E(TAG, "Failed to allocate timing tuner context");
|
|
notification_message(app->notifications, &sequence_error);
|
|
scene_manager_previous_scene(app->scene_manager);
|
|
return;
|
|
}
|
|
memset(g_timing_ctx, 0, sizeof(TimingTunerContext));
|
|
g_timing_ctx->is_receiving = false;
|
|
g_timing_ctx->has_match = false;
|
|
g_timing_ctx->rssi = -127.0f;
|
|
g_timing_ctx->timing_info = NULL;
|
|
g_timing_ctx->scroll_offset = 0;
|
|
g_timing_ctx->total_lines = 0;
|
|
g_timing_ctx->write_idx = 0;
|
|
g_timing_ctx->sample_count = 0;
|
|
g_timing_ctx->buffer_wrapped = false;
|
|
g_timing_ctx->app = app;
|
|
|
|
view_set_draw_callback(app->view_about, timing_tuner_draw_callback);
|
|
view_set_input_callback(app->view_about, timing_tuner_input_callback);
|
|
view_set_context(app->view_about, app);
|
|
|
|
// Allocate worker
|
|
if(!app->txrx->worker) {
|
|
app->txrx->worker = subghz_worker_alloc();
|
|
if(!app->txrx->worker) {
|
|
FURI_LOG_E(TAG, "Failed to allocate worker!");
|
|
return;
|
|
}
|
|
// Set up worker callbacks
|
|
subghz_worker_set_overrun_callback(
|
|
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
|
|
subghz_worker_set_pair_callback(
|
|
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
|
|
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
|
|
}
|
|
|
|
subghz_receiver_set_rx_callback(app->txrx->receiver, timing_tuner_rx_callback, app);
|
|
|
|
subghz_worker_set_pair_callback(
|
|
app->txrx->worker, (SubGhzWorkerPairCallback)timing_tuner_pair_callback);
|
|
|
|
protopirate_begin(app, app->txrx->preset->data);
|
|
protopirate_rx(app, app->txrx->preset->frequency);
|
|
g_timing_ctx->is_receiving = true;
|
|
|
|
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewAbout);
|
|
}
|
|
|
|
bool protopirate_scene_timing_tuner_on_event(void* context, SceneManagerEvent event) {
|
|
ProtoPirateApp* app = context;
|
|
bool consumed = false;
|
|
|
|
if(event.type == SceneManagerEventTypeCustom) {
|
|
if(event.event == 0) {
|
|
scene_manager_previous_scene(app->scene_manager);
|
|
consumed = true;
|
|
} else if(event.event == 1) {
|
|
if(g_timing_ctx && g_timing_ctx->is_receiving) {
|
|
protopirate_rx_end(app);
|
|
g_timing_ctx->is_receiving = false;
|
|
}
|
|
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneReceiverConfig);
|
|
consumed = true;
|
|
}
|
|
} else if(event.type == SceneManagerEventTypeTick) {
|
|
if(g_timing_ctx && g_timing_ctx->is_receiving && !g_timing_ctx->has_match) {
|
|
if(app->txrx->radio_device) {
|
|
g_timing_ctx->rssi = subghz_devices_get_rssi(app->txrx->radio_device);
|
|
}
|
|
// Blink the light like the SubGHZ app
|
|
notification_message(app->notifications, &sequence_blink_cyan_10);
|
|
}
|
|
view_commit_model(app->view_about, true);
|
|
consumed = true;
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
void protopirate_scene_timing_tuner_on_exit(void* context) {
|
|
ProtoPirateApp* app = context;
|
|
|
|
FURI_LOG_I(TAG, "Exiting Timing Tuner");
|
|
|
|
if(g_timing_ctx && g_timing_ctx->is_receiving) {
|
|
protopirate_rx_end(app);
|
|
}
|
|
|
|
subghz_worker_set_pair_callback(
|
|
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
|
|
|
|
if(app->txrx->worker) {
|
|
FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker);
|
|
subghz_worker_free(app->txrx->worker);
|
|
app->txrx->worker = NULL;
|
|
} else {
|
|
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
|
|
}
|
|
|
|
view_set_draw_callback(app->view_about, NULL);
|
|
view_set_input_callback(app->view_about, NULL);
|
|
|
|
if(g_timing_ctx) {
|
|
free(g_timing_ctx);
|
|
g_timing_ctx = NULL;
|
|
}
|
|
}
|
|
#endif
|