flipper_http.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. // flipper_http.h
  2. #ifndef FLIPPER_HTTP_H
  3. #define FLIPPER_HTTP_H
  4. #include <furi.h>
  5. #include <furi_hal.h>
  6. #include <furi_hal_gpio.h>
  7. #include <furi_hal_serial.h>
  8. #include <storage/storage.h>
  9. // STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
  10. #define HTTP_TAG "WebCrawler"
  11. #define http_tag "web_crawler_app"
  12. #define UART_CH (FuriHalSerialIdUsart)
  13. #define TIMEOUT_DURATION_TICKS (2 * 1000) // 2 seconds
  14. #define BAUDRATE (115200)
  15. #define RX_BUF_SIZE 1024
  16. // UART RX Handler Callback declaration
  17. void flipper_http_rx_callback(const char *line, void *context);
  18. // Function to save received data to a file
  19. bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]);
  20. static bool flipper_http_connect_wifi();
  21. // Define GPIO pins for UART
  22. GpioPin test_pins[2] = {
  23. {.port = GPIOA, .pin = LL_GPIO_PIN_7}, // USART1_RX
  24. {.port = GPIOA, .pin = LL_GPIO_PIN_6} // USART1_TX
  25. };
  26. // State variable to track if serial is receiving, sending, or idle
  27. typedef enum
  28. {
  29. INACTIVE, // Inactive state
  30. IDLE, // Default state
  31. RECEIVING, // Receiving data
  32. SENDING, // Sending data
  33. ISSUE, // Issue with connection
  34. } SerialState;
  35. // Event Flags for UART Worker Thread
  36. typedef enum
  37. {
  38. WorkerEvtStop = (1 << 0),
  39. WorkerEvtRxDone = (1 << 1),
  40. } WorkerEvtFlags;
  41. // Forward declaration for callback
  42. typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
  43. // FlipperHTTP Structure
  44. typedef struct
  45. {
  46. FuriStreamBuffer *flipper_http_stream; // Stream buffer for UART communication
  47. FuriHalSerialHandle *serial_handle; // Serial handle for UART communication
  48. FuriThread *rx_thread;
  49. uint8_t rx_buf[RX_BUF_SIZE];
  50. FuriThreadId rx_thread_id;
  51. FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
  52. void *callback_context; // Context for the callback
  53. SerialState state;
  54. // variable to store the last received data from the UART
  55. char *last_response;
  56. // Timer-related members
  57. FuriTimer *get_timeout_timer; // Timer for GET request timeout
  58. bool started_receiving; // Indicates if a GET request has started
  59. bool just_started; // Indicates if data reception has just started
  60. char *received_data; // Buffer to store received data
  61. } FlipperHTTP;
  62. // Declare uart as extern to prevent multiple definitions
  63. FlipperHTTP fhttp;
  64. // Timer callback function
  65. void get_timeout_timer_callback(void *context)
  66. {
  67. UNUSED(context);
  68. FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving [GET/END]...");
  69. // Reset the state
  70. fhttp.started_receiving = false;
  71. fhttp.just_started = false;
  72. // Free received data if any
  73. if (fhttp.received_data)
  74. {
  75. free(fhttp.received_data);
  76. fhttp.received_data = NULL;
  77. }
  78. // Update UART state
  79. fhttp.state = ISSUE;
  80. }
  81. // UART RX Handler Callback (Interrupt Context)
  82. static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerialRxEvent event, void *context)
  83. {
  84. UNUSED(context);
  85. if (event == FuriHalSerialRxEventData)
  86. {
  87. uint8_t data = furi_hal_serial_async_rx(handle);
  88. furi_stream_buffer_send(fhttp.flipper_http_stream, &data, 1, 0);
  89. furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtRxDone);
  90. }
  91. }
  92. // UART worker thread
  93. static int32_t flipper_http_worker(void *context)
  94. {
  95. UNUSED(context);
  96. size_t rx_line_pos = 0;
  97. char rx_line_buffer[256]; // Buffer to collect a line
  98. while (1)
  99. {
  100. uint32_t events = furi_thread_flags_wait(WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
  101. if (events & WorkerEvtStop)
  102. break;
  103. if (events & WorkerEvtRxDone)
  104. {
  105. size_t len = furi_stream_buffer_receive(fhttp.flipper_http_stream, fhttp.rx_buf, RX_BUF_SIZE * 10, 0);
  106. for (size_t i = 0; i < len; i++)
  107. {
  108. char c = fhttp.rx_buf[i];
  109. if (c == '\n' || rx_line_pos >= sizeof(rx_line_buffer) - 1)
  110. {
  111. rx_line_buffer[rx_line_pos] = '\0';
  112. // Invoke the callback with the complete line
  113. if (fhttp.handle_rx_line_cb)
  114. {
  115. fhttp.handle_rx_line_cb(rx_line_buffer, fhttp.callback_context);
  116. }
  117. // Reset the line buffer
  118. rx_line_pos = 0;
  119. }
  120. else
  121. {
  122. rx_line_buffer[rx_line_pos++] = c;
  123. }
  124. }
  125. }
  126. }
  127. return 0;
  128. }
  129. // UART initialization function
  130. bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
  131. {
  132. fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
  133. if (!fhttp.flipper_http_stream)
  134. {
  135. FURI_LOG_E(HTTP_TAG, "Failed to allocate UART stream buffer.");
  136. return false;
  137. }
  138. fhttp.rx_thread = furi_thread_alloc();
  139. if (!fhttp.rx_thread)
  140. {
  141. FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread.");
  142. furi_stream_buffer_free(fhttp.flipper_http_stream);
  143. return false;
  144. }
  145. furi_thread_set_name(fhttp.rx_thread, "FlipperHTTP_RxThread");
  146. furi_thread_set_stack_size(fhttp.rx_thread, 1024);
  147. furi_thread_set_context(fhttp.rx_thread, &fhttp);
  148. furi_thread_set_callback(fhttp.rx_thread, flipper_http_worker);
  149. fhttp.handle_rx_line_cb = callback;
  150. fhttp.callback_context = context;
  151. furi_thread_start(fhttp.rx_thread);
  152. fhttp.rx_thread_id = furi_thread_get_id(fhttp.rx_thread);
  153. // Initialize GPIO pins for UART
  154. furi_hal_gpio_init_simple(&test_pins[0], GpioModeInput);
  155. furi_hal_gpio_init_simple(&test_pins[1], GpioModeOutputPushPull);
  156. fhttp.serial_handle = NULL;
  157. fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH);
  158. if (fhttp.serial_handle == NULL)
  159. {
  160. FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL");
  161. // Cleanup resources
  162. furi_thread_free(fhttp.rx_thread);
  163. furi_stream_buffer_free(fhttp.flipper_http_stream);
  164. return false;
  165. }
  166. // Initialize UART with acquired handle
  167. furi_hal_serial_init(fhttp.serial_handle, BAUDRATE);
  168. // Enable RX direction
  169. furi_hal_serial_enable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
  170. // Start asynchronous RX with the callback
  171. furi_hal_serial_async_rx_start(fhttp.serial_handle, _flipper_http_rx_callback, &fhttp, false);
  172. // Wait for the TX to complete to ensure UART is ready
  173. furi_hal_serial_tx_wait_complete(fhttp.serial_handle);
  174. // Allocate the timer for handling timeouts
  175. fhttp.get_timeout_timer = furi_timer_alloc(
  176. get_timeout_timer_callback, // Callback function
  177. FuriTimerTypeOnce, // One-shot timer
  178. &fhttp // Context passed to callback
  179. );
  180. if (!fhttp.get_timeout_timer)
  181. {
  182. FURI_LOG_E(HTTP_TAG, "Failed to allocate GET timeout timer.");
  183. // Cleanup resources
  184. furi_hal_serial_async_rx_stop(fhttp.serial_handle);
  185. furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
  186. furi_hal_serial_control_release(fhttp.serial_handle);
  187. furi_hal_serial_deinit(fhttp.serial_handle);
  188. furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop);
  189. furi_thread_join(fhttp.rx_thread);
  190. furi_thread_free(fhttp.rx_thread);
  191. furi_stream_buffer_free(fhttp.flipper_http_stream);
  192. return false;
  193. }
  194. // Set the timer thread priority if needed
  195. furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
  196. FURI_LOG_I(HTTP_TAG, "UART initialized successfully.");
  197. return true;
  198. }
  199. // Deinitialize UART
  200. void flipper_http_deinit()
  201. {
  202. if (fhttp.serial_handle == NULL)
  203. {
  204. FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?");
  205. return;
  206. }
  207. // Stop asynchronous RX
  208. furi_hal_serial_async_rx_stop(fhttp.serial_handle);
  209. // Release and deinitialize the serial handle
  210. furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
  211. furi_hal_serial_control_release(fhttp.serial_handle);
  212. furi_hal_serial_deinit(fhttp.serial_handle);
  213. // Signal the worker thread to stop
  214. furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop);
  215. // Wait for the thread to finish
  216. furi_thread_join(fhttp.rx_thread);
  217. // Free the thread resources
  218. furi_thread_free(fhttp.rx_thread);
  219. // Free the stream buffer
  220. furi_stream_buffer_free(fhttp.flipper_http_stream);
  221. // Free the timer
  222. if (fhttp.get_timeout_timer)
  223. {
  224. furi_timer_free(fhttp.get_timeout_timer);
  225. fhttp.get_timeout_timer = NULL;
  226. }
  227. // Free received data if any
  228. if (fhttp.received_data)
  229. {
  230. free(fhttp.received_data);
  231. fhttp.received_data = NULL;
  232. }
  233. FURI_LOG_I("FlipperHTTP", "UART deinitialized successfully.");
  234. }
  235. // Function to send data over UART with newline termination
  236. bool flipper_http_send_data(const char *data)
  237. {
  238. size_t data_length = strlen(data);
  239. if (data_length == 0)
  240. {
  241. FURI_LOG_E("FlipperHTTP", "Attempted to send empty data.");
  242. return false;
  243. }
  244. // Create a buffer with data + '\n'
  245. size_t send_length = data_length + 1; // +1 for '\n'
  246. if (send_length > 256)
  247. { // Ensure buffer size is sufficient
  248. FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP.");
  249. return false;
  250. }
  251. char send_buffer[257]; // 256 + 1 for safety
  252. strncpy(send_buffer, data, 256);
  253. send_buffer[data_length] = '\n'; // Append newline
  254. send_buffer[data_length + 1] = '\0'; // Null-terminate
  255. if (fhttp.state == INACTIVE && ((strstr(send_buffer, "[PING]") == NULL) && (strstr(send_buffer, "[WIFI/CONNECT]") == NULL)))
  256. {
  257. FURI_LOG_E("FlipperHTTP", "Cannot send data while INACTIVE.");
  258. fhttp.last_response = "Cannot send data while INACTIVE.";
  259. return false;
  260. }
  261. fhttp.state = SENDING;
  262. furi_hal_serial_tx(fhttp.serial_handle, (const uint8_t *)send_buffer, send_length);
  263. FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
  264. fhttp.state = IDLE;
  265. return true;
  266. }
  267. bool flipper_http_ping()
  268. {
  269. const char *command = "[PING]";
  270. if (!flipper_http_send_data(command))
  271. {
  272. FURI_LOG_E("FlipperHTTP", "Failed to send PING command.");
  273. return false;
  274. }
  275. // set state as INACTIVE to be made IDLE if PONG is received
  276. fhttp.state = INACTIVE;
  277. // The response will be handled asynchronously via the callback
  278. return true;
  279. }
  280. // Function to save WiFi settings (returns true if successful)
  281. bool flipper_http_save_wifi(const char *ssid, const char *password)
  282. {
  283. char buffer[256];
  284. int ret = snprintf(buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password);
  285. if (ret < 0 || ret >= (int)sizeof(buffer))
  286. {
  287. FURI_LOG_E("FlipperHTTP", "Failed to format WiFi save command.");
  288. return false;
  289. }
  290. if (!flipper_http_send_data(buffer))
  291. {
  292. FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command.");
  293. return false;
  294. }
  295. // The response will be handled asynchronously via the callback
  296. return true;
  297. }
  298. // Function to disconnect from WiFi (returns true if successful)
  299. bool flipper_http_disconnect_wifi()
  300. {
  301. const char *command = "[WIFI/DISCONNECT]";
  302. if (!flipper_http_send_data(command))
  303. {
  304. FURI_LOG_E("FlipperHTTP", "Failed to send WiFi disconnect command.");
  305. return false;
  306. }
  307. // The response will be handled asynchronously via the callback
  308. return true;
  309. }
  310. // Function to connect to WiFi (returns true if successful)
  311. static bool flipper_http_connect_wifi()
  312. {
  313. const char *command = "[WIFI/CONNECT]";
  314. if (!flipper_http_send_data(command))
  315. {
  316. FURI_LOG_E("FlipperHTTP", "Failed to send WiFi connect command.");
  317. return false;
  318. }
  319. // The response will be handled asynchronously via the callback
  320. return true;
  321. }
  322. // Function to send a GET request
  323. /**
  324. * @brief Send a GET request to the specified URL.
  325. * @return true if the request was successful, false otherwise.
  326. * @param url The URL to send the GET request to.
  327. * @note The received data will be handled asynchronously via the callback.
  328. */
  329. bool flipper_http_get_request(const char *url)
  330. {
  331. if (!url)
  332. {
  333. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request.");
  334. return false;
  335. }
  336. // Prepare GET request command
  337. char command[256];
  338. int ret = snprintf(command, sizeof(command), "[GET]%s", url);
  339. if (ret < 0 || ret >= (int)sizeof(command))
  340. {
  341. FURI_LOG_E("FlipperHTTP", "Failed to format GET request command.");
  342. return false;
  343. }
  344. // Send GET request via UART
  345. if (!flipper_http_send_data(command))
  346. {
  347. FURI_LOG_E("FlipperHTTP", "Failed to send GET request command.");
  348. return false;
  349. }
  350. // The response will be handled asynchronously via the callback
  351. return true;
  352. }
  353. // UART RX Handler Callback
  354. void flipper_http_rx_callback(const char *line, void *context)
  355. {
  356. if (!line || !context)
  357. {
  358. FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to flipper_http_rx_callback.");
  359. return;
  360. }
  361. fhttp.last_response = (char *)line;
  362. // the only way for the state to change from INACTIVE to RECEIVING is if a PONG is received
  363. if (fhttp.state != INACTIVE)
  364. {
  365. fhttp.state = RECEIVING;
  366. }
  367. // Process the received line
  368. FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
  369. // Check if we've started receiving data from a GET request
  370. if (fhttp.started_receiving)
  371. {
  372. // Restart the timeout timer each time new data is received
  373. furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  374. if (strstr(line, "[GET/END]") != NULL)
  375. {
  376. FURI_LOG_I(HTTP_TAG, "GET request completed.");
  377. // Stop the timer since we've completed the GET request
  378. furi_timer_stop(fhttp.get_timeout_timer);
  379. if (fhttp.received_data)
  380. {
  381. flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
  382. free(fhttp.received_data);
  383. fhttp.received_data = NULL;
  384. }
  385. else
  386. {
  387. FURI_LOG_E(HTTP_TAG, "No data received.");
  388. }
  389. fhttp.started_receiving = false;
  390. fhttp.just_started = false;
  391. fhttp.state = IDLE;
  392. return;
  393. }
  394. // Append the new line to the existing data
  395. if (fhttp.received_data == NULL)
  396. {
  397. fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
  398. if (fhttp.received_data)
  399. {
  400. strcpy(fhttp.received_data, line);
  401. strcat(fhttp.received_data, "\n");
  402. }
  403. }
  404. else
  405. {
  406. size_t new_size = strlen(fhttp.received_data) + strlen(line) + 2; // +2 for newline and null terminator
  407. fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
  408. if (fhttp.received_data)
  409. {
  410. strcat(fhttp.received_data, line);
  411. strcat(fhttp.received_data, "\n");
  412. }
  413. }
  414. if (!fhttp.just_started)
  415. {
  416. fhttp.just_started = true;
  417. }
  418. return;
  419. }
  420. // Handle different types of responses
  421. if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL)
  422. {
  423. FURI_LOG_I(HTTP_TAG, "Operation succeeded.");
  424. }
  425. else if (strstr(line, "[INFO]") != NULL)
  426. {
  427. FURI_LOG_I(HTTP_TAG, "Received info: %s", line);
  428. if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
  429. {
  430. fhttp.state = IDLE;
  431. }
  432. }
  433. else if (strstr(line, "[GET/SUCCESS]") != NULL)
  434. {
  435. FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
  436. fhttp.started_receiving = true;
  437. furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  438. }
  439. else if (strstr(line, "[DISCONNECTED]") != NULL)
  440. {
  441. FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");
  442. }
  443. else if (strstr(line, "[ERROR]") != NULL)
  444. {
  445. FURI_LOG_E(HTTP_TAG, "Received error: %s", line);
  446. fhttp.state = ISSUE;
  447. return;
  448. }
  449. else if (strstr(line, "[PONG]") != NULL)
  450. {
  451. FURI_LOG_I(HTTP_TAG, "Received PONG response: Wifi Dev Board is still alive.");
  452. // send command to connect to WiFi
  453. if (fhttp.state == INACTIVE)
  454. {
  455. fhttp.state = IDLE;
  456. return;
  457. }
  458. }
  459. if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL)
  460. {
  461. fhttp.state = IDLE;
  462. }
  463. else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL)
  464. {
  465. fhttp.state = INACTIVE;
  466. }
  467. else
  468. {
  469. fhttp.state = IDLE;
  470. }
  471. }
  472. bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[])
  473. {
  474. const char *output_file_path = STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt";
  475. // Ensure the directory exists
  476. char directory_path[128];
  477. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag);
  478. Storage *_storage = NULL;
  479. File *_file = NULL;
  480. // Open the storage if not opened already
  481. // Initialize storage and create the directory if it doesn't exist
  482. _storage = furi_record_open(RECORD_STORAGE);
  483. storage_common_mkdir(_storage, directory_path); // Create directory if it doesn't exist
  484. _file = storage_file_alloc(_storage);
  485. // Open file for writing and append data line by line
  486. if (!storage_file_open(_file, output_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  487. {
  488. FURI_LOG_E(HTTP_TAG, "Failed to open output file for writing.");
  489. storage_file_free(_file);
  490. furi_record_close(RECORD_STORAGE);
  491. return false;
  492. }
  493. // Write each line received from the UART to the file
  494. if (bytes_received > 0 && _file)
  495. {
  496. storage_file_write(_file, line_buffer, bytes_received);
  497. storage_file_write(_file, "\n", 1); // Add a newline after each line
  498. }
  499. else
  500. {
  501. FURI_LOG_E(HTTP_TAG, "No data received.");
  502. return false;
  503. }
  504. if (_file)
  505. {
  506. storage_file_close(_file);
  507. storage_file_free(_file);
  508. _file = NULL;
  509. }
  510. if (_storage)
  511. {
  512. furi_record_close(RECORD_STORAGE);
  513. _storage = NULL;
  514. }
  515. return true;
  516. }
  517. #endif // FLIPPER_HTTP_H