Ver código fonte

[FL-1652, FL-1554] IRDA: Continuous transmitting (#636)

* [FL-1652] IRDA: Continuous transmitting
* continuous encoding and sending signals by pressing button on menu
* fast buttons scrolling in remote menu
* bruteforce: stop reading file if progress == 100%
* IRDA: .hpp -> .h
* [FL-1554] IRDA: xTaskNotify -> osEventsFlagSet
* IRDA: some stability fixes
* Irda: minor cleanup, api-hal to furi-hal rename.

Co-authored-by: あく <alleteam@gmail.com>
Albert Kharisov 4 anos atrás
pai
commit
5ed9bdbc37
43 arquivos alterados com 799 adições e 213 exclusões
  1. 31 9
      applications/gui/modules/button_menu.c
  2. 1 1
      applications/gui/modules/button_menu.h
  3. 4 5
      applications/irda/cli/irda-cli.cpp
  4. 2 3
      applications/irda/irda-app-brute-force.cpp
  5. 6 4
      applications/irda/irda-app-brute-force.h
  6. 2 1
      applications/irda/irda-app-event.h
  7. 2 2
      applications/irda/irda-app-file-parser.cpp
  8. 10 3
      applications/irda/irda-app-file-parser.h
  9. 2 2
      applications/irda/irda-app-remote-manager.cpp
  10. 11 6
      applications/irda/irda-app-remote-manager.h
  11. 10 4
      applications/irda/irda-app-view-manager.cpp
  12. 1 2
      applications/irda/irda-app-view-manager.h
  13. 16 5
      applications/irda/irda-app.cpp
  14. 11 11
      applications/irda/irda-app.h
  15. 1 1
      applications/irda/irda-runner.cpp
  16. 1 1
      applications/irda/scene/irda-app-scene-edit-delete-done.cpp
  17. 2 2
      applications/irda/scene/irda-app-scene-edit-delete.cpp
  18. 1 1
      applications/irda/scene/irda-app-scene-edit-key-select.cpp
  19. 1 1
      applications/irda/scene/irda-app-scene-edit-rename-done.cpp
  20. 1 1
      applications/irda/scene/irda-app-scene-edit-rename.cpp
  21. 1 1
      applications/irda/scene/irda-app-scene-edit.cpp
  22. 1 1
      applications/irda/scene/irda-app-scene-learn-done.cpp
  23. 1 1
      applications/irda/scene/irda-app-scene-learn-enter-name.cpp
  24. 6 2
      applications/irda/scene/irda-app-scene-learn-success.cpp
  25. 7 9
      applications/irda/scene/irda-app-scene-learn.cpp
  26. 2 2
      applications/irda/scene/irda-app-scene-remote-list.cpp
  27. 56 8
      applications/irda/scene/irda-app-scene-remote.cpp
  28. 1 1
      applications/irda/scene/irda-app-scene-start.cpp
  29. 12 9
      applications/irda/scene/irda-app-scene-universal-common.cpp
  30. 2 2
      applications/irda/scene/irda-app-scene-universal-tv.cpp
  31. 1 1
      applications/irda/scene/irda-app-scene-universal.cpp
  32. 23 10
      applications/irda/scene/irda-app-scene.h
  33. 3 1
      applications/irda/view/irda-app-brut-view.c
  34. 1 1
      applications/irda/view/irda-app-brut-view.h
  35. 6 6
      applications/irda_monitor/irda_monitor.c
  36. 16 4
      firmware/targets/f6/furi-hal/furi-hal-irda.c
  37. 23 8
      firmware/targets/furi-hal-include/furi-hal-irda.h
  38. 30 0
      lib/irda/encoder_decoder/irda.c
  39. 24 0
      lib/irda/encoder_decoder/irda.h
  40. 2 1
      lib/irda/encoder_decoder/irda_protocol_defs_i.h
  41. 1 1
      lib/irda/worker/irda_transmit.c
  42. 378 61
      lib/irda/worker/irda_worker.c
  43. 86 18
      lib/irda/worker/irda_worker.h

+ 31 - 9
applications/gui/modules/button_menu.c

@@ -1,6 +1,7 @@
 #include "button_menu.h"
 #include "gui/canvas.h"
 #include "gui/elements.h"
+#include "input/input.h"
 #include <m-array.h>
 #include <furi.h>
 #include <stdint.h>
@@ -23,6 +24,7 @@ ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST);
 
 struct ButtonMenu {
     View* view;
+    bool freeze_input;
 };
 
 typedef struct {
@@ -158,7 +160,7 @@ static void button_menu_process_down(ButtonMenu* button_menu) {
         });
 }
 
-static void button_menu_process_ok(ButtonMenu* button_menu) {
+static void button_menu_process_ok(ButtonMenu* button_menu, InputType type) {
     furi_assert(button_menu);
 
     ButtonMenuItem* item = NULL;
@@ -168,11 +170,22 @@ static void button_menu_process_ok(ButtonMenu* button_menu) {
             if(model->position < (ButtonMenuItemArray_size(model->items))) {
                 item = ButtonMenuItemArray_get(model->items, model->position);
             }
-            return true;
+            return false;
         });
 
-    if(item && item->callback) {
-        item->callback(item->callback_context, item->index);
+    if(item->type == ButtonMenuItemTypeControl) {
+        if(type == InputTypeShort) {
+            if(item && item->callback) {
+                item->callback(item->callback_context, item->index, type);
+            }
+        }
+    }
+    if(item->type == ButtonMenuItemTypeCommon) {
+        if((type == InputTypePress) || (type == InputTypeRelease)) {
+            if(item && item->callback) {
+                item->callback(item->callback_context, item->index, type);
+            }
+        }
     }
 }
 
@@ -182,7 +195,19 @@ static bool button_menu_view_input_callback(InputEvent* event, void* context) {
     ButtonMenu* button_menu = context;
     bool consumed = false;
 
-    if(event->type == InputTypeShort) {
+    if(event->key == InputKeyOk) {
+        if((event->type == InputTypeRelease) || (event->type == InputTypePress)) {
+            consumed = true;
+            button_menu->freeze_input = (event->type == InputTypePress);
+            button_menu_process_ok(button_menu, event->type);
+        } else if(event->type == InputTypeShort) {
+            consumed = true;
+            button_menu_process_ok(button_menu, event->type);
+        }
+    }
+
+    if(!button_menu->freeze_input &&
+       ((event->type == InputTypeRepeat) || (event->type == InputTypeShort))) {
         switch(event->key) {
         case InputKeyUp:
             consumed = true;
@@ -192,10 +217,6 @@ static bool button_menu_view_input_callback(InputEvent* event, void* context) {
             consumed = true;
             button_menu_process_down(button_menu);
             break;
-        case InputKeyOk:
-            consumed = true;
-            button_menu_process_ok(button_menu);
-            break;
         default:
             break;
         }
@@ -272,6 +293,7 @@ ButtonMenu* button_menu_alloc(void) {
             return true;
         });
 
+    button_menu->freeze_input = false;
     return button_menu;
 }
 

+ 1 - 1
applications/gui/modules/button_menu.h

@@ -11,7 +11,7 @@ typedef struct ButtonMenu ButtonMenu;
 typedef struct ButtonMenuItem ButtonMenuItem;
 
 /* Callback for any button menu actions */
-typedef void (*ButtonMenuItemCallback)(void* context, int32_t index);
+typedef void (*ButtonMenuItemCallback)(void* context, int32_t index, InputType type);
 
 /* Type of button. Difference in drawing buttons. */
 typedef enum {

+ 4 - 5
applications/irda/cli/irda-cli.cpp

@@ -19,7 +19,7 @@ static void signal_received_callback(void* context, IrdaWorkerSignal* received_s
     Cli* cli = (Cli*)context;
 
     if(irda_worker_signal_is_decoded(received_signal)) {
-        const IrdaMessage* message = irda_worker_get_decoded_message(received_signal);
+        const IrdaMessage* message = irda_worker_get_decoded_signal(received_signal);
         buf_cnt = sniprintf(
             buf,
             sizeof(buf),
@@ -54,16 +54,15 @@ static void irda_cli_start_ir_rx(Cli* cli, string_t args, void* context) {
     }
 
     IrdaWorker* worker = irda_worker_alloc();
-    irda_worker_set_context(worker, cli);
-    irda_worker_start(worker);
-    irda_worker_set_received_signal_callback(worker, signal_received_callback);
+    irda_worker_rx_start(worker);
+    irda_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
 
     printf("Receiving IRDA...\r\nPress Ctrl+C to abort\r\n");
     while(!cli_cmd_interrupt_received(cli)) {
         delay(50);
     }
 
-    irda_worker_stop(worker);
+    irda_worker_rx_stop(worker);
     irda_worker_free(worker);
 }
 

+ 2 - 3
applications/irda/irda-app-brute-force.cpp

@@ -1,5 +1,5 @@
-#include "irda-app-brute-force.hpp"
-#include "irda/irda-app-file-parser.hpp"
+#include "irda-app-brute-force.h"
+#include "irda/irda-app-file-parser.h"
 #include "m-string.h"
 #include <file-worker-cpp.h>
 #include <memory>
@@ -47,7 +47,6 @@ void IrdaAppBruteForce::stop_bruteforce() {
     }
 }
 
-// TODO: [FL-1418] replace with timer-chained consequence of messages.
 bool IrdaAppBruteForce::send_next_bruteforce(void) {
     furi_assert(current_record.size());
     furi_assert(file_parser);

+ 6 - 4
applications/irda/irda-app-brute-force.hpp → applications/irda/irda-app-brute-force.h

@@ -1,7 +1,7 @@
 #pragma once
 #include "furi/check.h"
 #include <unordered_map>
-#include "irda-app-file-parser.hpp"
+#include "irda-app-file-parser.h"
 #include <memory>
 
 class IrdaAppBruteForce {
@@ -28,7 +28,9 @@ public:
     bool start_bruteforce(int index, int& record_amount);
     void add_record(int index, const char* name);
 
-    IrdaAppBruteForce(const char* filename) : universal_db_filename (filename) {}
-    ~IrdaAppBruteForce() {}
+    IrdaAppBruteForce(const char* filename)
+        : universal_db_filename(filename) {
+    }
+    ~IrdaAppBruteForce() {
+    }
 };
-

+ 2 - 1
applications/irda/irda-app-event.hpp → applications/irda/irda-app-event.h

@@ -9,6 +9,8 @@ public:
         Exit,
         Back,
         MenuSelected,
+        MenuSelectedPress,
+        MenuSelectedRelease,
         DialogExSelected,
         NextScene,
         IrdaMessageReceived,
@@ -24,4 +26,3 @@ public:
 
     Type type;
 };
-

+ 2 - 2
applications/irda/irda-app-file-parser.cpp

@@ -1,6 +1,6 @@
-#include "irda-app-file-parser.hpp"
+#include "irda-app-file-parser.h"
 #include "furi/check.h"
-#include "irda-app-remote-manager.hpp"
+#include "irda-app-remote-manager.h"
 #include "irda-app-signal.h"
 #include "m-string.h"
 #include <text-store.h>

+ 10 - 3
applications/irda/irda-app-file-parser.hpp → applications/irda/irda-app-file-parser.h

@@ -26,8 +26,16 @@ public:
     std::string make_name(const std::string& full_name) const;
 
 private:
-    size_t stringify_message(const IrdaAppSignal& signal, const char* name, char* content, size_t content_len);
-    size_t stringify_raw_signal(const IrdaAppSignal& signal, const char* name, char* content, size_t content_len);
+    size_t stringify_message(
+        const IrdaAppSignal& signal,
+        const char* name,
+        char* content,
+        size_t content_len);
+    size_t stringify_raw_signal(
+        const IrdaAppSignal& signal,
+        const char* name,
+        char* content,
+        size_t content_len);
     std::unique_ptr<IrdaFileSignal> parse_signal(const std::string& str) const;
     std::unique_ptr<IrdaFileSignal> parse_signal_raw(const std::string& str) const;
     std::string make_full_name(const std::string& name) const;
@@ -41,4 +49,3 @@ private:
     char file_buf[128];
     size_t file_buf_cnt = 0;
 };
-

+ 2 - 2
applications/irda/irda-app-remote-manager.cpp

@@ -1,4 +1,4 @@
-#include "irda-app-remote-manager.hpp"
+#include "irda-app-remote-manager.h"
 #include <storage/storage.h>
 #include "furi.h"
 #include "furi/check.h"
@@ -8,7 +8,7 @@
 #include <stdint.h>
 #include <string>
 #include <utility>
-#include "irda-app-file-parser.hpp"
+#include "irda-app-file-parser.h"
 
 static const std::string default_remote_name = "remote";
 

+ 11 - 6
applications/irda/irda-app-remote-manager.hpp → applications/irda/irda-app-remote-manager.h

@@ -12,21 +12,27 @@ class IrdaAppRemoteButton {
     friend class IrdaAppRemoteManager;
     std::string name;
     IrdaAppSignal signal;
+
 public:
     IrdaAppRemoteButton(const char* name, const IrdaAppSignal& signal)
-        : name(name), signal (signal) {}
-    ~IrdaAppRemoteButton() {}
+        : name(name)
+        , signal(signal) {
+    }
+    ~IrdaAppRemoteButton() {
+    }
 };
 
 class IrdaAppRemote {
     friend class IrdaAppRemoteManager;
     std::vector<IrdaAppRemoteButton> buttons;
     std::string name;
+
 public:
-    IrdaAppRemote(const std::string& name) : name(name) {}
+    IrdaAppRemote(const std::string& name)
+        : name(name) {
+    }
 
-    IrdaAppRemote& operator=(std::string& new_name) noexcept
-    {
+    IrdaAppRemote& operator=(std::string& new_name) noexcept {
         name = new_name;
         buttons.clear();
         return *this;
@@ -61,4 +67,3 @@ public:
     bool store();
     bool load(const std::string& name);
 };
-

+ 10 - 4
applications/irda/irda-app-view-manager.cpp

@@ -1,7 +1,7 @@
 #include "furi.h"
 #include "gui/modules/button_panel.h"
-#include "irda-app.hpp"
-#include "irda/irda-app-event.hpp"
+#include "irda-app.h"
+#include "irda/irda-app-event.h"
 #include <callback-connector.h>
 
 IrdaAppViewManager::IrdaAppViewManager() {
@@ -112,8 +112,14 @@ void IrdaAppViewManager::receive_event(IrdaAppEvent* event) {
 }
 
 void IrdaAppViewManager::send_event(IrdaAppEvent* event) {
-    osStatus_t result = osMessageQueuePut(event_queue, event, 0, 0);
-    furi_check(result == osOK);
+    uint32_t timeout = 0;
+    /* Rapid button hammering on Remote Scene causes queue overflow - ignore it,
+     * but try to keep button release event - it switches off IRDA DMA sending. */
+    if(event->type == IrdaAppEvent::Type::MenuSelectedRelease) {
+        timeout = 200;
+    }
+    osMessageQueuePut(event_queue, event, 0, timeout);
+    /* furi_check(result == osOK); */
 }
 
 uint32_t IrdaAppViewManager::previous_view_callback(void* context) {

+ 1 - 2
applications/irda/irda-app-view-manager.hpp → applications/irda/irda-app-view-manager.h

@@ -6,7 +6,7 @@
 #include <gui/modules/dialog_ex.h>
 #include <gui/modules/submenu.h>
 #include <gui/modules/popup.h>
-#include "irda-app.hpp"
+#include "irda-app.h"
 #include "view/irda-app-brut-view.h"
 #include "gui/modules/button_panel.h"
 
@@ -57,4 +57,3 @@ private:
 
     void add_view(ViewType view_type, View* view);
 };
-

+ 16 - 5
applications/irda/irda-app.cpp

@@ -1,5 +1,5 @@
-#include "irda-app.hpp"
-#include "irda/irda-app-file-parser.hpp"
+#include "irda-app.h"
+#include "irda/irda-app-file-parser.h"
 #include <irda_worker.h>
 #include <furi.h>
 #include <gui/gui.h>
@@ -222,22 +222,33 @@ void IrdaApp::notify_click() {
     notification_message_block(notification, &sequence);
 }
 
-void IrdaApp::notify_click_and_blink() {
+void IrdaApp::notify_click_and_green_blink() {
     static const NotificationSequence sequence = {
         &message_click,
         &message_delay_1,
         &message_sound_off,
-        &message_red_0,
         &message_green_255,
-        &message_blue_0,
         &message_delay_10,
         &message_green_0,
+        &message_do_not_reset,
         NULL,
     };
 
     notification_message_block(notification, &sequence);
 }
 
+void IrdaApp::notify_blink_green() {
+    static const NotificationSequence sequence = {
+        &message_green_255,
+        &message_delay_10,
+        &message_green_0,
+        &message_do_not_reset,
+        NULL,
+    };
+
+    notification_message(notification, &sequence);
+}
+
 void IrdaApp::notify_double_vibro() {
     notification_message(notification, &sequence_double_vibro);
 }

+ 11 - 11
applications/irda/irda-app.hpp → applications/irda/irda-app.h

@@ -2,17 +2,16 @@
 #include <map>
 #include <irda.h>
 #include <furi.h>
-#include "scene/irda-app-scene.hpp"
-#include "irda-app-event.hpp"
-#include "scene/irda-app-scene.hpp"
-#include "irda-app-view-manager.hpp"
-#include "irda-app-remote-manager.hpp"
+#include "scene/irda-app-scene.h"
+#include "irda-app-event.h"
+#include "scene/irda-app-scene.h"
+#include "irda-app-view-manager.h"
+#include "irda-app-remote-manager.h"
 #include <forward_list>
 #include <stdint.h>
 #include <notification/notification-messages.h>
 #include <irda_worker.h>
 
-
 class IrdaApp {
 public:
     enum class EditElement : uint8_t {
@@ -71,7 +70,7 @@ public:
     void set_learn_new_remote(bool value);
 
     enum : int {
-           ButtonNA = -1,
+        ButtonNA = -1,
     };
     int get_current_button();
     void set_current_button(int value);
@@ -83,7 +82,8 @@ public:
     void notify_green_on();
     void notify_green_off();
     void notify_click();
-    void notify_click_and_blink();
+    void notify_click_and_green_blink();
+    void notify_blink_green();
 
     static void text_input_callback(void* context);
     static void popup_callback(void* context);
@@ -95,9 +95,9 @@ public:
     ~IrdaApp() {
         irda_worker_free(irda_worker);
         furi_record_close("notification");
-        for (auto &it : scenes)
-            delete it.second;
+        for(auto& it : scenes) delete it.second;
     }
+
 private:
     static const uint8_t text_store_size = 128;
     static const uint8_t text_store_max = 2;
@@ -120,7 +120,7 @@ private:
         {Scene::Start, new IrdaAppSceneStart()},
         {Scene::Universal, new IrdaAppSceneUniversal()},
         {Scene::UniversalTV, new IrdaAppSceneUniversalTV()},
-//        {Scene::UniversalAudio, new IrdaAppSceneUniversalAudio()},
+        //        {Scene::UniversalAudio, new IrdaAppSceneUniversalAudio()},
         {Scene::Learn, new IrdaAppSceneLearn()},
         {Scene::LearnSuccess, new IrdaAppSceneLearnSuccess()},
         {Scene::LearnEnterName, new IrdaAppSceneLearnEnterName()},

+ 1 - 1
applications/irda/irda-runner.cpp

@@ -1,4 +1,4 @@
-#include "irda-app.hpp"
+#include "irda-app.h"
 
 extern "C" int32_t irda_app(void* p) {
     IrdaApp* app = new IrdaApp();

+ 1 - 1
applications/irda/scene/irda-app-scene-edit-delete-done.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 
 void IrdaAppSceneEditDeleteDone::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();

+ 2 - 2
applications/irda/scene/irda-app-scene-edit-delete.cpp

@@ -1,6 +1,6 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 #include "irda.h"
-#include "irda/scene/irda-app-scene.hpp"
+#include "irda/scene/irda-app-scene.h"
 #include <string>
 
 static void dialog_result_callback(DialogExResult result, void* context) {

+ 1 - 1
applications/irda/scene/irda-app-scene-edit-key-select.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 #include "gui/modules/submenu.h"
 
 static void submenu_callback(void* context, uint32_t index) {

+ 1 - 1
applications/irda/scene/irda-app-scene-edit-rename-done.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 
 void IrdaAppSceneEditRenameDone::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();

+ 1 - 1
applications/irda/scene/irda-app-scene-edit-rename.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 
 void IrdaAppSceneEditRename::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();

+ 1 - 1
applications/irda/scene/irda-app-scene-edit.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 #include "gui/modules/submenu.h"
 
 typedef enum {

+ 1 - 1
applications/irda/scene/irda-app-scene-learn-done.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 
 void IrdaAppSceneLearnDone::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();

+ 1 - 1
applications/irda/scene/irda-app-scene-learn-enter-name.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 #include "gui/modules/text_input.h"
 
 void IrdaAppSceneLearnEnterName::on_enter(IrdaApp* app) {

+ 6 - 2
applications/irda/scene/irda-app-scene-learn-success.cpp

@@ -1,6 +1,6 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 #include "irda.h"
-#include "../irda-app-file-parser.hpp"
+#include "../irda-app-file-parser.h"
 #include <memory>
 
 static void dialog_result_callback(DialogExResult result, void* context) {
@@ -51,6 +51,10 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) {
 
 bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) {
     bool consumed = false;
+    if(event->type == IrdaAppEvent::Type::Tick) {
+        /* Send event every tick to suppress any switching off green light */
+        app->notify_green_on();
+    }
 
     if(event->type == IrdaAppEvent::Type::DialogExSelected) {
         switch(event->payload.dialog_ex_result) {

+ 7 - 9
applications/irda/scene/irda-app-scene-learn.cpp

@@ -1,5 +1,5 @@
-#include "../irda-app.hpp"
-#include "../irda-app-event.hpp"
+#include "../irda-app.h"
+#include "../irda-app-event.h"
 #include <irda_worker.h>
 
 static void signal_received_callback(void* context, IrdaWorkerSignal* received_signal) {
@@ -9,7 +9,7 @@ static void signal_received_callback(void* context, IrdaWorkerSignal* received_s
     IrdaApp* app = static_cast<IrdaApp*>(context);
 
     if(irda_worker_signal_is_decoded(received_signal)) {
-        IrdaAppSignal signal(irda_worker_get_decoded_message(received_signal));
+        IrdaAppSignal signal(irda_worker_get_decoded_signal(received_signal));
         app->set_received_signal(signal);
     } else {
         const uint32_t* timings;
@@ -19,7 +19,7 @@ static void signal_received_callback(void* context, IrdaWorkerSignal* received_s
         app->set_received_signal(signal);
     }
 
-    irda_worker_set_received_signal_callback(app->get_irda_worker(), NULL);
+    irda_worker_rx_set_received_signal_callback(app->get_irda_worker(), NULL, NULL);
     IrdaAppEvent event;
     event.type = IrdaAppEvent::Type::IrdaMessageReceived;
     auto view_manager = app->get_view_manager();
@@ -31,9 +31,8 @@ void IrdaAppSceneLearn::on_enter(IrdaApp* app) {
     auto popup = view_manager->get_popup();
 
     auto worker = app->get_irda_worker();
-    irda_worker_set_context(worker, app);
-    irda_worker_set_received_signal_callback(worker, signal_received_callback);
-    irda_worker_start(worker);
+    irda_worker_rx_set_received_signal_callback(worker, signal_received_callback, app);
+    irda_worker_rx_start(worker);
 
     popup_set_icon(popup, 0, 32, &I_IrdaLearnShort_128x31);
     popup_set_text(
@@ -58,11 +57,9 @@ bool IrdaAppSceneLearn::on_event(IrdaApp* app, IrdaAppEvent* event) {
     case IrdaAppEvent::Type::IrdaMessageReceived:
         app->notify_success();
         app->switch_to_next_scene_without_saving(IrdaApp::Scene::LearnSuccess);
-        irda_worker_stop(app->get_irda_worker());
         break;
     case IrdaAppEvent::Type::Back:
         consumed = true;
-        irda_worker_stop(app->get_irda_worker());
         app->switch_to_previous_scene();
         break;
     default:
@@ -73,4 +70,5 @@ bool IrdaAppSceneLearn::on_event(IrdaApp* app, IrdaAppEvent* event) {
 }
 
 void IrdaAppSceneLearn::on_exit(IrdaApp* app) {
+    irda_worker_rx_stop(app->get_irda_worker());
 }

+ 2 - 2
applications/irda/scene/irda-app-scene-remote-list.cpp

@@ -1,5 +1,5 @@
-#include "../irda-app.hpp"
-#include "irda/irda-app-event.hpp"
+#include "../irda-app.h"
+#include "irda/irda-app-event.h"
 
 void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) {
     IrdaAppFileParser file_parser;

+ 56 - 8
applications/irda/scene/irda-app-scene-remote.cpp

@@ -1,5 +1,7 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 #include "gui/modules/button_menu.h"
+#include "input/input.h"
+#include "irda_worker.h"
 
 typedef enum {
     ButtonIndexPlus = -2,
@@ -7,22 +9,41 @@ typedef enum {
     ButtonIndexNA = 0,
 } ButtonIndex;
 
-static void button_menu_callback(void* context, int32_t index) {
+static void button_menu_callback(void* context, int32_t index, InputType type) {
     IrdaApp* app = static_cast<IrdaApp*>(context);
     IrdaAppEvent event;
 
-    event.type = IrdaAppEvent::Type::MenuSelected;
+    if(type == InputTypePress) {
+        event.type = IrdaAppEvent::Type::MenuSelectedPress;
+    } else if(type == InputTypeRelease) {
+        event.type = IrdaAppEvent::Type::MenuSelectedRelease;
+    } else if(type == InputTypeShort) {
+        event.type = IrdaAppEvent::Type::MenuSelected;
+    } else {
+        furi_assert(0);
+    }
+
     event.payload.menu_index = index;
 
     app->get_view_manager()->send_event(&event);
 }
 
+static void irda_app_message_sent_callback(void* context) {
+    IrdaApp* app = static_cast<IrdaApp*>(context);
+    app->notify_blink_green();
+}
+
 void IrdaAppSceneRemote::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();
     ButtonMenu* button_menu = view_manager->get_button_menu();
     auto remote_manager = app->get_remote_manager();
     int i = 0;
+    button_pressed = false;
 
+    irda_worker_tx_set_get_signal_callback(
+        app->get_irda_worker(), irda_worker_tx_get_signal_steady_callback, app);
+    irda_worker_tx_set_signal_sent_callback(
+        app->get_irda_worker(), irda_app_message_sent_callback, app);
     buttons_names = remote_manager->get_button_list();
 
     i = 0;
@@ -48,24 +69,49 @@ void IrdaAppSceneRemote::on_enter(IrdaApp* app) {
 bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) {
     bool consumed = true;
 
-    if(event->type == IrdaAppEvent::Type::MenuSelected) {
+    if((event->type == IrdaAppEvent::Type::MenuSelected) ||
+       (event->type == IrdaAppEvent::Type::MenuSelectedPress) ||
+       (event->type == IrdaAppEvent::Type::MenuSelectedRelease)) {
         switch(event->payload.menu_index) {
         case ButtonIndexPlus:
+            furi_assert(event->type == IrdaAppEvent::Type::MenuSelected);
             app->notify_click();
             buttonmenu_item_selected = event->payload.menu_index;
             app->set_learn_new_remote(false);
             app->switch_to_next_scene(IrdaApp::Scene::Learn);
             break;
         case ButtonIndexEdit:
+            furi_assert(event->type == IrdaAppEvent::Type::MenuSelected);
             app->notify_click();
             buttonmenu_item_selected = event->payload.menu_index;
             app->switch_to_next_scene(IrdaApp::Scene::Edit);
             break;
         default:
-            app->notify_click_and_blink();
-            auto remote_manager = app->get_remote_manager();
-            auto signal = remote_manager->get_button_data(event->payload.menu_index);
-            signal.transmit();
+            furi_assert(event->type != IrdaAppEvent::Type::MenuSelected);
+            bool pressed = (event->type == IrdaAppEvent::Type::MenuSelectedPress);
+
+            if(pressed && !button_pressed) {
+                button_pressed = true;
+                app->notify_click_and_green_blink();
+
+                auto button_signal =
+                    app->get_remote_manager()->get_button_data(event->payload.menu_index);
+                if(button_signal.is_raw()) {
+                    irda_worker_set_raw_signal(
+                        app->get_irda_worker(),
+                        button_signal.get_raw_signal().timings,
+                        button_signal.get_raw_signal().timings_cnt);
+                } else {
+                    irda_worker_set_decoded_signal(
+                        app->get_irda_worker(), &button_signal.get_message());
+                }
+
+                irda_worker_tx_start(app->get_irda_worker());
+            } else if(!pressed && button_pressed) {
+                button_pressed = false;
+                irda_worker_tx_stop(app->get_irda_worker());
+                app->notify_green_off();
+            }
             break;
         }
     } else if(event->type == IrdaAppEvent::Type::Back) {
@@ -79,6 +125,8 @@ bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) {
 }
 
 void IrdaAppSceneRemote::on_exit(IrdaApp* app) {
+    irda_worker_tx_set_get_signal_callback(app->get_irda_worker(), nullptr, nullptr);
+    irda_worker_tx_set_signal_sent_callback(app->get_irda_worker(), nullptr, nullptr);
     IrdaAppViewManager* view_manager = app->get_view_manager();
     ButtonMenu* button_menu = view_manager->get_button_menu();
 

+ 1 - 1
applications/irda/scene/irda-app-scene-start.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 
 typedef enum {
     SubmenuIndexUniversalLibrary,

+ 12 - 9
applications/irda/scene/irda-app-scene-universal-common.cpp

@@ -1,12 +1,12 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 #include "assets_icons.h"
 #include "gui/modules/button_menu.h"
 #include "gui/modules/button_panel.h"
 #include "../view/irda-app-brut-view.h"
 #include "gui/view.h"
-#include "irda/irda-app-event.hpp"
-#include "irda/irda-app-view-manager.hpp"
-#include "irda/scene/irda-app-scene.hpp"
+#include "irda/irda-app-event.h"
+#include "irda/irda-app-view-manager.h"
+#include "irda/scene/irda-app-scene.h"
 
 void IrdaAppSceneUniversalCommon::irda_app_item_callback(void* context, uint32_t index) {
     IrdaApp* app = static_cast<IrdaApp*>(context);
@@ -49,10 +49,11 @@ void IrdaAppSceneUniversalCommon::show_popup(IrdaApp* app, int record_amount) {
     button_panel_set_popup_input_callback(button_panel, irda_popup_brut_input_callback, app);
 }
 
-void IrdaAppSceneUniversalCommon::progress_popup(IrdaApp* app) {
-    popup_brut_increase_progress(app->get_view_manager()->get_popup_brut());
+bool IrdaAppSceneUniversalCommon::progress_popup(IrdaApp* app) {
+    bool result = popup_brut_increase_progress(app->get_view_manager()->get_popup_brut());
     auto button_panel = app->get_view_manager()->get_button_panel();
     with_view_model_cpp(button_panel_get_view(button_panel), void*, model, { return true; });
+    return result;
 }
 
 bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) {
@@ -63,9 +64,11 @@ bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) {
             auto view_manager = app->get_view_manager();
             IrdaAppEvent tick_event = {.type = IrdaAppEvent::Type::Tick};
             view_manager->send_event(&tick_event);
-            if(brute_force.send_next_bruteforce()) {
-                progress_popup(app);
-            } else {
+            bool result = brute_force.send_next_bruteforce();
+            if(result) {
+                result = progress_popup(app);
+            }
+            if(!result) {
                 brute_force.stop_bruteforce();
                 brute_force_started = false;
                 remove_popup(app);

+ 2 - 2
applications/irda/scene/irda-app-scene-universal-tv.cpp

@@ -1,5 +1,5 @@
-#include "irda/scene/irda-app-scene.hpp"
-#include "irda/irda-app.hpp"
+#include "irda/scene/irda-app-scene.h"
+#include "irda/irda-app.h"
 
 void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();

+ 1 - 1
applications/irda/scene/irda-app-scene-universal.cpp

@@ -1,4 +1,4 @@
-#include "../irda-app.hpp"
+#include "../irda-app.h"
 
 typedef enum {
     SubmenuIndexUniversalTV,

+ 23 - 10
applications/irda/scene/irda-app-scene.hpp → applications/irda/scene/irda-app-scene.h

@@ -1,11 +1,10 @@
 #pragma once
-#include "../irda-app-event.hpp"
+#include "../irda-app-event.h"
 #include <furi-hal-irda.h>
 #include "irda.h"
 #include <vector>
 #include <string>
-#include "../irda-app-brute-force.hpp"
-
+#include "../irda-app-brute-force.h"
 
 class IrdaApp;
 
@@ -24,6 +23,7 @@ public:
     void on_enter(IrdaApp* app) final;
     bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
     void on_exit(IrdaApp* app) final;
+
 private:
     uint32_t submenu_item_selected = 0;
 };
@@ -33,6 +33,7 @@ public:
     void on_enter(IrdaApp* app) final;
     bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
     void on_exit(IrdaApp* app) final;
+
 private:
     uint32_t submenu_item_selected = 0;
 };
@@ -70,9 +71,11 @@ public:
     void on_enter(IrdaApp* app) final;
     bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
     void on_exit(IrdaApp* app) final;
+
 private:
     std::vector<std::string> buttons_names;
     uint32_t buttonmenu_item_selected = 0;
+    bool button_pressed = false;
 };
 
 class IrdaAppSceneRemoteList : public IrdaAppScene {
@@ -80,6 +83,7 @@ public:
     void on_enter(IrdaApp* app) final;
     bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
     void on_exit(IrdaApp* app) final;
+
 private:
     uint32_t submenu_item_selected = 0;
     std::vector<std::string> remote_names;
@@ -90,6 +94,7 @@ public:
     void on_enter(IrdaApp* app) final;
     bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
     void on_exit(IrdaApp* app) final;
+
 private:
     uint32_t submenu_item_selected = 0;
 };
@@ -99,6 +104,7 @@ public:
     void on_enter(IrdaApp* app) final;
     bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
     void on_exit(IrdaApp* app) final;
+
 private:
     std::vector<std::string> buttons_names;
 };
@@ -133,16 +139,20 @@ public:
 
 class IrdaAppSceneUniversalCommon : public IrdaAppScene {
     bool brute_force_started = false;
+
 protected:
     bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
     void on_exit(IrdaApp* app) final;
     IrdaAppBruteForce brute_force;
     void remove_popup(IrdaApp* app);
     void show_popup(IrdaApp* app, int record_amount);
-    void progress_popup(IrdaApp* app);
+    bool progress_popup(IrdaApp* app);
     static void irda_app_item_callback(void* context, uint32_t index);
-    IrdaAppSceneUniversalCommon(const char* filename) : brute_force(filename) {}
-    ~IrdaAppSceneUniversalCommon() {}
+    IrdaAppSceneUniversalCommon(const char* filename)
+        : brute_force(filename) {
+    }
+    ~IrdaAppSceneUniversalCommon() {
+    }
 };
 
 class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon {
@@ -151,13 +161,16 @@ public:
     IrdaAppSceneUniversalTV()
         : IrdaAppSceneUniversalCommon("/ext/irda/universal/tv.ir") {
     }
-    ~IrdaAppSceneUniversalTV() {}
+    ~IrdaAppSceneUniversalTV() {
+    }
 };
 
 class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon {
 public:
     void on_enter(IrdaApp* app) final;
-    IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/ext/irda/universal/audio.ir") {}
-    ~IrdaAppSceneUniversalAudio() {}
+    IrdaAppSceneUniversalAudio()
+        : IrdaAppSceneUniversalCommon("/ext/irda/universal/audio.ir") {
+    }
+    ~IrdaAppSceneUniversalAudio() {
+    }
 };
-

+ 3 - 1
applications/irda/view/irda-app-brut-view.c

@@ -15,13 +15,15 @@ struct IrdaAppPopupBrut {
     char percents_string_storage[8];
 };
 
-void popup_brut_increase_progress(IrdaAppPopupBrut* popup_brut) {
+bool popup_brut_increase_progress(IrdaAppPopupBrut* popup_brut) {
     furi_assert(popup_brut);
 
     if(popup_brut->progress < popup_brut->progress_max)
         ++popup_brut->progress;
     else
         furi_assert(0);
+
+    return popup_brut->progress < popup_brut->progress_max;
 }
 
 void popup_brut_draw_callback(Canvas* canvas, void* context) {

+ 1 - 1
applications/irda/view/irda-app-brut-view.h

@@ -7,7 +7,7 @@ extern "C" {
 
 typedef struct IrdaAppPopupBrut IrdaAppPopupBrut;
 
-void popup_brut_increase_progress(IrdaAppPopupBrut* popup_brut);
+bool popup_brut_increase_progress(IrdaAppPopupBrut* popup_brut);
 IrdaAppPopupBrut* popup_brut_alloc();
 void popup_brut_free(IrdaAppPopupBrut* popup_brut);
 void popup_brut_draw_callback(Canvas* canvas, void* model);

+ 6 - 6
applications/irda_monitor/irda_monitor.c

@@ -58,7 +58,7 @@ static void signal_received_callback(void* context, IrdaWorkerSignal* received_s
     IrdaMonitor* irda_monitor = context;
 
     if(irda_worker_signal_is_decoded(received_signal)) {
-        const IrdaMessage* message = irda_worker_get_decoded_message(received_signal);
+        const IrdaMessage* message = irda_worker_get_decoded_signal(received_signal);
         snprintf(
             irda_monitor->display_text,
             sizeof(irda_monitor->display_text),
@@ -112,10 +112,10 @@ int32_t irda_monitor_app(void* p) {
     gui_add_view_port(gui, irda_monitor->view_port, GuiLayerFullscreen);
 
     irda_monitor->worker = irda_worker_alloc();
-    irda_worker_set_context(irda_monitor->worker, irda_monitor);
-    irda_worker_start(irda_monitor->worker);
-    irda_worker_set_received_signal_callback(irda_monitor->worker, signal_received_callback);
-    irda_worker_enable_blink_on_receiving(irda_monitor->worker, true);
+    irda_worker_rx_start(irda_monitor->worker);
+    irda_worker_rx_set_received_signal_callback(
+        irda_monitor->worker, signal_received_callback, irda_monitor);
+    irda_worker_rx_enable_blink_on_receiving(irda_monitor->worker, true);
 
     while(1) {
         InputEvent event;
@@ -126,7 +126,7 @@ int32_t irda_monitor_app(void* p) {
         }
     }
 
-    irda_worker_stop(irda_monitor->worker);
+    irda_worker_rx_stop(irda_monitor->worker);
     irda_worker_free(irda_monitor->worker);
     osMessageQueueDelete(irda_monitor->event_queue);
     view_port_enabled_set(irda_monitor->view_port, false);

+ 16 - 4
firmware/targets/f6/furi-hal/furi-hal-irda.c

@@ -40,8 +40,10 @@ typedef struct{
 
 typedef struct {
     float cycle_duration;
-    FuriHalIrdaTxGetDataCallback data_callback;
+    FuriHalIrdaTxGetDataISRCallback data_callback;
+    FuriHalIrdaTxSignalSentISRCallback signal_sent_callback;
     void* data_context;
+    void* signal_sent_context;
     IrdaTxBuf buffer[2];
     osSemaphoreId_t stop_semaphore;
 } IrdaTimTx;
@@ -175,8 +177,10 @@ void furi_hal_irda_async_rx_stop(void) {
     furi_hal_irda_state = IrdaStateIdle;
 }
 
-void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_ms) {
-    LL_TIM_OC_SetCompareCH3(TIM2, timeout_ms * 1000);
+void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_us) {
+    furi_assert(LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM2));
+
+    LL_TIM_OC_SetCompareCH3(TIM2, timeout_us);
     LL_TIM_OC_SetMode(TIM2, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE);
     LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH3);
     LL_TIM_EnableIT_CC3(TIM2);
@@ -287,6 +291,9 @@ static void furi_hal_irda_tx_dma_isr() {
             /* if it's not end of the packet - continue receiving */
             furi_hal_irda_tx_dma_set_buffer(next_buf_num);
         }
+        if (irda_tim_tx.signal_sent_callback) {
+            irda_tim_tx.signal_sent_callback(irda_tim_tx.signal_sent_context);
+        }
     }
 }
 
@@ -576,9 +583,14 @@ void furi_hal_irda_async_tx_stop(void) {
     furi_hal_irda_async_tx_wait_termination();
 }
 
-void furi_hal_irda_async_tx_set_data_isr_callback(FuriHalIrdaTxGetDataCallback callback, void* context) {
+void furi_hal_irda_async_tx_set_data_isr_callback(FuriHalIrdaTxGetDataISRCallback callback, void* context) {
     furi_assert(furi_hal_irda_state == IrdaStateIdle);
     irda_tim_tx.data_callback = callback;
     irda_tim_tx.data_context = context;
 }
 
+void furi_hal_irda_async_tx_set_signal_sent_isr_callback(FuriHalIrdaTxSignalSentISRCallback callback, void* context) {
+    irda_tim_tx.signal_sent_callback = callback;
+    irda_tim_tx.signal_sent_context = context;
+}
+

+ 23 - 8
firmware/targets/furi-hal-include/furi-hal-irda.h

@@ -14,7 +14,15 @@ typedef enum {
     FuriHalIrdaTxGetDataStateLastDone,   /* New data obtained, and this is end of package and no more data available */
 } FuriHalIrdaTxGetDataState;
 
-typedef FuriHalIrdaTxGetDataState (*FuriHalIrdaTxGetDataCallback) (void* context, uint32_t* duration, bool* level);
+/* Callback type for providing data to IRDA DMA TX system. It is called every tim */
+typedef FuriHalIrdaTxGetDataState (*FuriHalIrdaTxGetDataISRCallback) (void* context, uint32_t* duration, bool* level);
+
+/* Callback type called every time signal is sent by DMA to Timer.
+ * Actually, it means there are 2 timings left to send for this signal, which is almost end.
+ * Don't use this callback to stop transmission, as far as there are next signal is
+ * charged for transmission by DMA.
+ */
+typedef void (*FuriHalIrdaTxSignalSentISRCallback) (void* context);
 
 /**
  * Signature of callback function for receiving continuous IRDA rx signal.
@@ -44,16 +52,15 @@ void furi_hal_irda_async_rx_start(void);
  */
 void furi_hal_irda_async_rx_stop(void);
 
-/** Setup api hal for receiving silence timeout.
+/** Setup hal for receiving silence timeout.
  * Should be used with 'furi_hal_irda_timeout_irq_set_callback()'.
  *
- * @param[in]   timeout_ms - time to wait for silence on IRDA port
+ * @param[in]   timeout_us - time to wait for silence on IRDA port
  *                           before generating IRQ.
  */
-void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_ms);
+void furi_hal_irda_async_rx_set_timeout(uint32_t timeout_us);
 
-/**
- * Setup callback for previously initialized IRDA RX interrupt.
+/** Setup callback for previously initialized IRDA RX interrupt.
  *
  * @param[in]   callback - callback to call when RX signal edge changing occurs
  * @param[in]   ctx - context for callback
@@ -62,7 +69,7 @@ void furi_hal_irda_async_rx_set_capture_isr_callback(FuriHalIrdaRxCaptureCallbac
 
 /**
  * Setup callback for reaching silence timeout on IRDA port.
- * Should setup api hal with 'furi_hal_irda_setup_rx_timeout_irq()' first.
+ * Should setup hal with 'furi_hal_irda_setup_rx_timeout_irq()' first.
  *
  * @param[in]   callback - callback for silence timeout
  * @param[in]   ctx - context to pass to callback
@@ -82,7 +89,7 @@ bool furi_hal_irda_is_busy(void);
  * @param[in]   callback - function to provide new data
  * @param[in]   context - context for callback
  */
-void furi_hal_irda_async_tx_set_data_isr_callback(FuriHalIrdaTxGetDataCallback callback, void* context);
+void furi_hal_irda_async_tx_set_data_isr_callback(FuriHalIrdaTxGetDataISRCallback callback, void* context);
 
 /**
  * Start IR asynchronous transmission. It can be stopped by 2 reasons:
@@ -115,6 +122,14 @@ void furi_hal_irda_async_tx_stop(void);
  */
 void furi_hal_irda_async_tx_wait_termination(void);
 
+/**
+ * Set callback for end of signal transmission
+ *
+ * @param[in]   callback - function to call when signal is sent
+ * @param[in]   context - context for callback
+ */
+void furi_hal_irda_async_tx_set_signal_sent_isr_callback(FuriHalIrdaTxSignalSentISRCallback callback, void* context);
+
 #ifdef __cplusplus
 }
 #endif

+ 30 - 0
lib/irda/encoder_decoder/irda.c

@@ -34,6 +34,8 @@ typedef struct {
     IrdaEncoders encoder;
     uint8_t address_length;
     uint8_t command_length;
+    uint32_t frequency;
+    float duty_cycle;
 } IrdaProtocolImplementation;
 
 struct IrdaEncoderHandler {
@@ -58,6 +60,8 @@ static const IrdaProtocolImplementation irda_protocols[] = {
           .free = irda_encoder_nec_free},
       .address_length = 2,
       .command_length = 2,
+      .frequency = IRDA_COMMON_CARRIER_FREQUENCY,
+      .duty_cycle = IRDA_COMMON_DUTY_CYCLE,
     },
     // #1 - have to be after NEC
     { .protocol = IrdaProtocolNECext,
@@ -74,6 +78,8 @@ static const IrdaProtocolImplementation irda_protocols[] = {
           .free = irda_encoder_nec_free},
       .address_length = 4,
       .command_length = 2,
+      .frequency = IRDA_COMMON_CARRIER_FREQUENCY,
+      .duty_cycle = IRDA_COMMON_DUTY_CYCLE,
     },
     // #2
     { .protocol = IrdaProtocolSamsung32,
@@ -90,6 +96,8 @@ static const IrdaProtocolImplementation irda_protocols[] = {
           .free = irda_encoder_samsung32_free},
       .address_length = 2,
       .command_length = 2,
+      .frequency = IRDA_COMMON_CARRIER_FREQUENCY,
+      .duty_cycle = IRDA_COMMON_DUTY_CYCLE,
     },
     // #3
     { .protocol = IrdaProtocolRC6,
@@ -106,6 +114,8 @@ static const IrdaProtocolImplementation irda_protocols[] = {
           .free = irda_encoder_rc6_free},
       .address_length = 2,
       .command_length = 2,
+      .frequency = IRDA_COMMON_CARRIER_FREQUENCY,
+      .duty_cycle = IRDA_COMMON_DUTY_CYCLE,
     },
 };
 
@@ -222,10 +232,12 @@ IrdaProtocol irda_get_protocol_by_name(const char* protocol_name) {
         if (!strcmp(irda_protocols[i].name, protocol_name))
             return i;
     }
+    furi_assert(0);
     return IrdaProtocolUnknown;
 }
 
 const char* irda_get_protocol_name(IrdaProtocol protocol) {
+    furi_assert(irda_is_protocol_valid(protocol));
     if (irda_is_protocol_valid(protocol))
         return irda_protocols[protocol].name;
     else
@@ -233,6 +245,7 @@ const char* irda_get_protocol_name(IrdaProtocol protocol) {
 }
 
 uint8_t irda_get_protocol_address_length(IrdaProtocol protocol) {
+    furi_assert(irda_is_protocol_valid(protocol));
     if (irda_is_protocol_valid(protocol))
         return irda_protocols[protocol].address_length;
     else
@@ -240,9 +253,26 @@ uint8_t irda_get_protocol_address_length(IrdaProtocol protocol) {
 }
 
 uint8_t irda_get_protocol_command_length(IrdaProtocol protocol) {
+    furi_assert(irda_is_protocol_valid(protocol));
     if (irda_is_protocol_valid(protocol))
         return irda_protocols[protocol].command_length;
     else
         return 0;
 }
 
+uint32_t irda_get_protocol_frequency(IrdaProtocol protocol) {
+    furi_assert(irda_is_protocol_valid(protocol));
+    if (irda_is_protocol_valid(protocol))
+        return irda_protocols[protocol].frequency;
+    else
+        return 0;
+}
+
+float irda_get_protocol_duty_cycle(IrdaProtocol protocol) {
+    furi_assert(irda_is_protocol_valid(protocol));
+    if (irda_is_protocol_valid(protocol))
+        return irda_protocols[protocol].duty_cycle;
+    else
+        return 0;
+}
+

+ 24 - 0
lib/irda/encoder_decoder/irda.h

@@ -10,6 +10,12 @@ extern "C" {
 #define IRDA_COMMON_CARRIER_FREQUENCY      38000
 #define IRDA_COMMON_DUTY_CYCLE             0.33
 
+/* if we want to see splitted raw signals during brutforce,
+ * we have to have RX raw timing delay less than TX */
+#define IRDA_RAW_RX_TIMING_DELAY_US        150000
+#define IRDA_RAW_TX_TIMING_DELAY_US        180000
+
+
 typedef struct IrdaDecoderHandler IrdaDecoderHandler;
 typedef struct IrdaEncoderHandler IrdaEncoderHandler;
 
@@ -150,6 +156,24 @@ IrdaStatus irda_encode(IrdaEncoderHandler* handler, uint32_t* duration, bool* le
  */
 void irda_reset_encoder(IrdaEncoderHandler* handler, const IrdaMessage* message);
 
+/**
+ * Get PWM frequency value for selected protocol
+ *
+ * \param[in]   protocol    - protocol to get from PWM frequency
+ *
+ * \return      frequency
+ */
+uint32_t irda_get_protocol_frequency(IrdaProtocol protocol);
+
+/**
+ * Get PWM duty cycle value for selected protocol
+ *
+ * \param[in]   protocol    - protocol to get from PWM duty cycle
+ *
+ * \return      duty cycle
+ */
+float irda_get_protocol_duty_cycle(IrdaProtocol protocol);
+
 #ifdef __cplusplus
 }
 #endif

+ 2 - 1
lib/irda/encoder_decoder/irda_protocol_defs_i.h

@@ -137,7 +137,8 @@ extern const IrdaCommonProtocolSpec protocol_samsung32;
 #define IRDA_RC6_BIT                        444     // half of time-quant for 1 bit
 #define IRDA_RC6_PREAMBLE_TOLERANCE         0.07    // percents
 #define IRDA_RC6_BIT_TOLERANCE              120     // us
-#define IRDA_RC6_SILENCE                    2700
+/* protocol allows 2700 silence, but it is hard to send 1 message without repeat */
+#define IRDA_RC6_SILENCE                    (2700 * 10)
 
 void* irda_decoder_rc6_alloc(void);
 void irda_decoder_rc6_reset(void* decoder);

+ 1 - 1
lib/irda/worker/irda_transmit.c

@@ -23,7 +23,7 @@ FuriHalIrdaTxGetDataState irda_get_raw_data_callback (void* context, uint32_t* d
     if (irda_tx_raw_add_silence && (irda_tx_raw_timings_index == 0)) {
         irda_tx_raw_add_silence = false;
         *level = false;
-        *duration = 180000;     // 180 ms delay between raw packets
+        *duration = IRDA_RAW_TX_TIMING_DELAY_US;
     } else {
         *level = irda_tx_raw_start_from_mark ^ (irda_tx_raw_timings_index % 2);
         *duration = timings[irda_tx_raw_timings_index++];

+ 378 - 61
lib/irda/worker/irda_worker.c

@@ -1,20 +1,45 @@
+#include "furi/check.h"
+#include "furi/common_defines.h"
+#include "sys/_stdint.h"
 #include "irda_worker.h"
 #include <irda.h>
 #include <furi-hal-irda.h>
 #include <limits.h>
 #include <stdint.h>
-#include <stream_buffer.h>
 #include <furi.h>
 #include <notification/notification-messages.h>
+#include <stream_buffer.h>
+
+#define IRDA_WORKER_RX_TIMEOUT              IRDA_RAW_RX_TIMING_DELAY_US
 
-#define MAX_TIMINGS_AMOUNT                  500
-#define IRDA_WORKER_RX_TIMEOUT              150 // ms
 #define IRDA_WORKER_RX_RECEIVED             0x01
 #define IRDA_WORKER_RX_TIMEOUT_RECEIVED     0x02
 #define IRDA_WORKER_OVERRUN                 0x04
 #define IRDA_WORKER_EXIT                    0x08
-
-struct IrdaWorkerSignal {
+#define IRDA_WORKER_TX_FILL_BUFFER          0x10
+#define IRDA_WORKER_TX_MESSAGE_SENT         0x20
+
+#define IRDA_WORKER_ALL_RX_EVENTS       (IRDA_WORKER_RX_RECEIVED \
+                                        | IRDA_WORKER_RX_TIMEOUT_RECEIVED \
+                                        | IRDA_WORKER_OVERRUN \
+                                        | IRDA_WORKER_EXIT)
+
+#define IRDA_WORKER_ALL_TX_EVENTS       (IRDA_WORKER_TX_FILL_BUFFER \
+                                        | IRDA_WORKER_TX_MESSAGE_SENT \
+                                        | IRDA_WORKER_EXIT)
+
+#define IRDA_WORKER_ALL_EVENTS          (IRDA_WORKER_ALL_RX_EVENTS | IRDA_WORKER_ALL_TX_EVENTS)
+
+typedef enum {
+    IrdaWorkerStateIdle,
+    IrdaWorkerStateRunRx,
+    IrdaWorkerStateRunTx,
+    IrdaWorkerStateWaitTxEnd,
+    IrdaWorkerStateStopTx,
+    IrdaWorkerStateStartTx,
+} IrdaWorkerState;
+
+struct IrdaWorkerSignal{
     bool decoded;
     size_t timings_cnt;
     union {
@@ -25,37 +50,67 @@ struct IrdaWorkerSignal {
 
 struct IrdaWorker {
     FuriThread* thread;
-    IrdaDecoderHandler* irda_decoder;
     StreamBufferHandle_t stream;
+    osEventFlagsId_t events;
 
-    TaskHandle_t worker_handle;
     IrdaWorkerSignal signal;
-
-    IrdaWorkerReceivedSignalCallback received_signal_callback;
-    void* context;
-    bool blink_enable;
-    bool overrun;
+    IrdaWorkerState state;
+    IrdaEncoderHandler* irda_encoder;
+    IrdaDecoderHandler* irda_decoder;
     NotificationApp* notification;
+    bool blink_enable;
+
+    union {
+        struct {
+            IrdaWorkerGetSignalCallback get_signal_callback;
+            IrdaWorkerMessageSentCallback message_sent_callback;
+            void* get_signal_context;
+            void* message_sent_context;
+            uint32_t frequency;
+            float duty_cycle;
+            uint32_t tx_raw_cnt;
+            bool need_reinitialization;
+            bool steady_signal_sent;
+        } tx;
+        struct {
+            IrdaWorkerReceivedSignalCallback received_signal_callback;
+            void* received_signal_context;
+            bool overrun;
+        } rx;
+    };
 };
 
+typedef struct {
+    uint32_t duration;
+    bool level;
+    FuriHalIrdaTxGetDataState state;
+} IrdaWorkerTiming;
+
+static int32_t irda_worker_tx_thread(void* context);
+static FuriHalIrdaTxGetDataState irda_worker_furi_hal_data_isr_callback(void* context, uint32_t* duration, bool* level);
+static void irda_worker_furi_hal_message_sent_isr_callback(void* context);
+
+
 static void irda_worker_rx_timeout_callback(void* context) {
     IrdaWorker* instance = context;
-    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
-    xTaskNotifyFromISR(instance->worker_handle, IRDA_WORKER_RX_TIMEOUT_RECEIVED, eSetBits,  &xHigherPriorityTaskWoken);
-    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+    uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_RX_TIMEOUT_RECEIVED);
+    furi_check(flags_set & IRDA_WORKER_RX_TIMEOUT_RECEIVED);
 }
 
 static void irda_worker_rx_callback(void* context, bool level, uint32_t duration) {
     IrdaWorker* instance = context;
 
     BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+    furi_assert(duration != 0);
     LevelDuration level_duration = level_duration_make(level, duration);
 
     size_t ret =
         xStreamBufferSendFromISR(instance->stream, &level_duration, sizeof(LevelDuration), &xHigherPriorityTaskWoken);
-    uint32_t notify_value = (ret == sizeof(LevelDuration)) ? IRDA_WORKER_RX_RECEIVED : IRDA_WORKER_OVERRUN;
-    xTaskNotifyFromISR(instance->worker_handle, notify_value, eSetBits,  &xHigherPriorityTaskWoken);
+    uint32_t events = (ret == sizeof(LevelDuration)) ? IRDA_WORKER_RX_RECEIVED : IRDA_WORKER_OVERRUN;
     portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+
+    uint32_t flags_set = osEventFlagsSet(instance->events, events);
+    furi_check(flags_set & events);
 }
 
 static void irda_worker_process_timeout(IrdaWorker* instance) {
@@ -63,8 +118,8 @@ static void irda_worker_process_timeout(IrdaWorker* instance) {
         return;
 
     instance->signal.decoded = false;
-    if (instance->received_signal_callback)
-        instance->received_signal_callback(instance->context, &instance->signal);
+    if (instance->rx.received_signal_callback)
+        instance->rx.received_signal_callback(instance->rx.received_signal_context, &instance->signal);
 }
 
 static void irda_worker_process_timings(IrdaWorker* instance, uint32_t duration, bool level) {
@@ -73,8 +128,8 @@ static void irda_worker_process_timings(IrdaWorker* instance, uint32_t duration,
         instance->signal.data.message = *message_decoded;
         instance->signal.timings_cnt = 0;
         instance->signal.decoded = true;
-        if (instance->received_signal_callback)
-            instance->received_signal_callback(instance->context, &instance->signal);
+        if (instance->rx.received_signal_callback)
+            instance->rx.received_signal_callback(instance->rx.received_signal_context, &instance->signal);
     } else {
         /* Skip first timing if it's starts from Space */
         if ((instance->signal.timings_cnt == 0) && !level) {
@@ -85,50 +140,49 @@ static void irda_worker_process_timings(IrdaWorker* instance, uint32_t duration,
             instance->signal.data.timings[instance->signal.timings_cnt] = duration;
             ++instance->signal.timings_cnt;
         } else {
-            xTaskNotify(instance->worker_handle, IRDA_WORKER_OVERRUN, eSetBits);
-            instance->overrun = true;
+            uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_OVERRUN);
+            furi_check(flags_set & IRDA_WORKER_OVERRUN);
+            instance->rx.overrun = true;
         }
     }
 }
 
-static int32_t irda_worker_thread_callback(void* context) {
-    IrdaWorker* instance = context;
-    uint32_t notify_value = 0;
+static int32_t irda_worker_rx_thread(void* thread_context) {
+    IrdaWorker* instance = thread_context;
+    uint32_t events = 0;
     LevelDuration level_duration;
     TickType_t last_blink_time = 0;
 
     while(1) {
-        BaseType_t result;
-        result = xTaskNotifyWait(pdFALSE, ULONG_MAX, &notify_value, 1000);
-        if (result != pdPASS)
-            continue;
+        events = osEventFlagsWait(instance->events, IRDA_WORKER_ALL_RX_EVENTS, 0, osWaitForever);
+        furi_check(events & IRDA_WORKER_ALL_RX_EVENTS); /* at least one caught */
 
-        if (notify_value & IRDA_WORKER_RX_RECEIVED) {
-            if (!instance->overrun && instance->blink_enable && ((xTaskGetTickCount() - last_blink_time) > 80)) {
+        if (events & IRDA_WORKER_RX_RECEIVED) {
+            if (!instance->rx.overrun && instance->blink_enable && ((xTaskGetTickCount() - last_blink_time) > 80)) {
                 last_blink_time = xTaskGetTickCount();
                 notification_message(instance->notification, &sequence_blink_blue_10);
             }
             if (instance->signal.timings_cnt == 0)
                 notification_message(instance->notification, &sequence_display_on);
             while (sizeof(LevelDuration) == xStreamBufferReceive(instance->stream, &level_duration, sizeof(LevelDuration), 0)) {
-                if (!instance->overrun) {
+                if (!instance->rx.overrun) {
                     bool level = level_duration_get_level(level_duration);
                     uint32_t duration = level_duration_get_duration(level_duration);
                     irda_worker_process_timings(instance, duration, level);
                 }
             }
         }
-        if (notify_value & IRDA_WORKER_OVERRUN) {
+        if (events & IRDA_WORKER_OVERRUN) {
             printf("#");
             irda_reset_decoder(instance->irda_decoder);
             instance->signal.timings_cnt = 0;
             if (instance->blink_enable)
                 notification_message(instance->notification, &sequence_set_red_255);
         }
-        if (notify_value & IRDA_WORKER_RX_TIMEOUT_RECEIVED) {
-            if (instance->overrun) {
+        if (events & IRDA_WORKER_RX_TIMEOUT_RECEIVED) {
+            if (instance->rx.overrun) {
                 printf("\nOVERRUN, max samples: %d\n", MAX_TIMINGS_AMOUNT);
-                instance->overrun = false;
+                instance->rx.overrun = false;
                 if (instance->blink_enable)
                     notification_message(instance->notification, &sequence_reset_red);
             } else {
@@ -136,16 +190,17 @@ static int32_t irda_worker_thread_callback(void* context) {
             }
             instance->signal.timings_cnt = 0;
         }
-        if (notify_value & IRDA_WORKER_EXIT)
+        if (events & IRDA_WORKER_EXIT)
             break;
     }
 
     return 0;
 }
 
-void irda_worker_set_received_signal_callback(IrdaWorker* instance, IrdaWorkerReceivedSignalCallback callback) {
+void irda_worker_rx_set_received_signal_callback(IrdaWorker* instance, IrdaWorkerReceivedSignalCallback callback, void* context) {
     furi_assert(instance);
-    instance->received_signal_callback = callback;
+    instance->rx.received_signal_callback = callback;
+    instance->rx.received_signal_context = context;
 }
 
 IrdaWorker* irda_worker_alloc() {
@@ -155,60 +210,67 @@ IrdaWorker* irda_worker_alloc() {
     furi_thread_set_name(instance->thread, "irda_worker");
     furi_thread_set_stack_size(instance->thread, 2048);
     furi_thread_set_context(instance->thread, instance);
-    furi_thread_set_callback(instance->thread, irda_worker_thread_callback);
-
-    instance->stream = xStreamBufferCreate(sizeof(LevelDuration) * 512, sizeof(LevelDuration));
 
+    size_t buffer_size = MAX(sizeof(IrdaWorkerTiming) * MAX_TIMINGS_AMOUNT, sizeof(LevelDuration) * MAX_TIMINGS_AMOUNT);
+    instance->stream = xStreamBufferCreate(buffer_size, sizeof(IrdaWorkerTiming));
     instance->irda_decoder = irda_alloc_decoder();
+    instance->irda_encoder = irda_alloc_encoder();
     instance->blink_enable = false;
     instance->notification = furi_record_open("notification");
+    instance->state = IrdaWorkerStateIdle;
+    instance->events = osEventFlagsNew(NULL);
 
     return instance;
 }
 
 void irda_worker_free(IrdaWorker* instance) {
     furi_assert(instance);
-    furi_assert(!instance->worker_handle);
+    furi_assert(instance->state == IrdaWorkerStateIdle);
 
     furi_record_close("notification");
     irda_free_decoder(instance->irda_decoder);
+    irda_free_encoder(instance->irda_encoder);
     vStreamBufferDelete(instance->stream);
     furi_thread_free(instance->thread);
+    osEventFlagsDelete(instance->events);
 
     free(instance);
 }
 
-void irda_worker_set_context(IrdaWorker* instance, void* context) {
+void irda_worker_rx_start(IrdaWorker* instance) {
     furi_assert(instance);
-    instance->context = context;
-}
+    furi_assert(instance->state == IrdaWorkerStateIdle);
 
-void irda_worker_start(IrdaWorker* instance) {
-    furi_assert(instance);
-    furi_assert(!instance->worker_handle);
+    xStreamBufferSetTriggerLevel(instance->stream, sizeof(LevelDuration));
 
+    osEventFlagsClear(instance->events, IRDA_WORKER_ALL_EVENTS);
+    furi_thread_set_callback(instance->thread, irda_worker_rx_thread);
     furi_thread_start(instance->thread);
 
-    instance->worker_handle = furi_thread_get_thread_id(instance->thread);
-    furi_hal_irda_async_rx_start();
-    furi_hal_irda_async_rx_set_timeout(IRDA_WORKER_RX_TIMEOUT);
     furi_hal_irda_async_rx_set_capture_isr_callback(irda_worker_rx_callback, instance);
     furi_hal_irda_async_rx_set_timeout_isr_callback(irda_worker_rx_timeout_callback, instance);
+    furi_hal_irda_async_rx_start();
+    furi_hal_irda_async_rx_set_timeout(IRDA_WORKER_RX_TIMEOUT);
+
+    instance->state = IrdaWorkerStateRunRx;
 }
 
-void irda_worker_stop(IrdaWorker* instance) {
+void irda_worker_rx_stop(IrdaWorker* instance) {
     furi_assert(instance);
-    furi_assert(instance->worker_handle);
+    furi_assert(instance->state == IrdaWorkerStateRunRx);
 
     furi_hal_irda_async_rx_set_timeout_isr_callback(NULL, NULL);
     furi_hal_irda_async_rx_set_capture_isr_callback(NULL, NULL);
     furi_hal_irda_async_rx_stop();
 
-    xTaskNotify(instance->worker_handle, IRDA_WORKER_EXIT, eSetBits);
-
-    instance->worker_handle = NULL;
-
+    osEventFlagsSet(instance->events, IRDA_WORKER_EXIT);
     furi_thread_join(instance->thread);
+
+    BaseType_t xReturn = pdFAIL;
+    xReturn = xStreamBufferReset(instance->stream);
+    furi_assert(xReturn == pdPASS);
+    instance->state = IrdaWorkerStateIdle;
+    instance->state = IrdaWorkerStateIdle;
 }
 
 bool irda_worker_signal_is_decoded(const IrdaWorkerSignal* signal) {
@@ -225,13 +287,268 @@ void irda_worker_get_raw_signal(const IrdaWorkerSignal* signal, const uint32_t**
     *timings_cnt = signal->timings_cnt;
 }
 
-const IrdaMessage* irda_worker_get_decoded_message(const IrdaWorkerSignal* signal) {
+const IrdaMessage* irda_worker_get_decoded_signal(const IrdaWorkerSignal* signal) {
     furi_assert(signal);
     return &signal->data.message;
 }
 
-void irda_worker_enable_blink_on_receiving(IrdaWorker* instance, bool enable) {
+void irda_worker_rx_enable_blink_on_receiving(IrdaWorker* instance, bool enable) {
     furi_assert(instance);
     instance->blink_enable = enable;
 }
 
+void irda_worker_tx_start(IrdaWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->state == IrdaWorkerStateIdle);
+
+    // size have to be greater than api hal irda async tx buffer size
+    xStreamBufferSetTriggerLevel(instance->stream, sizeof(IrdaWorkerTiming));
+
+    osEventFlagsClear(instance->events, IRDA_WORKER_ALL_EVENTS);
+    furi_thread_set_callback(instance->thread, irda_worker_tx_thread);
+    furi_thread_start(instance->thread);
+
+    instance->tx.steady_signal_sent = false;
+    instance->tx.need_reinitialization = false;
+    furi_hal_irda_async_tx_set_data_isr_callback(irda_worker_furi_hal_data_isr_callback, instance);
+    furi_hal_irda_async_tx_set_signal_sent_isr_callback(irda_worker_furi_hal_message_sent_isr_callback, instance);
+
+    instance->state = IrdaWorkerStateStartTx;
+}
+
+static void irda_worker_furi_hal_message_sent_isr_callback(void* context) {
+    IrdaWorker* instance = context;
+    uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_TX_MESSAGE_SENT);
+    furi_check(flags_set & IRDA_WORKER_TX_MESSAGE_SENT);
+}
+
+static FuriHalIrdaTxGetDataState irda_worker_furi_hal_data_isr_callback(void* context, uint32_t* duration, bool* level) {
+    furi_assert(context);
+    furi_assert(duration);
+    furi_assert(level);
+
+    IrdaWorker* instance = context;
+    IrdaWorkerTiming timing = {.state = FuriHalIrdaTxGetDataStateError} ;
+
+    if (sizeof(IrdaWorkerTiming) == xStreamBufferReceiveFromISR(instance->stream, &timing, sizeof(IrdaWorkerTiming), 0)) {
+        *level = timing.level;
+        *duration = timing.duration;
+        furi_assert(timing.state != FuriHalIrdaTxGetDataStateError);
+    } else {
+        furi_assert(0);
+        timing.state = FuriHalIrdaTxGetDataStateError;
+    }
+
+    uint32_t flags_set = osEventFlagsSet(instance->events, IRDA_WORKER_TX_FILL_BUFFER);
+    furi_check(flags_set & IRDA_WORKER_TX_FILL_BUFFER);
+
+    return timing.state;
+}
+
+static bool irda_get_new_signal(IrdaWorker* instance) {
+    bool new_signal_obtained = false;
+
+    IrdaWorkerGetSignalResponse response = instance->tx.get_signal_callback(instance->tx.get_signal_context, instance);
+    if (response == IrdaWorkerGetSignalResponseNew) {
+        uint32_t new_tx_frequency = 0;
+        float new_tx_duty_cycle = 0;
+        if (instance->signal.decoded) {
+            new_tx_frequency = irda_get_protocol_frequency(instance->signal.data.message.protocol);
+            new_tx_duty_cycle = irda_get_protocol_duty_cycle(instance->signal.data.message.protocol);
+        } else {
+            furi_assert(instance->signal.timings_cnt > 1);
+            new_tx_frequency = IRDA_COMMON_CARRIER_FREQUENCY;
+            new_tx_duty_cycle = IRDA_COMMON_DUTY_CYCLE;
+        }
+
+        instance->tx.tx_raw_cnt = 0;
+        instance->tx.need_reinitialization = (new_tx_frequency != instance->tx.frequency) || (new_tx_duty_cycle != instance->tx.duty_cycle);
+        instance->tx.frequency = new_tx_frequency;
+        instance->tx.duty_cycle = new_tx_duty_cycle;
+        if (instance->signal.decoded) {
+            irda_reset_encoder(instance->irda_encoder, &instance->signal.data.message);
+        }
+        new_signal_obtained = true;
+    } else if (response == IrdaWorkerGetSignalResponseSame) {
+        new_signal_obtained = true;
+        /* no need to reinit */
+    } else if (response == IrdaWorkerGetSignalResponseStop) {
+        new_signal_obtained = false;
+    } else {
+        furi_assert(0);
+    }
+
+    return new_signal_obtained;
+}
+
+static bool irda_worker_tx_fill_buffer(IrdaWorker* instance) {
+    bool new_data_available = true;
+    IrdaWorkerTiming timing;
+    IrdaStatus status = IrdaStatusError;
+
+    while(!xStreamBufferIsFull(instance->stream) && !instance->tx.need_reinitialization && new_data_available) {
+        if (instance->signal.decoded) {
+            status = irda_encode(instance->irda_encoder, &timing.duration, &timing.level);
+        } else {
+            timing.duration = instance->signal.data.timings[instance->tx.tx_raw_cnt];
+/* raw always starts from Mark, but we fulfill it with space delay at start */
+            timing.level = (instance->tx.tx_raw_cnt % 2);
+            ++instance->tx.tx_raw_cnt;
+            if (instance->tx.tx_raw_cnt >= instance->signal.timings_cnt) {
+                instance->tx.tx_raw_cnt = 0;
+                status = IrdaStatusDone;
+            } else {
+                status = IrdaStatusOk;
+            }
+        }
+
+        if (status == IrdaStatusError) {
+            furi_assert(0);
+            new_data_available = false;
+            break;
+        } else if (status == IrdaStatusOk) {
+            timing.state = FuriHalIrdaTxGetDataStateOk;
+        } else if (status == IrdaStatusDone) {
+            timing.state = FuriHalIrdaTxGetDataStateDone;
+
+            new_data_available = irda_get_new_signal(instance);
+            if (instance->tx.need_reinitialization || !new_data_available) {
+                timing.state = FuriHalIrdaTxGetDataStateLastDone;
+            }
+        } else {
+            furi_assert(0);
+        }
+        uint32_t written_size = xStreamBufferSend(instance->stream, &timing, sizeof(IrdaWorkerTiming), 0);
+        furi_assert(sizeof(IrdaWorkerTiming) == written_size);
+    }
+
+    return new_data_available;
+}
+
+static int32_t irda_worker_tx_thread(void* thread_context) {
+    IrdaWorker* instance = thread_context;
+    furi_assert(instance->state == IrdaWorkerStateStartTx);
+    furi_assert(thread_context);
+
+    uint32_t events = 0;
+    bool new_data_available = true;
+    bool exit = false;
+
+    exit = !irda_get_new_signal(instance);
+    furi_assert(!exit);
+
+    while(!exit) {
+        switch (instance->state) {
+        case IrdaWorkerStateStartTx:
+            instance->tx.need_reinitialization = false;
+            new_data_available = irda_worker_tx_fill_buffer(instance);
+            furi_hal_irda_async_tx_start(instance->tx.frequency, instance->tx.duty_cycle);
+
+            if (!new_data_available) {
+                instance->state = IrdaWorkerStateStopTx;
+            } else if (instance->tx.need_reinitialization) {
+                instance->state = IrdaWorkerStateWaitTxEnd;
+            } else {
+                instance->state = IrdaWorkerStateRunTx;
+            }
+
+            break;
+        case IrdaWorkerStateStopTx:
+            furi_hal_irda_async_tx_stop();
+            exit = true;
+            break;
+        case IrdaWorkerStateWaitTxEnd:
+            furi_hal_irda_async_tx_wait_termination();
+            instance->state = IrdaWorkerStateStartTx;
+
+            events = osEventFlagsGet(instance->events);
+            if(events & IRDA_WORKER_EXIT) {
+                exit = true;
+                break;
+            }
+
+            break;
+        case IrdaWorkerStateRunTx:
+            events = osEventFlagsWait(instance->events, IRDA_WORKER_ALL_TX_EVENTS, 0, osWaitForever);
+            furi_check(events & IRDA_WORKER_ALL_TX_EVENTS); /* at least one caught */
+
+            if (events & IRDA_WORKER_EXIT) {
+                instance->state = IrdaWorkerStateStopTx;
+                break;
+            }
+
+            if (events & IRDA_WORKER_TX_FILL_BUFFER) {
+                irda_worker_tx_fill_buffer(instance);
+
+                if (instance->tx.need_reinitialization) {
+                    instance->state = IrdaWorkerStateWaitTxEnd;
+                }
+            }
+
+            if (events & IRDA_WORKER_TX_MESSAGE_SENT) {
+                if (instance->tx.message_sent_callback)
+                    instance->tx.message_sent_callback(instance->tx.message_sent_context);
+            }
+            break;
+        default:
+            furi_assert(0);
+            break;
+        }
+    }
+
+    return 0;
+}
+
+void irda_worker_tx_set_get_signal_callback(IrdaWorker* instance, IrdaWorkerGetSignalCallback callback, void* context) {
+    furi_assert(instance);
+    instance->tx.get_signal_callback = callback;
+    instance->tx.get_signal_context = context;
+}
+
+void irda_worker_tx_set_signal_sent_callback(IrdaWorker* instance, IrdaWorkerMessageSentCallback callback, void* context) {
+    furi_assert(instance);
+    instance->tx.message_sent_callback = callback;
+    instance->tx.message_sent_context = context;
+}
+
+void irda_worker_tx_stop(IrdaWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->state != IrdaWorkerStateRunRx);
+
+    osEventFlagsSet(instance->events, IRDA_WORKER_EXIT);
+    furi_thread_join(instance->thread);
+    furi_hal_irda_async_tx_set_data_isr_callback(NULL, NULL);
+    furi_hal_irda_async_tx_set_signal_sent_isr_callback(NULL, NULL);
+
+    instance->signal.timings_cnt = 0;
+    BaseType_t xReturn = pdFAIL;
+    xReturn = xStreamBufferReset(instance->stream);
+    furi_assert(xReturn == pdPASS);
+    instance->state = IrdaWorkerStateIdle;
+}
+
+void irda_worker_set_decoded_signal(IrdaWorker* instance, const IrdaMessage* message) {
+    furi_assert(instance);
+    furi_assert(message);
+
+    instance->signal.decoded = true;
+    instance->signal.data.message = *message;
+}
+
+void irda_worker_set_raw_signal(IrdaWorker* instance, const uint32_t* timings, size_t timings_cnt) {
+    furi_assert(instance);
+    furi_assert(timings);
+    furi_assert(timings_cnt > 2);
+
+    instance->signal.data.timings[0] = IRDA_RAW_TX_TIMING_DELAY_US;
+    memcpy(&instance->signal.data.timings[1], timings, timings_cnt * sizeof(uint32_t));
+    instance->signal.decoded = false;
+    instance->signal.timings_cnt = timings_cnt + 1;
+}
+
+IrdaWorkerGetSignalResponse irda_worker_tx_get_signal_steady_callback(void* context, IrdaWorker* instance) {
+    IrdaWorkerGetSignalResponse response = instance->tx.steady_signal_sent ? IrdaWorkerGetSignalResponseSame : IrdaWorkerGetSignalResponseNew;
+    instance->tx.steady_signal_sent = true;
+    return response;
+}
+

+ 86 - 18
lib/irda/worker/irda_worker.h

@@ -7,11 +7,28 @@
 extern "C" {
 #endif
 
+#define MAX_TIMINGS_AMOUNT                  512
+
 /** Interface struct of irda worker */
 typedef struct IrdaWorker IrdaWorker;
 /** Interface struct of received signal */
 typedef struct IrdaWorkerSignal IrdaWorkerSignal;
 
+typedef enum {
+    IrdaWorkerGetSignalResponseNew,     /** Signal, provided by callback is new and encoder should be reseted */
+    IrdaWorkerGetSignalResponseSame,    /** Signal, provided by callback is same. No encoder resetting. */
+    IrdaWorkerGetSignalResponseStop,    /** No more signals available. */
+} IrdaWorkerGetSignalResponse;
+
+/** Callback type for providing next signal to send. Should be used with
+ * irda_worker_make_decoded_signal() or irda_worker_make_raw_signal()
+ */
+typedef IrdaWorkerGetSignalResponse (*IrdaWorkerGetSignalCallback)(void* context, IrdaWorker* instance);
+
+/** Callback type for 'message is sent' event */
+typedef void (*IrdaWorkerMessageSentCallback)(void* context);
+
+
 /** Callback type to call by IrdaWorker thread when new signal is received */
 typedef void (*IrdaWorkerReceivedSignalCallback)(void* context, IrdaWorkerSignal* received_signal);
 
@@ -27,31 +44,33 @@ IrdaWorker* irda_worker_alloc();
  */
 void irda_worker_free(IrdaWorker* instance);
 
-/** Received data callback IrdaWorker
+/** Start IrdaWorker thread, initialise furi-hal, prepare all work.
  *
  * @param[in]   instance - IrdaWorker instance
- * @param[in]   callback - IrdaWorkerReceivedSignalCallback callback
  */
-void irda_worker_set_received_signal_callback(IrdaWorker* instance, IrdaWorkerReceivedSignalCallback callback);
+void irda_worker_rx_start(IrdaWorker* instance);
 
-/** Context callback IrdaWorker
+/** Stop IrdaWorker thread, deinitialize furi-hal.
  *
  * @param[in]   instance - IrdaWorker instance
- * @param[in]   context - context to pass to callbacks
  */
-void irda_worker_set_context(IrdaWorker* instance, void* context);
+void irda_worker_rx_stop(IrdaWorker* instance);
 
-/** Start IrdaWorker thread, initialise furi-hal, prepare all work.
+/** Set received data callback IrdaWorker
  *
  * @param[in]   instance - IrdaWorker instance
+ * @param[in]   context - context to pass to callbacks
+ * @param[in]   callback - IrdaWorkerReceivedSignalCallback callback
  */
-void irda_worker_start(IrdaWorker* instance);
+void irda_worker_rx_set_received_signal_callback(IrdaWorker* instance, IrdaWorkerReceivedSignalCallback callback, void* context);
 
-/** Stop IrdaWorker thread, deinitialize furi-hal.
+/** Enable blinking on receiving any signal on IR port.
  *
- * @param[in]   instance - IrdaWorker instance
+ * @param[in]   instance - instance of IrdaWorker
+ * @param[in]   enable - true if you want to enable blinking
+ *                       false otherwise
  */
-void irda_worker_stop(IrdaWorker* instance);
+void irda_worker_rx_enable_blink_on_receiving(IrdaWorker* instance, bool enable);
 
 /** Clarify is received signal either decoded or raw
  *
@@ -60,6 +79,48 @@ void irda_worker_stop(IrdaWorker* instance);
  */
 bool irda_worker_signal_is_decoded(const IrdaWorkerSignal* signal);
 
+/** Start transmitting signal. Callback IrdaWorkerGetSignalCallback should be
+ * set before this function is called, as it calls for it to fill buffer before
+ * starting transmission.
+ *
+ * @param[in]   instance - IrdaWorker instance
+ */
+void irda_worker_tx_start(IrdaWorker* instance);
+
+/** Stop transmitting signal. Waits for end of current signal and stops transmission.
+ *
+ * @param[in]   instance - IrdaWorker instance
+ */
+void irda_worker_tx_stop(IrdaWorker* instance);
+
+/** Set callback for providing next signal to send
+ *
+ * @param[in]   instance - IrdaWorker instance
+ * @param[in]   context - context to pass to callbacks
+ * @param[in]   callback - IrdaWorkerGetSignalCallback callback
+ */
+void irda_worker_tx_set_get_signal_callback(IrdaWorker* instance, IrdaWorkerGetSignalCallback callback, void* context);
+
+/** Set callback for end of signal transmitting
+ *
+ * @param[in]   instance - IrdaWorker instance
+ * @param[in]   context - context to pass to callbacks
+ * @param[in]   callback - IrdaWorkerMessageSentCallback callback
+ */
+void irda_worker_tx_set_signal_sent_callback(IrdaWorker* instance, IrdaWorkerMessageSentCallback callback, void* context);
+
+/** Callback to pass to irda_worker_tx_set_get_signal_callback() if signal
+ * is steady and will not be changed between irda_worker start and stop.
+ * Before starting transmission, desired steady signal must be set with
+ * irda_worker_make_decoded_signal() or irda_worker_make_raw_signal().
+ *
+ * This function should not be implicitly called.
+ *
+ * @param[in]   context - context
+ * @param[out]  instance - IrdaWorker instance
+ */
+IrdaWorkerGetSignalResponse irda_worker_tx_get_signal_steady_callback(void* context, IrdaWorker* instance);
+
 /** Acquire raw signal from interface struct 'IrdaWorkerSignal'.
  * First, you have to ensure that signal is raw.
  *
@@ -73,17 +134,24 @@ void irda_worker_get_raw_signal(const IrdaWorkerSignal* signal, const uint32_t**
  * First, you have to ensure that signal is decoded.
  *
  * @param[in]   signal - received signal
- * @return      decoded irda message
+ * @return      decoded IRDA message
  */
-const IrdaMessage* irda_worker_get_decoded_message(const IrdaWorkerSignal* signal);
+const IrdaMessage* irda_worker_get_decoded_signal(const IrdaWorkerSignal* signal);
 
-/** Enable blinking on receiving any signal on IR port.
+/** Set current decoded signal for IrdaWorker instance
  *
- * @param[in]   instance - instance of IrdaWorker
- * @param[in]   enable - true if you want to enable blinking
- *                       false otherwise
+ * @param[out]  instance - IrdaWorker instance
+ * @param[in]   message - decoded signal
+ */
+void irda_worker_set_decoded_signal(IrdaWorker* instance, const IrdaMessage* message);
+
+/** Set current raw signal for IrdaWorker instance
+ *
+ * @param[out]  instance - IrdaWorker instance
+ * @param[in]   timings - array of raw timings
+ * @param[in]   timings_cnt - size of array of raw timings
  */
-void irda_worker_enable_blink_on_receiving(IrdaWorker* instance, bool enable);
+void irda_worker_set_raw_signal(IrdaWorker* instance, const uint32_t* timings, size_t timings_cnt);
 
 #ifdef __cplusplus
 }