wifi_map.c 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <storage/storage.h>
  4. #define TAG "WIFI_MAP"
  5. #define FILE_NAME "wifi_map_data.csv"
  6. #include <gui/gui.h>
  7. #include <notification/notification.h>
  8. #include <notification/notification_messages.h>
  9. #include <gui/elements.h>
  10. #include <gui/view_dispatcher.h>
  11. #include <gui/modules/dialog_ex.h>
  12. #include <expansion/expansion.h>
  13. #define LINES_ON_SCREEN 6
  14. #define COLUMNS_ON_SCREEN 21
  15. #define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx)
  16. typedef struct UartDumpModel UartDumpModel;
  17. typedef struct {
  18. Gui* gui;
  19. NotificationApp* notification;
  20. ViewDispatcher* view_dispatcher;
  21. View* view;
  22. FuriThread* worker_thread;
  23. FuriStreamBuffer* rx_stream;
  24. File* file;
  25. FuriHalSerialHandle* serial_handle;
  26. } WiFiMapApp;
  27. typedef struct {
  28. FuriString* text;
  29. } ListElement;
  30. struct UartDumpModel {
  31. ListElement* list[LINES_ON_SCREEN];
  32. uint8_t line;
  33. char last_char;
  34. bool escape;
  35. File* file;
  36. };
  37. typedef enum {
  38. WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
  39. WorkerEventStop = (1 << 1),
  40. WorkerEventRx = (1 << 2),
  41. } WorkerEventFlags;
  42. const NotificationSequence sequence_notification = {
  43. &message_display_backlight_on,
  44. &message_green_255,
  45. &message_delay_10,
  46. NULL,
  47. };
  48. File* open_file() {
  49. Storage* storage = furi_record_open(RECORD_STORAGE);
  50. File* file = storage_file_alloc(storage);
  51. if(!storage_file_open(file, APP_DATA_PATH(FILE_NAME), FSAM_WRITE, FSOM_OPEN_APPEND)) {
  52. FURI_LOG_E(TAG, "Failed to open file");
  53. }
  54. return file;
  55. }
  56. int32_t write_to_file(char data_line, File* file) {
  57. char* data = (char*)malloc(sizeof(char) + 1);
  58. data[0] = data_line;
  59. if(!storage_file_write(file, data, (uint16_t)strlen(data))) {
  60. FURI_LOG_E(TAG, "Failed to write to file");
  61. }
  62. free(data);
  63. return 0;
  64. }
  65. int32_t close_file(File* file) {
  66. storage_file_close(file);
  67. storage_file_free(file);
  68. furi_record_close(RECORD_STORAGE);
  69. return 0;
  70. }
  71. static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) {
  72. UartDumpModel* model = _model;
  73. // Prepare canvas
  74. canvas_clear(canvas);
  75. canvas_set_color(canvas, ColorBlack);
  76. canvas_set_font(canvas, FontKeyboard);
  77. for(size_t i = 0; i < LINES_ON_SCREEN; i++) {
  78. canvas_draw_str(
  79. canvas,
  80. 0,
  81. (i + 1) * (canvas_current_font_height(canvas) - 1),
  82. furi_string_get_cstr(model->list[i]->text));
  83. if(i == model->line) {
  84. uint8_t width =
  85. canvas_string_width(canvas, furi_string_get_cstr(model->list[i]->text));
  86. canvas_draw_box(
  87. canvas,
  88. width,
  89. (i) * (canvas_current_font_height(canvas) - 1) + 2,
  90. 2,
  91. canvas_current_font_height(canvas) - 2);
  92. }
  93. }
  94. }
  95. static bool uart_echo_view_input_callback(InputEvent* event, void* context) {
  96. UNUSED(event);
  97. UNUSED(context);
  98. return false;
  99. }
  100. static uint32_t uart_echo_exit(void* context) {
  101. UNUSED(context);
  102. return VIEW_NONE;
  103. }
  104. static void
  105. uart_echo_on_irq_cb(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
  106. furi_assert(context);
  107. WiFiMapApp* app = context;
  108. if(event == FuriHalSerialRxEventData) {
  109. uint8_t data = furi_hal_serial_async_rx(handle);
  110. furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
  111. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx);
  112. }
  113. }
  114. static void uart_echo_push_to_list(UartDumpModel* model, const char data, WiFiMapApp* app) {
  115. if(model->escape) {
  116. // escape code end with letter
  117. if((data >= 'a' && data <= 'z') || (data >= 'A' && data <= 'Z')) {
  118. model->escape = false;
  119. }
  120. } else if(data == '[' && model->last_char == '\e') {
  121. // "Esc[" is a escape code
  122. model->escape = true;
  123. } else if((data >= ' ' && data <= '~') || (data == '\n' || data == '\r')) {
  124. write_to_file((char)data, app->file);
  125. bool new_string_needed = false;
  126. if(furi_string_size(model->list[model->line]->text) >= COLUMNS_ON_SCREEN) {
  127. new_string_needed = true;
  128. } else if((data == '\n' || data == '\r')) {
  129. // pack line breaks
  130. if(model->last_char != '\n' && model->last_char != '\r') {
  131. new_string_needed = true;
  132. }
  133. }
  134. if(new_string_needed) {
  135. if((model->line + 1) < LINES_ON_SCREEN) {
  136. model->line += 1;
  137. } else {
  138. ListElement* first = model->list[0];
  139. for(size_t i = 1; i < LINES_ON_SCREEN; i++) {
  140. model->list[i - 1] = model->list[i];
  141. }
  142. furi_string_reset(first->text);
  143. model->list[model->line] = first;
  144. }
  145. }
  146. if(data != '\n' && data != '\r') {
  147. furi_string_push_back(model->list[model->line]->text, data);
  148. }
  149. }
  150. model->last_char = data;
  151. }
  152. static int32_t uart_echo_worker(void* context) {
  153. furi_assert(context);
  154. WiFiMapApp* app = context;
  155. while(1) {
  156. uint32_t events =
  157. furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever);
  158. furi_check((events & FuriFlagError) == 0);
  159. if(events & WorkerEventStop) break;
  160. if(events & WorkerEventRx) {
  161. size_t length = 0;
  162. do {
  163. uint8_t data[64];
  164. length = furi_stream_buffer_receive(app->rx_stream, data, 64, 0);
  165. if(length > 0) {
  166. furi_hal_serial_tx(app->serial_handle, data, length);
  167. with_view_model(
  168. app->view,
  169. UartDumpModel * model,
  170. {
  171. for(size_t i = 0; i < length; i++) {
  172. uart_echo_push_to_list(model, data[i], app);
  173. }
  174. },
  175. false);
  176. }
  177. } while(length > 0);
  178. notification_message(app->notification, &sequence_notification);
  179. with_view_model(
  180. app->view, UartDumpModel * model, { UNUSED(model); }, true);
  181. }
  182. }
  183. return 0;
  184. }
  185. static WiFiMapApp* uart_echo_app_alloc() {
  186. WiFiMapApp* app = malloc(sizeof(WiFiMapApp));
  187. app->file = open_file();
  188. app->rx_stream = furi_stream_buffer_alloc(2048, 1);
  189. // Gui
  190. app->gui = furi_record_open(RECORD_GUI);
  191. app->notification = furi_record_open(RECORD_NOTIFICATION);
  192. // View dispatcher
  193. app->view_dispatcher = view_dispatcher_alloc();
  194. view_dispatcher_enable_queue(app->view_dispatcher);
  195. view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
  196. // Views
  197. app->view = view_alloc();
  198. view_set_draw_callback(app->view, uart_echo_view_draw_callback);
  199. view_set_input_callback(app->view, uart_echo_view_input_callback);
  200. view_allocate_model(app->view, ViewModelTypeLocking, sizeof(UartDumpModel));
  201. with_view_model(
  202. app->view,
  203. UartDumpModel * model,
  204. {
  205. for(size_t i = 0; i < LINES_ON_SCREEN; i++) {
  206. model->line = 0;
  207. model->escape = false;
  208. model->list[i] = malloc(sizeof(ListElement));
  209. model->list[i]->text = furi_string_alloc();
  210. }
  211. },
  212. true);
  213. view_set_previous_callback(app->view, uart_echo_exit);
  214. view_dispatcher_add_view(app->view_dispatcher, 0, app->view);
  215. view_dispatcher_switch_to_view(app->view_dispatcher, 0);
  216. app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app);
  217. furi_thread_start(app->worker_thread);
  218. // Enable uart listener
  219. app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
  220. furi_check(app->serial_handle);
  221. furi_hal_serial_init(app->serial_handle, 115200);
  222. furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, false);
  223. return app;
  224. }
  225. static void uart_echo_app_free(WiFiMapApp* app) {
  226. furi_assert(app);
  227. furi_hal_serial_async_rx_stop(app->serial_handle);
  228. furi_hal_serial_deinit(app->serial_handle);
  229. furi_hal_serial_control_release(app->serial_handle);
  230. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop);
  231. furi_thread_join(app->worker_thread);
  232. furi_thread_free(app->worker_thread);
  233. // Free views
  234. view_dispatcher_remove_view(app->view_dispatcher, 0);
  235. with_view_model(
  236. app->view,
  237. UartDumpModel * model,
  238. {
  239. for(size_t i = 0; i < LINES_ON_SCREEN; i++) {
  240. furi_string_free(model->list[i]->text);
  241. free(model->list[i]);
  242. }
  243. },
  244. true);
  245. view_free(app->view);
  246. view_dispatcher_free(app->view_dispatcher);
  247. // Close gui record
  248. furi_record_close(RECORD_GUI);
  249. furi_record_close(RECORD_NOTIFICATION);
  250. app->gui = NULL;
  251. furi_stream_buffer_free(app->rx_stream);
  252. close_file(app->file);
  253. // Free rest
  254. free(app);
  255. }
  256. int32_t wifi_map_app(void* p) {
  257. UNUSED(p);
  258. // Disable expansion protocol to avoid interference with UART Handle
  259. Expansion* expansion = furi_record_open(RECORD_EXPANSION);
  260. expansion_disable(expansion);
  261. FURI_LOG_D(TAG, "wifi_map_app");
  262. WiFiMapApp* app = uart_echo_app_alloc();
  263. view_dispatcher_run(app->view_dispatcher);
  264. uart_echo_app_free(app);
  265. // Return previous state of expansion
  266. expansion_enable(expansion);
  267. furi_record_close(RECORD_EXPANSION);
  268. return 0;
  269. }