1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 20:49:49 +04:00

Update protoview

This commit is contained in:
MX
2023-01-19 20:28:02 +03:00
parent e4fd3b1b0f
commit 3174d29dfa
5 changed files with 181 additions and 3 deletions

View File

@@ -125,6 +125,7 @@ ProtoViewApp* protoview_app_alloc() {
// GUI // GUI
app->gui = furi_record_open(RECORD_GUI); app->gui = furi_record_open(RECORD_GUI);
app->notification = furi_record_open(RECORD_NOTIFICATION);
app->view_port = view_port_alloc(); app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, render_callback, app); view_port_draw_callback_set(app->view_port, render_callback, app);
view_port_input_callback_set(app->view_port, input_callback, app); view_port_input_callback_set(app->view_port, input_callback, app);
@@ -185,7 +186,7 @@ ProtoViewApp* protoview_app_alloc() {
void protoview_app_free(ProtoViewApp* app) { void protoview_app_free(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
// Put CC1101 on sleep. // Put CC1101 on sleep, this also restores charging.
radio_sleep(app); radio_sleep(app);
// View related. // View related.
@@ -193,6 +194,7 @@ void protoview_app_free(ProtoViewApp* app) {
gui_remove_view_port(app->gui, app->view_port); gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port); view_port_free(app->view_port);
furi_record_close(RECORD_GUI); furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_message_queue_free(app->event_queue); furi_message_queue_free(app->event_queue);
app->gui = NULL; app->gui = NULL;

View File

@@ -36,6 +36,7 @@ typedef struct ProtoViewApp ProtoViewApp;
typedef enum { typedef enum {
TxRxStateIDLE, TxRxStateIDLE,
TxRxStateRx, TxRxStateRx,
TxRxStateTx,
TxRxStateSleep, TxRxStateSleep,
} TxRxState; } TxRxState;
@@ -112,9 +113,11 @@ typedef struct ProtoViewMsgInfo {
integer. */ integer. */
} ProtoViewMsgInfo; } ProtoViewMsgInfo;
/* Our main application context. */
struct ProtoViewApp { struct ProtoViewApp {
/* GUI */ /* GUI */
Gui* gui; Gui* gui;
NotificationApp* notification;
ViewPort* view_port; /* We just use a raw viewport and we render ViewPort* view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */ everything into the low level canvas. */
ProtoViewCurrentView current_view; /* Active left-right view ID. */ ProtoViewCurrentView current_view; /* Active left-right view ID. */
@@ -182,6 +185,7 @@ void radio_rx_end(ProtoViewApp* app);
void radio_sleep(ProtoViewApp* app); void radio_sleep(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp* app); void raw_sampling_worker_start(ProtoViewApp* app);
void raw_sampling_worker_stop(ProtoViewApp* app); void raw_sampling_worker_stop(ProtoViewApp* app);
void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder, void* ctx);
/* signal.c */ /* signal.c */
uint32_t duration_delta(uint32_t a, uint32_t b); uint32_t duration_delta(uint32_t a, uint32_t b);

View File

@@ -39,6 +39,10 @@ void radio_begin(ProtoViewApp* app) {
furi_hal_subghz_reset(); furi_hal_subghz_reset();
furi_hal_subghz_idle(); furi_hal_subghz_idle();
/* Power circuits are noisy. Suppressing the charge while we use
* ProtoView will improve the RF performances. */
furi_hal_power_suppress_charge_enter();
/* The CC1101 preset can be either one of the standard presets, if /* The CC1101 preset can be either one of the standard presets, if
* the modulation "custom" field is NULL, or a custom preset we * the modulation "custom" field is NULL, or a custom preset we
* defined in custom_presets.h. */ * defined in custom_presets.h. */
@@ -50,6 +54,8 @@ void radio_begin(ProtoViewApp* app) {
app->txrx->txrx_state = TxRxStateIDLE; app->txrx->txrx_state = TxRxStateIDLE;
} }
/* ================================= Reception ============================== */
/* Setup subghz to start receiving using a background worker. */ /* Setup subghz to start receiving using a background worker. */
uint32_t radio_rx(ProtoViewApp* app) { uint32_t radio_rx(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
@@ -78,6 +84,7 @@ uint32_t radio_rx(ProtoViewApp* app) {
/* Stop subghz worker (if active), put radio on idle state. */ /* Stop subghz worker (if active), put radio on idle state. */
void radio_rx_end(ProtoViewApp* app) { void radio_rx_end(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
if(app->txrx->txrx_state == TxRxStateRx) { if(app->txrx->txrx_state == TxRxStateRx) {
if(!app->txrx->debug_timer_sampling) { if(!app->txrx->debug_timer_sampling) {
if(subghz_worker_is_running(app->txrx->worker)) { if(subghz_worker_is_running(app->txrx->worker)) {
@@ -102,6 +109,33 @@ void radio_sleep(ProtoViewApp* app) {
} }
furi_hal_subghz_sleep(); furi_hal_subghz_sleep();
app->txrx->txrx_state = TxRxStateSleep; app->txrx->txrx_state = TxRxStateSleep;
furi_hal_power_suppress_charge_exit();
}
/* =============================== Transmission ============================= */
/* This function suspends the current RX state, switches to TX mode,
* transmits the signal provided by the callback data_feeder, and later
* restores the RX state if there was one. */
void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder, void* ctx) {
TxRxState oldstate = app->txrx->txrx_state;
if(oldstate == TxRxStateRx) radio_rx_end(app);
radio_begin(app);
furi_hal_subghz_idle();
uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_E(TAG, "Switched to frequency: %lu", value);
furi_hal_gpio_write(&gpio_cc1101_g0, false);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_start_async_tx(data_feeder, ctx);
while(!furi_hal_subghz_is_async_tx_complete()) furi_delay_ms(10);
furi_hal_subghz_stop_async_tx();
furi_hal_subghz_idle();
radio_begin(app);
if(oldstate == TxRxStateRx) radio_rx(app);
} }
/* ============================= Raw sampling mode ============================= /* ============================= Raw sampling mode =============================

View File

@@ -118,6 +118,26 @@ uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx) {
return len; return len;
} }
/* Called when we detect a message. Just blinks when the message was
* not decoded. Vibrates, too, when the message was correctly decoded. */
void notify_signal_detected(ProtoViewApp* app, bool decoded) {
static const NotificationSequence decoded_seq = {
&message_vibro_on,
&message_green_255,
&message_delay_50,
&message_green_0,
&message_vibro_off,
NULL};
static const NotificationSequence unknown_seq = {
&message_red_255, &message_delay_50, &message_red_0, NULL};
if(decoded)
notification_message(app->notification, &decoded_seq);
else
notification_message(app->notification, &unknown_seq);
}
/* Search the buffer with the stored signal (last N samples received) /* Search the buffer with the stored signal (last N samples received)
* in order to find a coherent signal. If a signal that does not appear to * in order to find a coherent signal. If a signal that does not appear to
* be just noise is found, it is set in DetectedSamples global signal * be just noise is found, it is set in DetectedSamples global signal
@@ -179,6 +199,7 @@ void scan_for_signal(ProtoViewApp* app) {
app->us_scale = 10; app->us_scale = 10;
else if(DetectedSamples->short_pulse_dur < 145) else if(DetectedSamples->short_pulse_dur < 145)
app->us_scale = 30; app->us_scale = 30;
notify_signal_detected(app, decoded);
} else { } else {
/* If the structure was not filled, discard it. Otherwise /* If the structure was not filled, discard it. Otherwise
* now the owner is app->msg_info. */ * now the owner is app->msg_info. */

View File

@@ -5,6 +5,8 @@
#include <gui/view_i.h> #include <gui/view_i.h>
#include <lib/toolbox/random_name.h> #include <lib/toolbox/random_name.h>
/* This view has subviews accessible navigating up/down. This
* enumaration is used to track the currently active subview. */
enum { enum {
SubViewInfoMain, SubViewInfoMain,
SubViewInfoSave, SubViewInfoSave,
@@ -90,7 +92,7 @@ static void render_subview_save(Canvas* const canvas, ProtoViewApp* app) {
} }
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 0, 6, "ok: save, < >: slide rows"); canvas_draw_str(canvas, 0, 6, "ok: send, long ok: save");
} }
/* Render the selected subview of this view. */ /* Render the selected subview of this view. */
@@ -147,6 +149,116 @@ void set_signal_random_filename(ProtoViewApp* app, char* buf, size_t buflen) {
str_replace(buf, '/', '_'); str_replace(buf, '/', '_');
} }
/* ========================== Signal transmission =========================== */
/* This is the context we pass to the data yield callback for
* asynchronous tx. */
typedef enum {
SendSignalSendStartGap,
SendSignalSendBits,
SendSignalSendEndGap,
SendSignalEndTransmission
} SendSignalState;
#define PROTOVIEW_SENDSIGNAL_START_GAP 10000 /* microseconds. */
#define PROTOVIEW_SENDSIGNAL_END_GAP 10000 /* microseconds. */
typedef struct {
SendSignalState state; // Current state.
uint32_t curpos; // Current bit position of data to send.
ProtoViewApp* app; // App reference.
uint32_t start_gap_dur; // Gap to send at the start.
uint32_t end_gap_dur; // Gap to send at the end.
} SendSignalCtx;
/* Setup the state context for the callback responsible to feed data
* to the subghz async tx system. */
static void send_signal_init(SendSignalCtx* ss, ProtoViewApp* app) {
ss->state = SendSignalSendStartGap;
ss->curpos = 0;
ss->app = app;
ss->start_gap_dur = PROTOVIEW_SENDSIGNAL_START_GAP;
ss->end_gap_dur = PROTOVIEW_SENDSIGNAL_END_GAP;
}
/* Send signal data feeder callback. When the asynchronous transmission is
* active, this function is called to return new samples from the currently
* decoded signal in app->msg_info. The subghz subsystem aspects this function,
* that is the data feeder, to return LevelDuration types (that is a structure
* with level, that is pulse or gap, and duration in microseconds).
*
* The position into the transmission is stored in the context 'ctx', that
* references a SendSignalCtx structure.
*
* In the SendSignalCtx structure 'ss' we remember at which bit of the
* message we are, in ss->curoff. We also send a start and end gap in order
* to make sure the transmission is clear.
*/
LevelDuration radio_tx_feed_data(void* ctx) {
SendSignalCtx* ss = ctx;
/* Send start gap. */
if(ss->state == SendSignalSendStartGap) {
ss->state = SendSignalSendBits;
return level_duration_make(0, ss->start_gap_dur);
}
/* Send data. */
if(ss->state == SendSignalSendBits) {
uint32_t dur = 0, j;
uint32_t level = 0;
/* Let's see how many consecutive bits we have with the same
* level. */
for(j = 0; ss->curpos + j < ss->app->msg_info->pulses_count; j++) {
uint32_t l =
bitmap_get(ss->app->msg_info->bits, ss->app->msg_info->bits_bytes, ss->curpos + j);
if(j == 0) {
/* At the first bit of this sequence, we store the
* level of the sequence. */
level = l;
dur += ss->app->msg_info->short_pulse_dur;
continue;
}
/* As long as the level is the same, we update the duration.
* Otherwise stop the loop and return this sample. */
if(l != level) break;
dur += ss->app->msg_info->short_pulse_dur;
}
ss->curpos += j;
/* If this was the last set of bits, change the state to
* send the final gap. */
if(ss->curpos >= ss->app->msg_info->pulses_count) ss->state = SendSignalSendEndGap;
return level_duration_make(level, dur);
}
/* Send end gap. */
if(ss->state == SendSignalSendEndGap) {
ss->state = SendSignalEndTransmission;
return level_duration_make(0, ss->end_gap_dur);
}
/* End transmission. Here state is guaranteed
* to be SendSignalEndTransmission */
return level_duration_reset();
}
/* Vibrate and produce a click sound when a signal is sent. */
void notify_signal_sent(ProtoViewApp* app) {
static const NotificationSequence sent_seq = {
&message_blue_255,
&message_vibro_on,
&message_note_g1,
&message_delay_10,
&message_sound_off,
&message_vibro_off,
&message_blue_0,
NULL};
notification_message(app->notification, &sent_seq);
}
/* Handle input for the info view. */ /* Handle input for the info view. */
void process_input_info(ProtoViewApp* app, InputEvent input) { void process_input_info(ProtoViewApp* app, InputEvent input) {
if(process_subview_updown(app, input, SubViewInfoLast)) return; if(process_subview_updown(app, input, SubViewInfoLast)) return;
@@ -165,10 +277,15 @@ void process_input_info(ProtoViewApp* app, InputEvent input) {
privdata->signal_display_start_row++; privdata->signal_display_start_row++;
} else if(input.type == InputTypePress && input.key == InputKeyLeft) { } else if(input.type == InputTypePress && input.key == InputKeyLeft) {
if(privdata->signal_display_start_row != 0) privdata->signal_display_start_row--; if(privdata->signal_display_start_row != 0) privdata->signal_display_start_row--;
} else if(input.type == InputTypePress && input.key == InputKeyOk) { } else if(input.type == InputTypeLong && input.key == InputKeyOk) {
privdata->filename = malloc(SAVE_FILENAME_LEN); privdata->filename = malloc(SAVE_FILENAME_LEN);
set_signal_random_filename(app, privdata->filename, SAVE_FILENAME_LEN); set_signal_random_filename(app, privdata->filename, SAVE_FILENAME_LEN);
show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, text_input_done_callback); show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, text_input_done_callback);
} else if(input.type == InputTypeShort && input.key == InputKeyOk) {
SendSignalCtx send_state;
send_signal_init(&send_state, app);
radio_tx_signal(app, radio_tx_feed_data, &send_state);
notify_signal_sent(app);
} }
} }
} }