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
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
app->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(app->view_port, render_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) {
|
||||
furi_assert(app);
|
||||
|
||||
// Put CC1101 on sleep.
|
||||
// Put CC1101 on sleep, this also restores charging.
|
||||
radio_sleep(app);
|
||||
|
||||
// View related.
|
||||
@@ -193,6 +194,7 @@ void protoview_app_free(ProtoViewApp* app) {
|
||||
gui_remove_view_port(app->gui, app->view_port);
|
||||
view_port_free(app->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_message_queue_free(app->event_queue);
|
||||
app->gui = NULL;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ typedef struct ProtoViewApp ProtoViewApp;
|
||||
typedef enum {
|
||||
TxRxStateIDLE,
|
||||
TxRxStateRx,
|
||||
TxRxStateTx,
|
||||
TxRxStateSleep,
|
||||
} TxRxState;
|
||||
|
||||
@@ -112,9 +113,11 @@ typedef struct ProtoViewMsgInfo {
|
||||
integer. */
|
||||
} ProtoViewMsgInfo;
|
||||
|
||||
/* Our main application context. */
|
||||
struct ProtoViewApp {
|
||||
/* GUI */
|
||||
Gui* gui;
|
||||
NotificationApp* notification;
|
||||
ViewPort* view_port; /* We just use a raw viewport and we render
|
||||
everything into the low level canvas. */
|
||||
ProtoViewCurrentView current_view; /* Active left-right view ID. */
|
||||
@@ -182,6 +185,7 @@ void radio_rx_end(ProtoViewApp* app);
|
||||
void radio_sleep(ProtoViewApp* app);
|
||||
void raw_sampling_worker_start(ProtoViewApp* app);
|
||||
void raw_sampling_worker_stop(ProtoViewApp* app);
|
||||
void radio_tx_signal(ProtoViewApp* app, FuriHalSubGhzAsyncTxCallback data_feeder, void* ctx);
|
||||
|
||||
/* signal.c */
|
||||
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_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 modulation "custom" field is NULL, or a custom preset we
|
||||
* defined in custom_presets.h. */
|
||||
@@ -50,6 +54,8 @@ void radio_begin(ProtoViewApp* app) {
|
||||
app->txrx->txrx_state = TxRxStateIDLE;
|
||||
}
|
||||
|
||||
/* ================================= Reception ============================== */
|
||||
|
||||
/* Setup subghz to start receiving using a background worker. */
|
||||
uint32_t radio_rx(ProtoViewApp* app) {
|
||||
furi_assert(app);
|
||||
@@ -78,6 +84,7 @@ uint32_t radio_rx(ProtoViewApp* app) {
|
||||
/* Stop subghz worker (if active), put radio on idle state. */
|
||||
void radio_rx_end(ProtoViewApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
if(app->txrx->txrx_state == TxRxStateRx) {
|
||||
if(!app->txrx->debug_timer_sampling) {
|
||||
if(subghz_worker_is_running(app->txrx->worker)) {
|
||||
@@ -102,6 +109,33 @@ void radio_sleep(ProtoViewApp* app) {
|
||||
}
|
||||
furi_hal_subghz_sleep();
|
||||
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 =============================
|
||||
|
||||
@@ -118,6 +118,26 @@ uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx) {
|
||||
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)
|
||||
* 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
|
||||
@@ -179,6 +199,7 @@ void scan_for_signal(ProtoViewApp* app) {
|
||||
app->us_scale = 10;
|
||||
else if(DetectedSamples->short_pulse_dur < 145)
|
||||
app->us_scale = 30;
|
||||
notify_signal_detected(app, decoded);
|
||||
} else {
|
||||
/* If the structure was not filled, discard it. Otherwise
|
||||
* now the owner is app->msg_info. */
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <gui/view_i.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 {
|
||||
SubViewInfoMain,
|
||||
SubViewInfoSave,
|
||||
@@ -90,7 +92,7 @@ static void render_subview_save(Canvas* const canvas, ProtoViewApp* app) {
|
||||
}
|
||||
|
||||
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. */
|
||||
@@ -147,6 +149,116 @@ void set_signal_random_filename(ProtoViewApp* app, char* buf, size_t buflen) {
|
||||
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. */
|
||||
void process_input_info(ProtoViewApp* app, InputEvent input) {
|
||||
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++;
|
||||
} else if(input.type == InputTypePress && input.key == InputKeyLeft) {
|
||||
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);
|
||||
set_signal_random_filename(app, privdata->filename, SAVE_FILENAME_LEN);
|
||||
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