mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
Update protoview
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 =============================
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user