gb_cartridge_scene_5.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. #include "../gb_cartridge_app.h"
  2. #include <furi.h>
  3. #include <furi_hal.h>
  4. #include <furi_hal_uart.h>
  5. #include <stm32wbxx_ll_lpuart.h>
  6. #include <stm32wbxx_ll_usart.h>
  7. #include <input/input.h>
  8. #include <gui/elements.h>
  9. #include <dolphin/dolphin.h>
  10. #include <gui/elements.h>
  11. #include <notification/notification_messages.h>
  12. #include <dialogs/dialogs.h>
  13. #include <gui/modules/dialog_ex.h>
  14. #include <toolbox/stream/file_stream.h>
  15. #include "../helpers/gb_cartridge_speaker.h"
  16. #include "../helpers/sequential_file.h"
  17. #include <stdio.h> // Para sprintf
  18. #include <string.h> // Para strlen
  19. static uint64_t last_toggle_time = 0;
  20. struct GBCartridgeScene5 {
  21. View* view;
  22. GBCartridgeScene5Callback callback;
  23. void* context;
  24. GBCartridge* app;
  25. };
  26. typedef struct {
  27. char* event_type;
  28. int progress;
  29. int total_ram;
  30. int transfered;
  31. int ramBanks;
  32. int elapsed_time;
  33. int start_time;
  34. char* cart_dump_ram_filename_sequential;
  35. bool rx_active;
  36. char* event_title;
  37. uint32_t offset;
  38. uint32_t value;
  39. File* selectedfile;
  40. } GameBoyCartridgeRAMWriteModel;
  41. static bool select_ram_file(GBCartridge* app, File* file) {
  42. bool result = false;
  43. FuriString* file_path = furi_string_alloc();
  44. furi_string_set(file_path, MALVEKE_APP_FOLDER_RAMS);
  45. DialogsFileBrowserOptions browser_options;
  46. dialog_file_browser_set_basic_options(&browser_options, "sav", NULL);
  47. browser_options.base_path = MALVEKE_APP_FOLDER_RAMS;
  48. browser_options.skip_assets = true;
  49. // Input events and views are managed by file_browser
  50. bool res = dialog_file_browser_show(app->dialogs, file_path, file_path, &browser_options);
  51. // UNUSED(res);
  52. // FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path));
  53. if(res) {
  54. if(!storage_file_open(file, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING))
  55. // if (!file_stream_open(stream, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING))
  56. {
  57. // FURI_LOG_D(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(file_path));
  58. // file_stream_close(stream);
  59. } else {
  60. // FURI_LOG_D(TAG, "Open file \"%s\"", furi_string_get_cstr(file_path));
  61. result = true;
  62. }
  63. }
  64. furi_string_free(file_path);
  65. return result;
  66. }
  67. static int32_t cartridge_writting_worker_thread(void* thread_context) {
  68. GBCartridge* app = thread_context;
  69. UNUSED(app);
  70. File* file = storage_file_alloc(app->storage);
  71. if(select_ram_file(app, file)) {
  72. uint16_t fileSize = storage_file_size(file);
  73. FURI_LOG_I(TAG, "fileSize: %d ", fileSize);
  74. with_view_model(
  75. app->gb_cartridge_scene_5->view,
  76. GameBoyCartridgeRAMWriteModel * model,
  77. {
  78. model->total_ram = fileSize;
  79. model->selectedfile = file;
  80. },
  81. true);
  82. char gbcartridge_start_command[80]; // A reasonably sized buffer.
  83. snprintf(
  84. gbcartridge_start_command,
  85. sizeof(gbcartridge_start_command),
  86. "gbcartridge -w -a %d\n",
  87. fileSize);
  88. uart_tx((uint8_t*)gbcartridge_start_command, strlen(gbcartridge_start_command));
  89. furi_delay_ms(500); // wait
  90. uint8_t* the_savefile = NULL;
  91. size_t savefile_size = 0;
  92. with_view_model(
  93. app->gb_cartridge_scene_5->view,
  94. GameBoyCartridgeRAMWriteModel * model,
  95. {
  96. model->event_title = "Transferring...";
  97. model->transfered = 0;
  98. model->start_time = furi_hal_rtc_get_timestamp(); // Registra el tiempo de inicio
  99. },
  100. true);
  101. the_savefile = malloc(fileSize); // to be freed by caller
  102. uint8_t* buf_ptr = the_savefile;
  103. size_t read = 0;
  104. while(read < fileSize) {
  105. size_t to_read = fileSize - read;
  106. if(to_read > UINT16_MAX) to_read = UINT16_MAX;
  107. uint16_t now_read = storage_file_read(file, buf_ptr, (uint16_t)to_read);
  108. read += now_read;
  109. buf_ptr += now_read;
  110. }
  111. savefile_size = read;
  112. uart_tx((uint8_t*)the_savefile, savefile_size);
  113. uart_tx((uint8_t*)("\n"), 1);
  114. with_view_model(
  115. app->gb_cartridge_scene_5->view,
  116. GameBoyCartridgeRAMWriteModel * model,
  117. { model->event_title = "Writing Cartridge..."; },
  118. true);
  119. free(the_savefile);
  120. if(file && storage_file_is_open(file)) {
  121. storage_file_close(file);
  122. }
  123. }
  124. return 0;
  125. }
  126. void gb_cartridge_scene_5_set_callback(
  127. GBCartridgeScene5* instance,
  128. GBCartridgeScene5Callback callback,
  129. void* context) {
  130. furi_assert(instance);
  131. furi_assert(callback);
  132. instance->callback = callback;
  133. instance->context = context;
  134. }
  135. static void drawProgressBar(Canvas* canvas, int progress) {
  136. for(int x = 0; x < 64 - 14 - UI_PADDING - UI_PADDING - UI_PADDING - UI_PADDING; x += 5) {
  137. for(int row = 0; row < 20; row += 5) {
  138. if(progress > 0) {
  139. canvas_draw_box(
  140. canvas, 14 /*ARROW*/ + UI_PADDING + 2 + x + 4, /*45*/ 26 + row, 4, 4);
  141. progress--;
  142. } else {
  143. canvas_draw_frame(
  144. canvas, 14 /*ARROW*/ + UI_PADDING + 2 + x + 4, /*45*/ 26 + row, 4, 4);
  145. }
  146. }
  147. }
  148. }
  149. void gb_cartridge_scene_5_draw(Canvas* canvas, GameBoyCartridgeRAMWriteModel* model) {
  150. // Clear the screen.
  151. canvas_set_color(canvas, ColorBlack);
  152. canvas_clear(canvas);
  153. canvas_set_color(canvas, ColorBlack);
  154. canvas_set_font(canvas, FontKeyboard);
  155. canvas_draw_frame(canvas, 0, 24, (128 / 2), 25);
  156. canvas_set_bitmap_mode(canvas, 1);
  157. canvas_set_font(canvas, FontPrimary);
  158. char progressText[42];
  159. int progress = 0;
  160. if(model->total_ram > 0 && model->transfered > 0) {
  161. progress = model->transfered * 100 / model->total_ram;
  162. }
  163. snprintf(progressText, sizeof(progressText), "%d%% Write RAM...", progress);
  164. canvas_draw_str_aligned(canvas, 128 / 2, 0, AlignCenter, AlignTop, progressText);
  165. canvas_set_font(canvas, FontSecondary);
  166. canvas_draw_str_aligned(canvas, 128 / 2, 12, AlignCenter, AlignTop, model->event_title);
  167. char total_ram_str[20];
  168. snprintf(
  169. total_ram_str,
  170. sizeof(total_ram_str),
  171. "of %.2lf MiB",
  172. (double)(model->total_ram / 1024.0 / 1024.0));
  173. char transfered_ram_str[20];
  174. snprintf(
  175. transfered_ram_str,
  176. sizeof(transfered_ram_str),
  177. "%.2lf MiB",
  178. (double)(model->transfered / 1024.0 / 1024.0));
  179. // Calcula la Tasa de Transferencia en KiB/s
  180. char transfer_rate_str[20];
  181. if(model->transfered > 0 && model->elapsed_time > 0) {
  182. double transfer_rate_kibps =
  183. (double)model->transfered / ((double)model->elapsed_time) / (double)1024.0;
  184. snprintf(transfer_rate_str, sizeof(transfer_rate_str), "%.2lf KiB/s", transfer_rate_kibps);
  185. } else {
  186. snprintf(transfer_rate_str, sizeof(transfer_rate_str), "0 KiB/s");
  187. }
  188. canvas_draw_str_aligned(
  189. canvas, (128 / 2) + UI_PADDING, 22 + 2, AlignLeft, AlignTop, transfered_ram_str);
  190. canvas_draw_str_aligned(
  191. canvas, (128 / 2) + UI_PADDING, 30 + 2, AlignLeft, AlignTop, total_ram_str);
  192. canvas_draw_str_aligned(
  193. canvas, (128 / 2) + UI_PADDING, 38 + 2, AlignLeft, AlignTop, transfer_rate_str);
  194. if(model->rx_active) {
  195. canvas_draw_icon_ex(canvas, UI_PADDING, 28, &I_ArrowUpFilled_14x15, IconRotation180);
  196. } else {
  197. canvas_draw_icon_ex(canvas, UI_PADDING, 28, &I_ArrowUpEmpty_14x15, IconRotation180);
  198. }
  199. char totalText[42];
  200. snprintf(totalText, sizeof(totalText), "%d", model->total_ram);
  201. drawProgressBar(
  202. canvas,
  203. (progress * UI_PROGRESS_ROWS * UI_PROGRESS_COLS) /
  204. 100); // Pinta las primeras 10 cajas de negro
  205. elements_button_center(canvas, "Write");
  206. }
  207. static void gb_cartridge_scene_5_model_init(GameBoyCartridgeRAMWriteModel* const model) {
  208. model->progress = 0;
  209. model->total_ram = 0;
  210. model->transfered = 0;
  211. model->ramBanks = 0;
  212. model->elapsed_time = 0;
  213. model->start_time = 0;
  214. model->event_title = "...";
  215. }
  216. void gameboy_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) {
  217. furi_assert(context);
  218. UNUSED(len);
  219. UNUSED(buf);
  220. GBCartridge* instance = context;
  221. with_view_model(
  222. instance->gb_cartridge_scene_5->view,
  223. GameBoyCartridgeRAMWriteModel * model,
  224. {
  225. UNUSED(model);
  226. uint64_t current_time = furi_hal_rtc_get_timestamp();
  227. model->elapsed_time = current_time - model->start_time;
  228. if(current_time - last_toggle_time >= 0.2) {
  229. model->rx_active = !model->rx_active;
  230. last_toggle_time = current_time;
  231. }
  232. cJSON* json = cJSON_Parse((char*)buf);
  233. if(json == NULL) {
  234. } else {
  235. cJSON* type = cJSON_GetObjectItemCaseSensitive(json, "type");
  236. if(cJSON_IsString(type) && (type->valuestring != NULL)) {
  237. model->event_type = strdup(type->valuestring);
  238. } else {
  239. model->event_type = "None";
  240. }
  241. // offset
  242. cJSON* offset = cJSON_GetObjectItemCaseSensitive(json, "offset");
  243. if(cJSON_IsNumber(offset)) {
  244. model->offset = offset->valueint;
  245. } else {
  246. model->offset = 0;
  247. }
  248. // value
  249. cJSON* value = cJSON_GetObjectItemCaseSensitive(json, "value");
  250. if(cJSON_IsNumber(value)) {
  251. model->value = value->valueint;
  252. } else {
  253. model->value = 0;
  254. }
  255. }
  256. if(strcmp(model->event_type, "progress") == 0) {
  257. // progress
  258. cJSON* progress = cJSON_GetObjectItemCaseSensitive(json, "progress");
  259. if(cJSON_IsNumber(progress)) {
  260. model->transfered += progress->valueint;
  261. }
  262. }
  263. if(strcmp(model->event_type, "success") == 0) {
  264. notification_success(instance->notification);
  265. model->transfered = model->total_ram;
  266. model->event_title = "Done!";
  267. }
  268. },
  269. true);
  270. }
  271. bool gb_cartridge_scene_5_input(InputEvent* event, void* context) {
  272. furi_assert(context);
  273. GBCartridgeScene5* instance = context;
  274. if(event->type == InputTypeRelease) {
  275. switch(event->key) {
  276. case InputKeyBack:
  277. with_view_model(
  278. instance->view,
  279. GameBoyCartridgeRAMWriteModel * model,
  280. {
  281. UNUSED(model);
  282. GBCartridge* app = (GBCartridge*)instance->context;
  283. // Unregister rx callback
  284. uart_set_handle_rx_data_cb(app->uart, NULL);
  285. uart_set_handle_rx_data_cb(app->lp_uart, NULL);
  286. instance->callback(GBCartridgeCustomEventScene5Back, instance->context);
  287. },
  288. true);
  289. break;
  290. case InputKeyUp:
  291. case InputKeyDown:
  292. case InputKeyLeft:
  293. case InputKeyRight:
  294. break;
  295. case InputKeyOk: {
  296. GBCartridge* app = ((GBCartridge*)instance->context);
  297. uart_set_handle_rx_data_cb(app->uart, gameboy_handle_rx_data_cb);
  298. cartridge_writting_worker_thread(app);
  299. } break;
  300. case InputKeyMAX:
  301. break;
  302. }
  303. }
  304. return true;
  305. }
  306. void gb_cartridge_scene_5_exit(void* context) {
  307. furi_assert(context);
  308. GBCartridge* app = context;
  309. gb_cartridge_stop_all_sound(app);
  310. }
  311. void gb_cartridge_scene_5_enter(void* context) {
  312. furi_assert(context);
  313. GBCartridgeScene5* instance = context;
  314. GBCartridge* app = (GBCartridge*)instance->context;
  315. UNUSED(app);
  316. with_view_model(
  317. app->gb_cartridge_scene_5->view,
  318. GameBoyCartridgeRAMWriteModel * model,
  319. {
  320. UNUSED(model);
  321. gb_cartridge_scene_5_model_init(model);
  322. },
  323. false);
  324. }
  325. GBCartridgeScene5* gb_cartridge_scene_5_alloc() {
  326. GBCartridgeScene5* instance = malloc(sizeof(GBCartridgeScene5));
  327. instance->view = view_alloc();
  328. view_allocate_model(
  329. instance->view, ViewModelTypeLocking, sizeof(GameBoyCartridgeRAMWriteModel));
  330. view_set_context(instance->view, instance);
  331. view_set_draw_callback(instance->view, (ViewDrawCallback)gb_cartridge_scene_5_draw);
  332. view_set_input_callback(instance->view, gb_cartridge_scene_5_input);
  333. view_set_enter_callback(instance->view, gb_cartridge_scene_5_enter);
  334. view_set_exit_callback(instance->view, gb_cartridge_scene_5_exit);
  335. with_view_model(
  336. instance->view,
  337. GameBoyCartridgeRAMWriteModel * model,
  338. { gb_cartridge_scene_5_model_init(model); },
  339. true);
  340. return instance;
  341. }
  342. void gb_cartridge_scene_5_free(GBCartridgeScene5* instance) {
  343. GBCartridge* app = instance->context;
  344. UNUSED(app);
  345. furi_assert(instance);
  346. view_free(instance->view);
  347. free(instance);
  348. }
  349. View* gb_cartridge_scene_5_get_view(GBCartridgeScene5* instance) {
  350. furi_assert(instance);
  351. return instance->view;
  352. }