xremote_learn.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /*!
  2. * @file flipper-xremote/xremote_learn.c
  3. @license This project is released under the GNU GPLv3 License
  4. * @copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com)
  5. *
  6. * @brief Functionality to read and store input from new remote.
  7. */
  8. #include "xremote_learn.h"
  9. #include "views/xremote_learn_view.h"
  10. struct XRemoteLearnContext {
  11. /* XRemote context */
  12. XRemoteSignalReceiver* ir_receiver;
  13. XRemoteAppContext* app_ctx;
  14. XRemoteView* signal_view;
  15. XRemoteViewID curr_view;
  16. XRemoteViewID prev_view;
  17. /* Main infrared app context */
  18. InfraredRemote* ir_remote;
  19. InfraredSignal* ir_signal;
  20. /* User interactions */
  21. TextInput* text_input;
  22. DialogEx* dialog_ex;
  23. /* User context and clear callback */
  24. XRemoteClearCallback on_clear;
  25. void* context;
  26. /* Private control flags */
  27. char text_store[XREMOTE_APP_TEXT_MAX + 1];
  28. uint8_t current_button;
  29. bool finish_learning;
  30. bool stop_receiver;
  31. bool is_dirty;
  32. };
  33. void xremote_learn_send_event(XRemoteLearnContext* learn_ctx, XRemoteEvent event) {
  34. xremote_app_assert_void(learn_ctx);
  35. if(event == XRemoteEventSignalFinish) learn_ctx->finish_learning = true;
  36. ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
  37. view_dispatcher_send_custom_event(view_disp, event);
  38. }
  39. const char* xremote_learn_get_curr_button_name(XRemoteLearnContext* learn_ctx) {
  40. xremote_app_assert(learn_ctx, NULL);
  41. return xremote_button_get_name(learn_ctx->current_button);
  42. }
  43. int xremote_learn_get_curr_button_index(XRemoteLearnContext* learn_ctx) {
  44. xremote_app_assert(learn_ctx, -1);
  45. return learn_ctx->current_button;
  46. }
  47. InfraredRemote* xremote_learn_get_ir_remote(XRemoteLearnContext* learn_ctx) {
  48. xremote_app_assert(learn_ctx, NULL);
  49. return learn_ctx->ir_remote;
  50. }
  51. InfraredSignal* xremote_learn_get_ir_signal(XRemoteLearnContext* learn_ctx) {
  52. xremote_app_assert(learn_ctx, NULL);
  53. return learn_ctx->ir_signal;
  54. }
  55. XRemoteSignalReceiver* xremote_learn_get_ir_receiver(XRemoteLearnContext* learn_ctx) {
  56. xremote_app_assert(learn_ctx, NULL);
  57. return learn_ctx->ir_receiver;
  58. }
  59. XRemoteAppContext* xremote_learn_get_app_context(XRemoteLearnContext* learn_ctx) {
  60. xremote_app_assert(learn_ctx, NULL);
  61. return learn_ctx->app_ctx;
  62. }
  63. bool xremote_learn_has_buttons(XRemoteLearnContext* learn_ctx) {
  64. xremote_app_assert(learn_ctx, false);
  65. return infrared_remote_get_button_count(learn_ctx->ir_remote) > 0;
  66. }
  67. static void xremote_learn_switch_to_view(XRemoteLearnContext* learn_ctx, XRemoteViewID view_id) {
  68. xremote_app_assert_void(learn_ctx);
  69. learn_ctx->prev_view = learn_ctx->curr_view;
  70. learn_ctx->curr_view = view_id;
  71. ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
  72. view_dispatcher_switch_to_view(view_disp, view_id);
  73. }
  74. static void xremote_learn_context_rx_stop(XRemoteLearnContext* learn_ctx) {
  75. xremote_app_assert_void(learn_ctx);
  76. learn_ctx->stop_receiver = true;
  77. xremote_signal_receiver_stop(learn_ctx->ir_receiver);
  78. }
  79. static void xremote_learn_context_rx_start(XRemoteLearnContext* learn_ctx) {
  80. xremote_app_assert_void(learn_ctx);
  81. learn_ctx->finish_learning = false;
  82. learn_ctx->stop_receiver = false;
  83. xremote_signal_receiver_start(learn_ctx->ir_receiver);
  84. }
  85. static void xremote_learn_exit_dialog_free(XRemoteLearnContext* learn_ctx) {
  86. xremote_app_assert_void(learn_ctx);
  87. xremote_app_assert_void(learn_ctx->dialog_ex);
  88. ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
  89. view_dispatcher_remove_view(view_disp, XRemoteViewDialogExit);
  90. dialog_ex_free(learn_ctx->dialog_ex);
  91. learn_ctx->dialog_ex = NULL;
  92. }
  93. static void xremote_learn_exit_dialog_alloc(
  94. XRemoteLearnContext* learn_ctx,
  95. DialogExResultCallback callback) {
  96. xremote_app_assert_void(learn_ctx);
  97. xremote_learn_exit_dialog_free(learn_ctx);
  98. ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
  99. const char* dialog_text = "All unsaved data\nwill be lost!";
  100. const char* header_text = "Exit to XRemote Menu?";
  101. learn_ctx->dialog_ex = dialog_ex_alloc();
  102. View* view = dialog_ex_get_view(learn_ctx->dialog_ex);
  103. view_dispatcher_add_view(view_disp, XRemoteViewDialogExit, view);
  104. dialog_ex_set_header(learn_ctx->dialog_ex, header_text, 64, 11, AlignCenter, AlignTop);
  105. dialog_ex_set_text(learn_ctx->dialog_ex, dialog_text, 64, 25, AlignCenter, AlignTop);
  106. dialog_ex_set_icon(learn_ctx->dialog_ex, 0, 0, NULL);
  107. dialog_ex_set_left_button_text(learn_ctx->dialog_ex, "Exit");
  108. dialog_ex_set_center_button_text(learn_ctx->dialog_ex, "Save");
  109. dialog_ex_set_right_button_text(learn_ctx->dialog_ex, "Stay");
  110. dialog_ex_set_result_callback(learn_ctx->dialog_ex, callback);
  111. dialog_ex_set_context(learn_ctx->dialog_ex, learn_ctx);
  112. }
  113. static uint32_t xremote_learn_view_exit_callback(void* context) {
  114. UNUSED(context);
  115. return XRemoteViewLearn;
  116. }
  117. static void xremote_learn_dialog_exit_callback(DialogExResult result, void* context) {
  118. XRemoteLearnContext* learn_ctx = (XRemoteLearnContext*)context;
  119. xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
  120. if(result == DialogExResultLeft)
  121. xremote_learn_send_event(learn_ctx, XRemoteEventSignalExit);
  122. else if(result == DialogExResultRight)
  123. xremote_learn_send_event(learn_ctx, XRemoteEventSignalRetry);
  124. else if(result == DialogExResultCenter)
  125. xremote_learn_send_event(learn_ctx, XRemoteEventSignalFinish);
  126. }
  127. static uint32_t xremote_learn_text_input_exit_callback(void* context) {
  128. TextInput* text_input = context;
  129. XRemoteLearnContext* learn_ctx;
  130. XRemoteEvent event;
  131. learn_ctx = text_input_get_validator_callback_context(text_input);
  132. xremote_app_assert(learn_ctx, XRemoteViewSubmenu);
  133. event = learn_ctx->prev_view == XRemoteViewSignal ? XRemoteEventSignalReceived :
  134. XRemoteEventSignalRetry;
  135. if(learn_ctx->current_button >= XREMOTE_BUTTON_COUNT)
  136. learn_ctx->current_button = XREMOTE_BUTTON_COUNT - 1;
  137. learn_ctx->finish_learning = false;
  138. xremote_learn_send_event(learn_ctx, event);
  139. return XRemoteViewTextInput;
  140. }
  141. static void xremote_learn_text_input_callback(void* context) {
  142. xremote_app_assert_void(context);
  143. XRemoteLearnContext* learn_ctx = context;
  144. if(learn_ctx->is_dirty) {
  145. const char* name = xremote_learn_get_curr_button_name(learn_ctx);
  146. if(!infrared_remote_get_button_by_name(learn_ctx->ir_remote, name)) {
  147. InfraredSignal* signal = xremote_learn_get_ir_signal(learn_ctx);
  148. infrared_remote_push_button(learn_ctx->ir_remote, name, signal);
  149. }
  150. learn_ctx->is_dirty = false;
  151. }
  152. if(infrared_remote_get_button_count(learn_ctx->ir_remote) &&
  153. learn_ctx->text_store[0] != '\0') {
  154. char output_file[256];
  155. snprintf(
  156. output_file,
  157. sizeof(output_file),
  158. "%s/%s%s",
  159. XREMOTE_APP_FOLDER,
  160. learn_ctx->text_store,
  161. XREMOTE_APP_EXTENSION);
  162. infrared_remote_set_name(learn_ctx->ir_remote, learn_ctx->text_store);
  163. infrared_remote_set_path(learn_ctx->ir_remote, output_file);
  164. infrared_remote_store(learn_ctx->ir_remote);
  165. infrared_remote_reset(learn_ctx->ir_remote);
  166. }
  167. xremote_learn_send_event(learn_ctx, XRemoteEventSignalExit);
  168. }
  169. static void xremote_learn_signal_callback(void* context, InfraredSignal* signal) {
  170. XRemoteLearnContext* learn_ctx = context;
  171. xremote_app_assert_void(!learn_ctx->stop_receiver);
  172. xremote_app_assert_void(!learn_ctx->finish_learning);
  173. learn_ctx->stop_receiver = true;
  174. learn_ctx->is_dirty = true;
  175. infrared_signal_set_signal(learn_ctx->ir_signal, signal);
  176. xremote_learn_send_event(learn_ctx, XRemoteEventSignalReceived);
  177. }
  178. static void xremote_learn_exit_dialog_ask(XRemoteLearnContext* learn_ctx) {
  179. xremote_app_assert_void(learn_ctx);
  180. if(infrared_remote_get_button_count(learn_ctx->ir_remote) || learn_ctx->is_dirty) {
  181. xremote_learn_exit_dialog_alloc(learn_ctx, xremote_learn_dialog_exit_callback);
  182. xremote_learn_switch_to_view(learn_ctx, XRemoteViewDialogExit);
  183. return;
  184. }
  185. learn_ctx->is_dirty = false;
  186. xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
  187. }
  188. static void xremote_learn_finish(XRemoteLearnContext* learn_ctx) {
  189. xremote_app_assert_void(learn_ctx);
  190. xremote_app_assert_void(learn_ctx->text_input);
  191. if(infrared_remote_get_button_count(learn_ctx->ir_remote) || learn_ctx->is_dirty) {
  192. snprintf(learn_ctx->text_store, XREMOTE_APP_TEXT_MAX, "Remote_");
  193. text_input_set_header_text(learn_ctx->text_input, "Name new remote");
  194. text_input_set_result_callback(
  195. learn_ctx->text_input,
  196. xremote_learn_text_input_callback,
  197. learn_ctx,
  198. learn_ctx->text_store,
  199. XREMOTE_APP_TEXT_MAX,
  200. true);
  201. xremote_learn_switch_to_view(learn_ctx, XRemoteViewTextInput);
  202. return;
  203. }
  204. learn_ctx->is_dirty = false;
  205. xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
  206. }
  207. static bool xremote_learn_custom_event_dummy_callback(void* context, uint32_t event) {
  208. UNUSED(context);
  209. UNUSED(event);
  210. return true;
  211. }
  212. static bool xremote_learn_custom_event_callback(void* context, uint32_t event) {
  213. xremote_app_assert(context, false);
  214. XRemoteLearnContext* learn_ctx = context;
  215. if(learn_ctx->finish_learning && event != XRemoteEventSignalFinish &&
  216. event != XRemoteEventSignalAskExit && event != XRemoteEventSignalExit)
  217. return true;
  218. if(event == XRemoteEventSignalReceived) {
  219. xremote_learn_context_rx_stop(learn_ctx);
  220. xremote_learn_switch_to_view(learn_ctx, XRemoteViewSignal);
  221. } else if(event == XRemoteEventSignalSave) {
  222. const char* name = xremote_learn_get_curr_button_name(learn_ctx);
  223. infrared_remote_delete_button_by_name(learn_ctx->ir_remote, name);
  224. InfraredSignal* signal = xremote_learn_get_ir_signal(learn_ctx);
  225. infrared_remote_push_button(learn_ctx->ir_remote, name, signal);
  226. learn_ctx->is_dirty = false;
  227. if(++learn_ctx->current_button >= XREMOTE_BUTTON_COUNT) {
  228. xremote_learn_context_rx_stop(learn_ctx);
  229. xremote_learn_finish(learn_ctx);
  230. return true;
  231. }
  232. xremote_learn_context_rx_start(learn_ctx);
  233. xremote_learn_switch_to_view(learn_ctx, XRemoteViewLearn);
  234. } else if(event == XRemoteEventSignalSkip) {
  235. learn_ctx->current_button++;
  236. learn_ctx->is_dirty = false;
  237. if(learn_ctx->current_button >= XREMOTE_BUTTON_COUNT) {
  238. if(xremote_learn_has_buttons(learn_ctx)) {
  239. xremote_learn_context_rx_stop(learn_ctx);
  240. xremote_learn_finish(learn_ctx);
  241. return true;
  242. }
  243. learn_ctx->current_button = 0;
  244. }
  245. xremote_learn_context_rx_start(learn_ctx);
  246. xremote_learn_switch_to_view(learn_ctx, XRemoteViewLearn);
  247. } else if(event == XRemoteEventSignalRetry) {
  248. learn_ctx->is_dirty = false;
  249. xremote_learn_context_rx_start(learn_ctx);
  250. xremote_learn_switch_to_view(learn_ctx, XRemoteViewLearn);
  251. } else if(event == XRemoteEventSignalFinish) {
  252. xremote_learn_context_rx_stop(learn_ctx);
  253. xremote_learn_finish(learn_ctx);
  254. } else if(event == XRemoteEventSignalAskExit) {
  255. xremote_learn_context_rx_stop(learn_ctx);
  256. xremote_learn_exit_dialog_ask(learn_ctx);
  257. } else if(event == XRemoteEventSignalExit) {
  258. learn_ctx->is_dirty = false;
  259. xremote_learn_context_rx_stop(learn_ctx);
  260. xremote_learn_switch_to_view(learn_ctx, XRemoteViewSubmenu);
  261. }
  262. return true;
  263. }
  264. static XRemoteLearnContext* xremote_learn_context_alloc(XRemoteAppContext* app_ctx) {
  265. XRemoteLearnContext* learn_ctx = malloc(sizeof(XRemoteLearnContext));
  266. learn_ctx->ir_signal = infrared_signal_alloc();
  267. learn_ctx->ir_remote = infrared_remote_alloc();
  268. learn_ctx->app_ctx = app_ctx;
  269. learn_ctx->dialog_ex = NULL;
  270. learn_ctx->text_store[0] = 0;
  271. learn_ctx->current_button = 0;
  272. learn_ctx->curr_view = XRemoteViewLearn;
  273. learn_ctx->prev_view = XRemoteViewNone;
  274. learn_ctx->finish_learning = false;
  275. learn_ctx->stop_receiver = false;
  276. learn_ctx->is_dirty = false;
  277. learn_ctx->signal_view = xremote_learn_success_view_alloc(app_ctx, learn_ctx);
  278. View* view = xremote_view_get_view(learn_ctx->signal_view);
  279. view_set_previous_callback(view, xremote_learn_view_exit_callback);
  280. view_dispatcher_add_view(app_ctx->view_dispatcher, XRemoteViewSignal, view);
  281. learn_ctx->text_input = text_input_alloc();
  282. text_input_set_validator(learn_ctx->text_input, NULL, learn_ctx);
  283. view = text_input_get_view(learn_ctx->text_input);
  284. view_set_previous_callback(view, xremote_learn_text_input_exit_callback);
  285. view_dispatcher_add_view(app_ctx->view_dispatcher, XRemoteViewTextInput, view);
  286. view_dispatcher_set_custom_event_callback(
  287. app_ctx->view_dispatcher, xremote_learn_custom_event_callback);
  288. view_dispatcher_set_event_callback_context(app_ctx->view_dispatcher, learn_ctx);
  289. learn_ctx->ir_receiver = xremote_signal_receiver_alloc(app_ctx);
  290. xremote_signal_receiver_set_context(learn_ctx->ir_receiver, learn_ctx, NULL);
  291. xremote_signal_receiver_set_rx_callback(learn_ctx->ir_receiver, xremote_learn_signal_callback);
  292. return learn_ctx;
  293. }
  294. static void xremote_learn_context_free(XRemoteLearnContext* learn_ctx) {
  295. xremote_app_assert_void(learn_ctx);
  296. xremote_signal_receiver_stop(learn_ctx->ir_receiver);
  297. xremote_learn_exit_dialog_free(learn_ctx);
  298. ViewDispatcher* view_disp = learn_ctx->app_ctx->view_dispatcher;
  299. view_dispatcher_set_custom_event_callback(
  300. view_disp, xremote_learn_custom_event_dummy_callback);
  301. view_dispatcher_remove_view(view_disp, XRemoteViewTextInput);
  302. text_input_free(learn_ctx->text_input);
  303. view_dispatcher_remove_view(view_disp, XRemoteViewSignal);
  304. xremote_view_free(learn_ctx->signal_view);
  305. xremote_signal_receiver_free(learn_ctx->ir_receiver);
  306. infrared_signal_free(learn_ctx->ir_signal);
  307. infrared_remote_free(learn_ctx->ir_remote);
  308. free(learn_ctx);
  309. }
  310. static void xremote_learn_context_clear_callback(void* context) {
  311. XRemoteLearnContext* learn = context;
  312. xremote_learn_context_free(learn);
  313. }
  314. XRemoteApp* xremote_learn_alloc(XRemoteAppContext* app_ctx) {
  315. XRemoteApp* app = xremote_app_alloc(app_ctx);
  316. app->view_id = XRemoteViewLearn;
  317. XRemoteLearnContext* learn = xremote_learn_context_alloc(app_ctx);
  318. app->view_ctx = xremote_learn_view_alloc(app->app_ctx, learn);
  319. View* view = xremote_view_get_view(app->view_ctx);
  320. ViewDispatcher* view_disp = app_ctx->view_dispatcher;
  321. view_dispatcher_add_view(view_disp, app->view_id, view);
  322. xremote_app_view_set_previous_callback(app, xremote_learn_view_exit_callback);
  323. xremote_app_set_view_context(app, learn, xremote_learn_context_clear_callback);
  324. xremote_signal_receiver_start(learn->ir_receiver);
  325. return app;
  326. }