xremote_learn.c 15 KB

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