Bläddra i källkod

Merge cross_remote from https://github.com/leedave/flipper-zero-cross-remote

Willy-JL 1 år sedan
förälder
incheckning
2f055e41e1

+ 9 - 0
cross_remote/.gitignore

@@ -0,0 +1,9 @@
+dist/*
+.vscode
+.clang-format
+.clangd
+.editorconfig
+.env
+.ufbt
+.clang-format
+.vscode/settings.json

+ 6 - 0
cross_remote/README.md

@@ -29,6 +29,7 @@ Wouldn't it be nicer to simply click one button and let everything happen? This
 - Save chained commands to a file<br>
 - Add pauses, becaue target systems are not always fast enough for multiple commands<br>
 - Run file containing chained IR & SubGhz commands<br>
+- Loop Transmissions until quit
 
 ### Settings
 - LED FX, allow the LED to blink
@@ -52,5 +53,10 @@ Then run the command:
  ```
 The application will be compiled and copied onto your device. 
 
+## Thank you notes
+- [Willy-JL](https://github.com/Willy-JL) for distributing in Momentum Firmware
+- [Roguemaster](https://github.com/RogueMaster/flipperzero-firmware-wPlugins) for distributing in Roguemaster Firmware
+- [Miccayo](https://github.com/miccayo) for contributing the loop transmit feature
+
 ## Licensing
 This code is open-source and may be used for whatever you want to do with it. 

+ 1 - 1
cross_remote/application.fam

@@ -6,7 +6,7 @@ App(
     stack_size=3 * 1024,
     fap_icon="icons/xremote_10px.png",
     fap_icon_assets="icons",
-    fap_version="2.7",
+    fap_version="3.0",
     fap_category="Infrared",
     fap_author="Leedave",
     fap_description="One-Click, sends multiple commands",

+ 1 - 0
cross_remote/docs/README.md

@@ -11,6 +11,7 @@ This app combines your IR and SubGhz commands into a playlist that can be run wi
 - Disable LED effects if not wanted
 - Configure duration of IR Signals
 - Configure default duration of Encoded SubGhz Signals
+- Loop Transmissions until quit
 
 ## What good is this?
 

+ 8 - 0
cross_remote/docs/changelog.md

@@ -1,3 +1,11 @@
+## 3.0
+- Added loop transmit feature (thanks to miccayo)
+- Replaced transmission counter with animations
+- Refactored the transmission part to allow interruption of command chains
+
+## 2.8
+- Update SubGhz Protocoll to include flippers official rolling code support
+
 ## 2.7
 - Replaced custom keyboard for timing with new number_input from Firmware
 - Requires minimum OFW version 0.105.0 or custom firmware based on this

+ 1 - 0
cross_remote/helpers/subghz/subghz.c

@@ -9,6 +9,7 @@ SubGhz* subghz_alloc() {
 
     subghz->file_path = furi_string_alloc();
     subghz->txrx = subghz_txrx_alloc();
+    subghz_txrx_set_need_save_callback(subghz->txrx, subghz_save_to_file, subghz);
     subghz->dialogs = furi_record_open(RECORD_DIALOGS);
 
     return subghz;

+ 56 - 2
cross_remote/helpers/subghz/subghz_i.c

@@ -188,6 +188,60 @@ SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz) {
     return subghz->load_type_file;
 }
 
+bool subghz_save_protocol_to_file(
+    SubGhz* subghz,
+    FlipperFormat* flipper_format,
+    const char* dev_file_name) {
+    furi_assert(subghz);
+    furi_assert(flipper_format);
+    furi_assert(dev_file_name);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Stream* flipper_format_stream = flipper_format_get_raw_stream(flipper_format);
+
+    bool saved = false;
+    FuriString* file_dir = furi_string_alloc();
+
+    path_extract_dirname(dev_file_name, file_dir);
+    do {
+        //removing additional fields
+        flipper_format_delete_key(flipper_format, "Repeat");
+        flipper_format_delete_key(flipper_format, "Manufacture");
+
+        // Create subghz folder directory if necessary
+        if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) {
+            dialog_message_show_storage_error(subghz->dialogs, "Cannot create\nfolder");
+            break;
+        }
+
+        if(!storage_simply_remove(storage, dev_file_name)) {
+            break;
+        }
+        stream_seek(flipper_format_stream, 0, StreamOffsetFromStart);
+        stream_save_to_file(flipper_format_stream, storage, dev_file_name, FSOM_CREATE_ALWAYS);
+
+        if(storage_common_stat(storage, dev_file_name, NULL) != FSE_OK) {
+            break;
+        }
+
+        saved = true;
+    } while(0);
+    furi_string_free(file_dir);
+    furi_record_close(RECORD_STORAGE);
+    return saved;
+}
+
+void subghz_save_to_file(void* context) {
+    furi_assert(context);
+    SubGhz* subghz = context;
+    if(subghz_path_is_file(subghz->file_path)) {
+        subghz_save_protocol_to_file(
+            subghz,
+            subghz_txrx_get_fff_data(subghz->txrx),
+            furi_string_get_cstr(subghz->file_path));
+    }
+}
+
 bool subghz_load_protocol_from_file(SubGhz* subghz, const char* path) {
     furi_assert(subghz);
 
@@ -214,9 +268,9 @@ bool subghz_load_protocol_from_file(SubGhz* subghz, const char* path) {
     return ret;
 }*/
 
-/*bool subghz_path_is_file(FuriString* path) {
+bool subghz_path_is_file(FuriString* path) {
     return furi_string_end_with(path, SUBGHZ_APP_FILENAME_EXTENSION);
-}*/
+}
 
 /*void subghz_lock(SubGhz* subghz) {
     furi_assert(subghz);

+ 7 - 0
cross_remote/helpers/subghz/subghz_i.h

@@ -37,6 +37,7 @@
 #include "subghz_txrx.h"
 
 #define SUBGHZ_MAX_LEN_NAME 64
+#define SUBGHZ_APP_FILENAME_EXTENSION ".sub"
 
 typedef struct SubGhz SubGhz;
 
@@ -67,6 +68,12 @@ struct SubGhz {
 //void subghz_set_default_preset(SubGhz* subghz);
 //void subghz_blink_start(SubGhz* subghz);
 //void subghz_blink_stop(SubGhz* subghz);
+bool subghz_save_protocol_to_file(
+    SubGhz* subghz,
+    FlipperFormat* flipper_format,
+    const char* dev_file_name);
+void subghz_save_to_file(void* context);
+bool subghz_path_is_file(FuriString* path);
 
 // Used on Encoded SubGhz
 bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);

+ 6 - 23
cross_remote/helpers/subghz/subghz_txrx.c

@@ -27,7 +27,7 @@ static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
     if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
 }
 
-SubGhzTxRx* subghz_txrx_alloc() {
+SubGhzTxRx* subghz_txrx_alloc(void) {
     SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx));
     instance->setting = subghz_setting_alloc();
     subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user"));
@@ -71,15 +71,12 @@ SubGhzTxRx* subghz_txrx_alloc() {
     instance->radio_device_type =
         subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101);
 
-    FURI_LOG_D(TAG, "completed TXRX alloc");
-
     return instance;
 }
 
 void subghz_txrx_free(SubGhzTxRx* instance) {
     furi_assert(instance);
-    FURI_LOG_D(TAG, "freeing TXRX");
-
+    
     if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
         subghz_txrx_radio_device_power_off(instance);
         subghz_devices_end(instance->radio_device);
@@ -194,7 +191,6 @@ static void subghz_txrx_idle(SubGhzTxRx* instance) {
         subghz_txrx_speaker_off(instance);
         instance->txrx_state = SubGhzTxRxStateIDLE;
     }
-    FURI_LOG_D(TAG, "completed subghz_txrx_idle");
 }
 
 /*static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
@@ -228,7 +224,6 @@ static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) {
         instance->txrx_state = SubGhzTxRxStateTx;
     }
 
-    FURI_LOG_D(TAG, "completed subghz_txrx_tx");
     return ret;
 }
 
@@ -241,10 +236,7 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
     SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
     FuriString* temp_str = furi_string_alloc();
     uint32_t repeat = 200;
-
-    FURI_LOG_D(TAG, "starting loop in subghz_txrx_tx_start");
     do {
-        FURI_LOG_D(TAG, "looping");
         if(!flipper_format_rewind(flipper_format)) {
             FURI_LOG_E(TAG, "Rewind error");
             break;
@@ -257,7 +249,6 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
             FURI_LOG_E(TAG, "Unable Repeat");
             break;
         }
-        //FURI_LOG_D(TAG, "File loaded");
         ret = SubGhzTxRxStartTxStateOk;
 
         SubGhzRadioPreset* preset = instance->preset;
@@ -265,24 +256,18 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
             subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
 
         if(instance->transmitter) {
-            FURI_LOG_D(TAG, "transmitter found");
             if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
                SubGhzProtocolStatusOk) {
-                //if (false) {
-                FURI_LOG_D(TAG, "deserialization");
                 if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
-                    FURI_LOG_D(TAG, "got preset name");
                     subghz_txrx_begin(
                         instance,
                         subghz_setting_get_preset_data_by_name(
                             instance->setting, furi_string_get_cstr(preset->name)));
-                    FURI_LOG_D(TAG, "loaded preset by name");
                     if(preset->frequency) {
                         if(!subghz_txrx_tx(instance, preset->frequency)) {
                             FURI_LOG_E(TAG, "Only Rx");
                             ret = SubGhzTxRxStartTxStateErrorOnlyRx;
                         }
-                        FURI_LOG_D(TAG, "got frequency");
                     } else {
                         ret = SubGhzTxRxStartTxStateErrorParserOthers;
                     }
@@ -295,12 +280,10 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
 
                 if(ret == SubGhzTxRxStartTxStateOk) {
                     //Start TX
-                    FURI_LOG_D(TAG, "starting Async TX");
                     subghz_devices_start_async_tx(
                         instance->radio_device, subghz_transmitter_yield, instance->transmitter);
                 }
             } else {
-                FURI_LOG_D(TAG, "no deserialization");
                 ret = SubGhzTxRxStartTxStateErrorParserOthers;
             }
         } else {
@@ -328,14 +311,14 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
     subghz_txrx_rx(instance, instance->preset->frequency);
 }*/
 
-/*void subghz_txrx_set_need_save_callback(
+void subghz_txrx_set_need_save_callback(
     SubGhzTxRx* instance,
     SubGhzTxRxNeedSaveCallback callback,
     void* context) {
     furi_assert(instance);
     instance->need_save_callback = callback;
     instance->need_save_context = context;
-}*/
+}
 
 static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
     furi_assert(instance);
@@ -346,11 +329,11 @@ static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
     subghz_transmitter_free(instance->transmitter);
 
     //if protocol dynamic then we save the last upload
-    /*if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+    if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
         if(instance->need_save_callback) {
             instance->need_save_callback(instance->need_save_context);
         }
-    }*/
+    }
     subghz_txrx_idle(instance);
     subghz_txrx_speaker_off(instance);
 }

+ 2 - 2
cross_remote/helpers/subghz/subghz_txrx.h

@@ -221,10 +221,10 @@ SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance);
  * @param callback Callback for save data
  * @param context Context for callback
  */
-/*void subghz_txrx_set_need_save_callback(
+void subghz_txrx_set_need_save_callback(
     SubGhzTxRx* instance,
     SubGhzTxRxNeedSaveCallback callback,
-    void* context);*/
+    void* context);
 
 /**
  * Get pointer to a load data key

+ 2 - 0
cross_remote/helpers/xremote_storage.c

@@ -59,6 +59,7 @@ void xremote_save_settings(void* context) {
         fff_file, XREMOTE_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
     flipper_format_write_uint32(fff_file, XREMOTE_SETTINGS_KEY_IR_TIMING, &app->ir_timing, 1);
     flipper_format_write_uint32(fff_file, XREMOTE_SETTINGS_KEY_SG_TIMING, &app->sg_timing, 1);
+    flipper_format_write_uint32(fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
 
     if(!flipper_format_rewind(fff_file)) {
         xremote_close_config_file(fff_file);
@@ -112,6 +113,7 @@ void xremote_read_settings(void* context) {
         fff_file, XREMOTE_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
     flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_IR_TIMING, &app->ir_timing, 1);
     flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_SG_TIMING, &app->sg_timing, 1);
+    flipper_format_read_uint32(fff_file, XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT, &app->loop_transmit, 1);
 
     flipper_format_rewind(fff_file);
 

+ 1 - 0
cross_remote/helpers/xremote_storage.h

@@ -13,6 +13,7 @@
 #define XREMOTE_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
 #define XREMOTE_SETTINGS_KEY_IR_TIMING "IRTiming"
 #define XREMOTE_SETTINGS_KEY_SG_TIMING "SGTiming"
+#define XREMOTE_SETTINGS_KEY_LOOP_TRANSMIT "LoopTransmit"
 
 void xremote_save_settings(void* context);
 void xremote_read_settings(void* context);

BIN
cross_remote/icons/KeyBackspaceSelected_16x9.png


BIN
cross_remote/icons/KeyBackspace_16x9.png


BIN
cross_remote/icons/KeySaveSelected_24x11.png


BIN
cross_remote/icons/KeySave_24x11.png


BIN
cross_remote/icons/ir_ani_1_32x22.png


BIN
cross_remote/icons/ir_ani_2_32x22.png


BIN
cross_remote/icons/ir_ani_32x22.png


BIN
cross_remote/icons/ir_ani_3_32x22.png


BIN
cross_remote/icons/pause_ani_1_22x23.png


BIN
cross_remote/icons/pause_ani_2_22x23.png


BIN
cross_remote/icons/pause_ani_3_22x23.png


BIN
cross_remote/icons/sg_ani_1_19x13.png


BIN
cross_remote/icons/sg_ani_2_19x13.png


BIN
cross_remote/icons/sg_ani_3_19x13.png


+ 23 - 2
cross_remote/scenes/xremote_scene_settings.c

@@ -33,7 +33,14 @@ const uint32_t led_value[2] = {
     XRemoteLedOff,
     XRemoteLedOn,
 };
-
+const char* const loop_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t loop_value[2] = {
+    XRemoteLoopOff,
+    XRemoteLoopOn,
+};
 const char* const settings_text[2] = {
     "OFF",
     "ON",
@@ -72,6 +79,13 @@ static void xremote_scene_settings_set_save_settings(VariableItem* item) {
     app->save_settings = settings_value[index];
 }
 
+static void xremote_scene_settings_set_loop(VariableItem* item) {
+    XRemote* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, loop_text[index]);
+    app->loop_transmit = loop_value[index];
+}
+
 static void xremote_scene_settings_set_ir_timing(VariableItem* item) {
     XRemote* app = variable_item_get_context(item);
     uint32_t index = variable_item_get_current_value_index(item);
@@ -116,11 +130,18 @@ void xremote_scene_settings_on_enter(void* context) {
 
     // LED Effects on/off
     item = variable_item_list_add(
-        app->variable_item_list, "LED FX:", 2, xremote_scene_settings_set_led, app);
+        app->variable_item_list, "LED FX", 2, xremote_scene_settings_set_led, app);
     value_index = value_index_uint32(app->led, led_value, 2);
     variable_item_set_current_value_index(item, value_index);
     variable_item_set_current_value_text(item, led_text[value_index]);
 
+    /* NEW: Loop saved command functionality */
+    item = variable_item_list_add(
+        app->variable_item_list, "Loop Transmit", 2, xremote_scene_settings_set_loop, app);
+    value_index = value_index_uint32(app->loop_transmit, loop_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, loop_text[value_index]);
+
     // Save Settings to File
     item = variable_item_list_add(
         app->variable_item_list, "Save Settings", 2, xremote_scene_settings_set_save_settings, app);

+ 43 - 30
cross_remote/scenes/xremote_scene_transmit.c

@@ -18,9 +18,10 @@ static const NotificationSequence* xremote_notification_sequences[] = {
     &sequence_blink_stop,
 };
 
-void xremote_transmit_callback(XRemoteCustomEvent event, void* context) {
+void xremote_scene_transmit_callback(XRemoteCustomEvent event, void* context) {
     furi_assert(context);
     XRemote* app = context;
+    FURI_LOG_D(TAG, "trigger xremote_transmit_callback");
     view_dispatcher_send_custom_event(app->view_dispatcher, event);
 }
 
@@ -114,61 +115,69 @@ void xremote_scene_transmit_send_signal(void* context, CrossRemoteItem* item) {
     }
 }
 
-void xremote_scene_transmit_run_remote(void* context) {
-    furi_assert(context);
-    XRemote* app = context;
+static void xremote_scene_transmit_end_scene(XRemote* app) {
+    xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+static void xremote_scene_transmit_run_single_transmit(XRemote* app) {
     CrossRemote* remote = app->cross_remote;
+    if (xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingIdle) {
+        xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStart);
+        CrossRemoteItem* item = xremote_cross_remote_get_item(remote, app->transmit_item);
+        xremote_scene_transmit_send_signal(app, item);
+    } else if (xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStopSubghz) {
+        app->transmit_item++;
+        app->state_notifications = SubGhzNotificationStateIDLE;
+        app->transmitting = false;
+        subghz_txrx_stop(app->subghz->txrx);
+        xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
+        xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+    } else if (xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStop) {
+        app->transmit_item++;
+        xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
+    }
+}
 
+static void xremote_scene_transmit_run_next_transmission(XRemote* app) {
+    CrossRemote* remote = app->cross_remote;
     size_t item_count = xremote_cross_remote_get_item_count(remote);
-    for(size_t i = 0; i < item_count;) {
-        if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingIdle) {
-            xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingStart);
-            CrossRemoteItem* item = xremote_cross_remote_get_item(remote, i);
-            xremote_scene_transmit_send_signal(app, item);
-            //furi_thread_flags_wait(0, FuriFlagWaitAny, 1000);
-        } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStopSubghz) {
-            i++;
-            app->state_notifications = SubGhzNotificationStateIDLE;
-            app->transmitting = false;
-            subghz_txrx_stop(app->subghz->txrx);
-            xremote_scene_ir_notification_message(app, SubGhzNotificationMessageBlinkStop);
-            xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
-            //furi_thread_flags_wait(0, FuriFlagWaitAny, 1000);
-        } else if(xremote_cross_remote_get_transmitting(remote) == XRemoteTransmittingStop) {
-            i++;
-            xremote_cross_remote_set_transmitting(remote, XRemoteTransmittingIdle);
-        }
+    if (app->transmit_item < item_count) {
+        xremote_scene_transmit_run_single_transmit(app);
+        return;
     }
-    xremote_scene_ir_notification_message(app, InfraredNotificationMessageBlinkStop);
-
-    scene_manager_previous_scene(app->scene_manager);
-    //xremote_transmit_model_set_name(app->xremote_transmit, xremote_cross_remote_get_name(remote));
+    if (app->loop_transmit && !app->stop_transmit) {
+        app->transmit_item = 0;
+        return;
+    }
+    xremote_scene_transmit_end_scene(app);
 }
 
 void xremote_scene_transmit_on_enter(void* context) {
     furi_assert(context);
     XRemote* app = context;
-    xremote_transmit_set_callback(app->xremote_transmit, xremote_transmit_callback, app);
+    app->transmit_item = 0;
+    xremote_transmit_set_callback(app->xremote_transmit, xremote_scene_transmit_callback, app);
 
     view_dispatcher_switch_to_view(app->view_dispatcher, XRemoteViewIdTransmit);
-    xremote_scene_transmit_run_remote(app);
 }
 
 bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
     XRemote* app = context;
     bool consumed = false;
-
+    
     if(event.type == SceneManagerEventTypeCustom) {
         FURI_LOG_D(TAG, "Custom Event");
         switch(event.event) {
         case XRemoteCustomEventViewTransmitterSendStop:
-            FURI_LOG_D("SUBGHZ", "stop event"); // doesn't trigger
+            app->stop_transmit = true;
             break;
         default:
             break;
         }
     } else if(event.type == SceneManagerEventTypeTick) {
         FURI_LOG_D(TAG, "Tick Event");
+        xremote_scene_transmit_run_next_transmission(app);
         with_view_model(
             xremote_transmit_get_view(app->xremote_transmit),
             void* model,
@@ -177,6 +186,10 @@ bool xremote_scene_transmit_on_event(void* context, SceneManagerEvent event) {
         if(app->state_notifications == SubGhzNotificationStateTx && app->led == 1) {
             //blink for subghz
         }
+        if(app->stop_transmit == true) {
+            app->stop_transmit = false;
+            xremote_scene_transmit_end_scene(app);
+        }
     }
 
     return consumed;

+ 46 - 29
cross_remote/views/xremote_transmit.c

@@ -50,12 +50,16 @@ void xremote_transmit_draw_ir(Canvas* canvas, XRemoteTransmitModel* model) {
     canvas_set_font(canvas, FontPrimary);
     canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Sending");
     canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "Infrared");
-    canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
-
-    char temp_str[18];
-    snprintf(temp_str, 18, "%u", model->time);
-    canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+    canvas_draw_str_aligned(canvas, 74, 15, AlignLeft, AlignTop, "Infrared");
+    canvas_draw_str_aligned(canvas, 74, 25, AlignLeft, AlignTop, model->name);
+
+    if (model->time == 0) {
+        canvas_draw_icon(canvas, 36, 2, &I_ir_ani_1_32x22);
+    } else if (model->time == 1) {
+        canvas_draw_icon(canvas, 36, 2, &I_ir_ani_2_32x22);
+    } else if (model->time == 2) {
+        canvas_draw_icon(canvas, 36, 2, &I_ir_ani_3_32x22);
+    }
 }
 
 void xremote_transmit_draw_pause(Canvas* canvas, XRemoteTransmitModel* model) {
@@ -66,12 +70,16 @@ void xremote_transmit_draw_pause(Canvas* canvas, XRemoteTransmitModel* model) {
     canvas_set_font(canvas, FontPrimary);
     canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Waiting");
     canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "Sequence");
-    canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
-
-    char temp_str[18];
-    snprintf(temp_str, 18, "%u", model->time);
-    canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+    canvas_draw_str_aligned(canvas, 74, 15, AlignLeft, AlignTop, "Sequence");
+    canvas_draw_str_aligned(canvas, 74, 25, AlignLeft, AlignTop, model->name);
+
+    if (model->time == 0) {
+        canvas_draw_icon(canvas, 9, 28, &I_pause_ani_1_22x23);
+    } else if (model->time == 1) {
+        canvas_draw_icon(canvas, 9, 28, &I_pause_ani_2_22x23);
+    } else if (model->time == 2) {
+        canvas_draw_icon(canvas, 9, 28, &I_pause_ani_3_22x23);
+    }
 }
 
 void xremote_transmit_draw_subghz(Canvas* canvas, XRemoteTransmitModel* model) {
@@ -82,12 +90,16 @@ void xremote_transmit_draw_subghz(Canvas* canvas, XRemoteTransmitModel* model) {
     canvas_set_font(canvas, FontPrimary);
     canvas_draw_str_aligned(canvas, 74, 5, AlignLeft, AlignTop, "Sending");
     canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str_aligned(canvas, 74, 20, AlignLeft, AlignTop, "SubGhz");
-    canvas_draw_str_aligned(canvas, 74, 30, AlignLeft, AlignTop, model->name);
-
-    char temp_str[18];
-    snprintf(temp_str, 18, "%u", model->time);
-    canvas_draw_str_aligned(canvas, 74, 40, AlignLeft, AlignTop, temp_str);
+    canvas_draw_str_aligned(canvas, 74, 15, AlignLeft, AlignTop, "SubGhz");
+    canvas_draw_str_aligned(canvas, 74, 25, AlignLeft, AlignTop, model->name);
+
+    if (model->time == 0) {
+        canvas_draw_icon(canvas, 15, 1, &I_sg_ani_1_19x13);
+    } else if (model->time == 1) {
+        canvas_draw_icon(canvas, 15, 1, &I_sg_ani_2_19x13);
+    } else if (model->time == 2) {
+        canvas_draw_icon(canvas, 15, 1, &I_sg_ani_3_19x13);
+    }
 }
 
 void xremote_transmit_draw(Canvas* canvas, XRemoteTransmitModel* model) {
@@ -98,6 +110,10 @@ void xremote_transmit_draw(Canvas* canvas, XRemoteTransmitModel* model) {
     } else if(model->type == XRemoteRemoteItemTypePause) {
         xremote_transmit_draw_pause(canvas, model);
     }
+    if (model->time > 2) {
+        model->time = 0;
+    }
+    elements_button_right(canvas, "exit");
 }
 
 bool xremote_transmit_input(InputEvent* event, void* context) {
@@ -106,6 +122,7 @@ bool xremote_transmit_input(InputEvent* event, void* context) {
     if(event->type == InputTypeRelease) {
         switch(event->key) {
         case InputKeyBack:
+        case InputKeyRight:
             with_view_model(
                 instance->view,
                 XRemoteTransmitModel * model,
@@ -123,6 +140,16 @@ bool xremote_transmit_input(InputEvent* event, void* context) {
     return true;
 }
 
+void xremote_transmit_enter(void* context) {
+    furi_assert(context);
+    XRemoteTransmit* instance = (XRemoteTransmit*)context;
+    with_view_model(
+        instance->view,
+        XRemoteTransmitModel * model,
+        { xremote_transmit_model_init(model); },
+        true);
+}
+
 XRemoteTransmit* xremote_transmit_alloc() {
     XRemoteTransmit* instance = malloc(sizeof(XRemoteTransmit));
     instance->view = view_alloc();
@@ -130,7 +157,7 @@ XRemoteTransmit* xremote_transmit_alloc() {
     view_set_context(instance->view, instance);
     view_set_draw_callback(instance->view, (ViewDrawCallback)xremote_transmit_draw);
     view_set_input_callback(instance->view, xremote_transmit_input);
-    view_set_enter_callback(instance->view, xremote_transmit_enter);
+    //view_set_enter_callback(instance->view, xremote_transmit_enter);
 
     with_view_model(
         instance->view,
@@ -141,16 +168,6 @@ XRemoteTransmit* xremote_transmit_alloc() {
     return instance;
 }
 
-void xremote_transmit_enter(void* context) {
-    furi_assert(context);
-    XRemoteTransmit* instance = (XRemoteTransmit*)context;
-    with_view_model(
-        instance->view,
-        XRemoteTransmitModel * model,
-        { xremote_transmit_model_init(model); },
-        true);
-}
-
 void xremote_transmit_free(XRemoteTransmit* instance) {
     furi_assert(instance);
 

+ 3 - 1
cross_remote/xremote.c

@@ -51,6 +51,8 @@ XRemote* xremote_app_alloc() {
     app->sg_timing = 500;
     app->sg_timing_char = "500";
     app->stop_transmit = false;
+    app->loop_transmit = 0;
+    app->transmit_item = 0;
 
     // Load configs
     xremote_read_settings(app);
@@ -172,7 +174,7 @@ void xremote_app_free(XRemote* app) {
 
     app->gui = NULL;
     app->notification = NULL;
-
+    
     //Remove whatever is left
     free(app);
 }

+ 7 - 0
cross_remote/xremote.h

@@ -42,6 +42,7 @@ typedef struct {
     uint32_t speaker;
     uint32_t led;
     uint32_t save_settings;
+    uint32_t loop_transmit;
     uint32_t edit_item;
     uint32_t ir_timing;
     char* ir_timing_char;
@@ -49,6 +50,7 @@ typedef struct {
     char* sg_timing_char;
     bool transmitting;
     bool stop_transmit;
+    size_t transmit_item;
     char text_store[XREMOTE_TEXT_STORE_NUM][XREMOTE_TEXT_STORE_SIZE + 1];
     SubGhz* subghz;
     NumberInput* number_input;
@@ -85,6 +87,11 @@ typedef enum {
     XRemoteLedOn,
 } XRemoteLedState;
 
+typedef enum {
+    XRemoteLoopOff,
+    XRemoteLoopOn,
+} XRemoteLoopState;
+
 typedef enum {
     XRemoteSettingsOff,
     XRemoteSettingsOn,

+ 2 - 2
cross_remote/xremote_i.h

@@ -51,10 +51,10 @@
 #define XREMOTE_TEXT_STORE_SIZE 128
 #define XREMOTE_MAX_ITEM_NAME_LENGTH 22
 #define XREMOTE_MAX_REMOTE_NAME_LENGTH 22
-#define XREMOTE_VERSION "2.7"
+#define XREMOTE_VERSION "3.0"
 
 #define INFRARED_APP_EXTENSION ".ir"
-#define INFRARED_APP_FOLDER ANY_PATH("infrared")
+#define INFRARED_APP_FOLDER EXT_PATH("infrared")
 
 #define TAG "XRemote"