loader.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. #include <callback/loader.h>
  2. static void loader_error_draw(Canvas *canvas, DataLoaderModel *model)
  3. {
  4. if (canvas == NULL)
  5. {
  6. FURI_LOG_E(TAG, "error_draw - canvas is NULL");
  7. DEV_CRASH();
  8. return;
  9. }
  10. if (model->fhttp->last_response != NULL)
  11. {
  12. if (strstr(model->fhttp->last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  13. {
  14. canvas_clear(canvas);
  15. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  16. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  17. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  18. }
  19. else if (strstr(model->fhttp->last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  20. {
  21. canvas_clear(canvas);
  22. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  23. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  24. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  25. }
  26. else if (strstr(model->fhttp->last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
  27. {
  28. canvas_clear(canvas);
  29. canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
  30. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  31. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  32. }
  33. else if (strstr(model->fhttp->last_response, "[PONG]") != NULL)
  34. {
  35. canvas_clear(canvas);
  36. canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
  37. }
  38. else
  39. {
  40. canvas_clear(canvas);
  41. FURI_LOG_E(TAG, "Received an error: %s", model->fhttp->last_response);
  42. canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
  43. canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
  44. }
  45. }
  46. else
  47. {
  48. canvas_clear(canvas);
  49. canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
  50. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  51. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  52. }
  53. }
  54. static void loader_process_callback(void *context)
  55. {
  56. if (context == NULL)
  57. {
  58. FURI_LOG_E(TAG, "loader_process_callback - context is NULL");
  59. DEV_CRASH();
  60. return;
  61. }
  62. FlipWorldApp *app = (FlipWorldApp *)context;
  63. View *view = app->view_loader;
  64. DataState current_data_state;
  65. DataLoaderModel *loader_model = NULL;
  66. with_view_model(
  67. view,
  68. DataLoaderModel * model,
  69. {
  70. current_data_state = model->data_state;
  71. loader_model = model;
  72. },
  73. false);
  74. if (!loader_model || !loader_model->fhttp)
  75. {
  76. FURI_LOG_E(TAG, "Model or fhttp is NULL");
  77. DEV_CRASH();
  78. return;
  79. }
  80. if (current_data_state == DataStateInitial)
  81. {
  82. with_view_model(
  83. view,
  84. DataLoaderModel * model,
  85. {
  86. model->data_state = DataStateRequested;
  87. DataLoaderFetch fetch = model->fetcher;
  88. if (fetch == NULL)
  89. {
  90. FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
  91. model->data_state = DataStateError;
  92. return;
  93. }
  94. // Clear any previous responses
  95. strncpy(model->fhttp->last_response, "", 1);
  96. bool request_status = fetch(model);
  97. if (!request_status)
  98. {
  99. model->data_state = DataStateError;
  100. }
  101. },
  102. true);
  103. }
  104. else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
  105. {
  106. if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
  107. {
  108. if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
  109. {
  110. FURI_LOG_DEV(TAG, "PONG received.");
  111. }
  112. else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9))
  113. {
  114. FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  115. }
  116. else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9))
  117. {
  118. FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  119. }
  120. else if (strlen(loader_model->fhttp->last_response))
  121. {
  122. // Still waiting on response
  123. }
  124. else
  125. {
  126. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
  127. }
  128. }
  129. else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
  130. {
  131. // continue waiting
  132. }
  133. else if (loader_model->fhttp->state == INACTIVE)
  134. {
  135. // inactive. try again
  136. }
  137. else if (loader_model->fhttp->state == ISSUE)
  138. {
  139. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
  140. }
  141. else
  142. {
  143. FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  144. DEV_CRASH();
  145. }
  146. }
  147. else if (current_data_state == DataStateReceived)
  148. {
  149. with_view_model(
  150. view,
  151. DataLoaderModel * model,
  152. {
  153. char *data_text;
  154. if (model->parser == NULL)
  155. {
  156. data_text = NULL;
  157. FURI_LOG_DEV(TAG, "Parser is NULL");
  158. DEV_CRASH();
  159. }
  160. else
  161. {
  162. data_text = model->parser(model);
  163. }
  164. FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
  165. model->data_text = data_text;
  166. if (data_text == NULL)
  167. {
  168. model->data_state = DataStateParseError;
  169. }
  170. else
  171. {
  172. model->data_state = DataStateParsed;
  173. }
  174. },
  175. true);
  176. }
  177. else if (current_data_state == DataStateParsed)
  178. {
  179. with_view_model(
  180. view,
  181. DataLoaderModel * model,
  182. {
  183. if (++model->request_index < model->request_count)
  184. {
  185. model->data_state = DataStateInitial;
  186. }
  187. else
  188. {
  189. loader_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
  190. if (model->data_text != NULL)
  191. {
  192. free(model->data_text);
  193. model->data_text = NULL;
  194. }
  195. view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
  196. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewWidgetResult);
  197. }
  198. },
  199. true);
  200. }
  201. }
  202. bool loader_custom_event_callback(void *context, uint32_t index)
  203. {
  204. if (context == NULL)
  205. {
  206. FURI_LOG_E(TAG, "custom_event_callback - context is NULL");
  207. DEV_CRASH();
  208. return false;
  209. }
  210. switch (index)
  211. {
  212. case FlipWorldCustomEventProcess:
  213. loader_process_callback(context);
  214. return true;
  215. default:
  216. FURI_LOG_DEV(TAG, "custom_event_callback. Unknown index: %ld", index);
  217. return false;
  218. }
  219. }
  220. void loader_draw_callback(Canvas *canvas, void *model)
  221. {
  222. if (!canvas || !model)
  223. {
  224. FURI_LOG_E(TAG, "loader_draw_callback - canvas or model is NULL");
  225. return;
  226. }
  227. DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
  228. HTTPState http_state = data_loader_model->fhttp->state;
  229. DataState data_state = data_loader_model->data_state;
  230. char *title = data_loader_model->title;
  231. canvas_set_font(canvas, FontSecondary);
  232. if (http_state == INACTIVE)
  233. {
  234. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  235. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  236. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  237. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  238. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  239. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  240. return;
  241. }
  242. if (data_state == DataStateError || data_state == DataStateParseError)
  243. {
  244. loader_error_draw(canvas, data_loader_model);
  245. return;
  246. }
  247. canvas_draw_str(canvas, 0, 7, title);
  248. canvas_draw_str(canvas, 0, 17, "Loading...");
  249. if (data_state == DataStateInitial)
  250. {
  251. return;
  252. }
  253. if (http_state == SENDING)
  254. {
  255. canvas_draw_str(canvas, 0, 27, "Fetching...");
  256. return;
  257. }
  258. if (http_state == RECEIVING || data_state == DataStateRequested)
  259. {
  260. canvas_draw_str(canvas, 0, 27, "Receiving...");
  261. return;
  262. }
  263. if (http_state == IDLE && data_state == DataStateReceived)
  264. {
  265. canvas_draw_str(canvas, 0, 27, "Processing...");
  266. return;
  267. }
  268. if (http_state == IDLE && data_state == DataStateParsed)
  269. {
  270. canvas_draw_str(canvas, 0, 27, "Processed...");
  271. return;
  272. }
  273. }
  274. static void loader_timer_callback(void *context)
  275. {
  276. if (context == NULL)
  277. {
  278. FURI_LOG_E(TAG, "loader_timer_callback - context is NULL");
  279. DEV_CRASH();
  280. return;
  281. }
  282. FlipWorldApp *app = (FlipWorldApp *)context;
  283. view_dispatcher_send_custom_event(app->view_dispatcher, FlipWorldCustomEventProcess);
  284. }
  285. static void loader_on_enter(void *context)
  286. {
  287. if (context == NULL)
  288. {
  289. FURI_LOG_E(TAG, "loader_on_enter - context is NULL");
  290. DEV_CRASH();
  291. return;
  292. }
  293. FlipWorldApp *app = (FlipWorldApp *)context;
  294. View *view = app->view_loader;
  295. with_view_model(
  296. view,
  297. DataLoaderModel * model,
  298. {
  299. view_set_previous_callback(view, model->back_callback);
  300. if (model->timer == NULL)
  301. {
  302. model->timer = furi_timer_alloc(loader_timer_callback, FuriTimerTypePeriodic, app);
  303. }
  304. furi_timer_start(model->timer, 250);
  305. },
  306. true);
  307. }
  308. static void loader_on_exit(void *context)
  309. {
  310. if (context == NULL)
  311. {
  312. FURI_LOG_E(TAG, "loader_on_exit - context is NULL");
  313. DEV_CRASH();
  314. return;
  315. }
  316. FlipWorldApp *app = (FlipWorldApp *)context;
  317. View *view = app->view_loader;
  318. with_view_model(
  319. view,
  320. DataLoaderModel * model,
  321. {
  322. if (model->timer)
  323. {
  324. furi_timer_stop(model->timer);
  325. }
  326. },
  327. false);
  328. }
  329. void loader_init(View *view)
  330. {
  331. if (view == NULL)
  332. {
  333. FURI_LOG_E(TAG, "loader_init - view is NULL");
  334. DEV_CRASH();
  335. return;
  336. }
  337. view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
  338. view_set_enter_callback(view, loader_on_enter);
  339. view_set_exit_callback(view, loader_on_exit);
  340. }
  341. void loader_free_model(View *view)
  342. {
  343. if (view == NULL)
  344. {
  345. FURI_LOG_E(TAG, "loader_free_model - view is NULL");
  346. DEV_CRASH();
  347. return;
  348. }
  349. with_view_model(
  350. view,
  351. DataLoaderModel * model,
  352. {
  353. if (model->timer)
  354. {
  355. furi_timer_free(model->timer);
  356. model->timer = NULL;
  357. }
  358. if (model->parser_context)
  359. {
  360. // do not free the context here, it is the app context
  361. // free(model->parser_context);
  362. // model->parser_context = NULL;
  363. }
  364. if (model->fhttp)
  365. {
  366. flipper_http_free(model->fhttp);
  367. model->fhttp = NULL;
  368. }
  369. },
  370. false);
  371. }
  372. void loader_switch_to_view(FlipWorldApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
  373. {
  374. if (app == NULL)
  375. {
  376. FURI_LOG_E(TAG, "loader_switch_to_view - app is NULL");
  377. DEV_CRASH();
  378. return;
  379. }
  380. View *view = app->view_loader;
  381. if (view == NULL)
  382. {
  383. FURI_LOG_E(TAG, "loader_switch_to_view - view is NULL");
  384. DEV_CRASH();
  385. return;
  386. }
  387. with_view_model(
  388. view,
  389. DataLoaderModel * model,
  390. {
  391. model->title = title;
  392. model->fetcher = fetcher;
  393. model->parser = parser;
  394. model->request_index = 0;
  395. model->request_count = request_count;
  396. model->back_callback = back;
  397. model->data_state = DataStateInitial;
  398. model->data_text = NULL;
  399. //
  400. model->parser_context = app;
  401. if (!model->fhttp)
  402. {
  403. model->fhttp = flipper_http_alloc();
  404. }
  405. },
  406. true);
  407. view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
  408. }
  409. void loader_widget_set_text(char *message, Widget **widget)
  410. {
  411. if (widget == NULL)
  412. {
  413. FURI_LOG_E(TAG, "set_widget_text - widget is NULL");
  414. DEV_CRASH();
  415. return;
  416. }
  417. if (message == NULL)
  418. {
  419. FURI_LOG_E(TAG, "set_widget_text - message is NULL");
  420. DEV_CRASH();
  421. return;
  422. }
  423. widget_reset(*widget);
  424. uint32_t message_length = strlen(message); // Length of the message
  425. uint32_t i = 0; // Index tracker
  426. uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
  427. char *formatted_message; // Buffer to hold the final formatted message
  428. // Allocate buffer with double the message length plus one for safety
  429. if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
  430. {
  431. return;
  432. }
  433. while (i < message_length)
  434. {
  435. uint32_t max_line_length = 31; // Maximum characters per line
  436. uint32_t remaining_length = message_length - i; // Remaining characters
  437. uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
  438. // Check for newline character within the current segment
  439. uint32_t newline_pos = i;
  440. bool found_newline = false;
  441. for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
  442. {
  443. if (message[newline_pos] == '\n')
  444. {
  445. found_newline = true;
  446. break;
  447. }
  448. }
  449. if (found_newline)
  450. {
  451. // If newline found, set line_length up to the newline
  452. line_length = newline_pos - i;
  453. }
  454. // Temporary buffer to hold the current line
  455. char line[32];
  456. strncpy(line, message + i, line_length);
  457. line[line_length] = '\0';
  458. // If newline was found, skip it for the next iteration
  459. if (found_newline)
  460. {
  461. i += line_length + 1; // +1 to skip the '\n' character
  462. }
  463. else
  464. {
  465. // Check if the line ends in the middle of a word and adjust accordingly
  466. if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
  467. {
  468. // Find the last space within the current line to avoid breaking a word
  469. char *last_space = strrchr(line, ' ');
  470. if (last_space != NULL)
  471. {
  472. // Adjust the line_length to avoid cutting the word
  473. line_length = last_space - line;
  474. line[line_length] = '\0'; // Null-terminate at the space
  475. }
  476. }
  477. // Move the index forward by the determined line_length
  478. i += line_length;
  479. // Skip any spaces at the beginning of the next line
  480. while (i < message_length && message[i] == ' ')
  481. {
  482. i++;
  483. }
  484. }
  485. // Manually copy the fixed line into the formatted_message buffer
  486. for (uint32_t j = 0; j < line_length; j++)
  487. {
  488. formatted_message[formatted_index++] = line[j];
  489. }
  490. // Add a newline character for line spacing
  491. formatted_message[formatted_index++] = '\n';
  492. }
  493. // Null-terminate the formatted_message
  494. formatted_message[formatted_index] = '\0';
  495. // Add the formatted message to the widget
  496. widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
  497. }