From 3174d29dfa87e8715d17896cf0452931928eab10 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 19 Jan 2023 20:28:02 +0300 Subject: [PATCH] Update protoview --- applications/plugins/protoview/app.c | 4 +- applications/plugins/protoview/app.h | 4 + applications/plugins/protoview/app_subghz.c | 34 ++++++ applications/plugins/protoview/signal.c | 21 ++++ applications/plugins/protoview/view_info.c | 121 +++++++++++++++++++- 5 files changed, 181 insertions(+), 3 deletions(-) diff --git a/applications/plugins/protoview/app.c b/applications/plugins/protoview/app.c index 33f740499..aa2cdf8a6 100644 --- a/applications/plugins/protoview/app.c +++ b/applications/plugins/protoview/app.c @@ -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; diff --git a/applications/plugins/protoview/app.h b/applications/plugins/protoview/app.h index beff44710..60fc12546 100644 --- a/applications/plugins/protoview/app.h +++ b/applications/plugins/protoview/app.h @@ -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); diff --git a/applications/plugins/protoview/app_subghz.c b/applications/plugins/protoview/app_subghz.c index 5bc6864fc..79a236f05 100644 --- a/applications/plugins/protoview/app_subghz.c +++ b/applications/plugins/protoview/app_subghz.c @@ -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 ============================= diff --git a/applications/plugins/protoview/signal.c b/applications/plugins/protoview/signal.c index c1f90b9ab..c5359de29 100644 --- a/applications/plugins/protoview/signal.c +++ b/applications/plugins/protoview/signal.c @@ -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. */ diff --git a/applications/plugins/protoview/view_info.c b/applications/plugins/protoview/view_info.c index 8416fd235..9b2c5d044 100644 --- a/applications/plugins/protoview/view_info.c +++ b/applications/plugins/protoview/view_info.c @@ -5,6 +5,8 @@ #include #include +/* 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); } } }