flipper_http.h 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279
  1. // flipper_http.h - Flipper HTTP Library (www.github.com/jblanked)
  2. // Author: JBlanked
  3. #ifndef FLIPPER_HTTP_H
  4. #define FLIPPER_HTTP_H
  5. #include <furi.h>
  6. #include <furi_hal.h>
  7. #include <furi_hal_gpio.h>
  8. #include <furi_hal_serial.h>
  9. #include <storage/storage.h>
  10. #include <stdlib.h>
  11. // STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
  12. #define HTTP_TAG "FlipStore" // change this to your app name
  13. #define http_tag "flip_store" // change this to your app id
  14. #define UART_CH (FuriHalSerialIdUsart) // UART channel
  15. #define TIMEOUT_DURATION_TICKS (2 * 1000) // 2 seconds
  16. #define BAUDRATE (115200) // UART baudrate
  17. #define RX_BUF_SIZE 1024 // UART RX buffer size
  18. #define RX_LINE_BUFFER_SIZE 9000 // UART RX line buffer size (increase for large responses)
  19. // Forward declaration for callback
  20. typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
  21. // Functions
  22. bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
  23. void flipper_http_deinit();
  24. //---
  25. void flipper_http_rx_callback(const char *line, void *context);
  26. bool flipper_http_send_data(const char *data);
  27. //---
  28. bool flipper_http_connect_wifi();
  29. bool flipper_http_disconnect_wifi();
  30. bool flipper_http_ping();
  31. bool flipper_http_save_wifi(const char *ssid, const char *password);
  32. //---
  33. bool flipper_http_get_request(const char *url);
  34. bool flipper_http_get_request_with_headers(const char *url, const char *headers);
  35. bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload);
  36. bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload);
  37. bool flipper_http_delete_request_with_headers(const char *url, const char *headers, const char *payload);
  38. //---
  39. bool flipper_http_get_request_bytes(const char *url, const char *headers);
  40. bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload);
  41. //---
  42. bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]);
  43. char *trim(const char *str);
  44. // Define GPIO pins for UART
  45. GpioPin test_pins[2] = {
  46. {.port = GPIOA, .pin = LL_GPIO_PIN_7}, // USART1_RX
  47. {.port = GPIOA, .pin = LL_GPIO_PIN_6} // USART1_TX
  48. };
  49. // State variable to track the UART state
  50. typedef enum
  51. {
  52. INACTIVE, // Inactive state
  53. IDLE, // Default state
  54. RECEIVING, // Receiving data
  55. SENDING, // Sending data
  56. ISSUE, // Issue with connection
  57. } SerialState;
  58. // Event Flags for UART Worker Thread
  59. typedef enum
  60. {
  61. WorkerEvtStop = (1 << 0),
  62. WorkerEvtRxDone = (1 << 1),
  63. } WorkerEvtFlags;
  64. // FlipperHTTP Structure
  65. typedef struct
  66. {
  67. FuriStreamBuffer *flipper_http_stream; // Stream buffer for UART communication
  68. FuriHalSerialHandle *serial_handle; // Serial handle for UART communication
  69. FuriThread *rx_thread; // Worker thread for UART
  70. uint8_t rx_buf[RX_BUF_SIZE]; // Buffer for received data
  71. FuriThreadId rx_thread_id; // Worker thread ID
  72. FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
  73. void *callback_context; // Context for the callback
  74. SerialState state; // State of the UART
  75. // variable to store the last received data from the UART
  76. char *last_response;
  77. // Timer-related members
  78. FuriTimer *get_timeout_timer; // Timer for HTTP request timeout
  79. char *received_data; // Buffer to store received data
  80. bool started_receiving_get; // Indicates if a GET request has started
  81. bool just_started_get; // Indicates if GET data reception has just started
  82. bool started_receiving_post; // Indicates if a POST request has started
  83. bool just_started_post; // Indicates if POST data reception has just started
  84. bool started_receiving_put; // Indicates if a PUT request has started
  85. bool just_started_put; // Indicates if PUT data reception has just started
  86. bool started_receiving_delete; // Indicates if a DELETE request has started
  87. bool just_started_delete; // Indicates if DELETE data reception has just started
  88. // Buffer to hold the raw bytes received from the UART
  89. uint8_t *received_bytes;
  90. size_t received_bytes_len; // Length of the received bytes
  91. // File path to save the bytes received
  92. char file_path[256];
  93. bool save_data; // Flag to save the received data
  94. } FlipperHTTP;
  95. FlipperHTTP fhttp;
  96. // Function to append received data to file
  97. bool append_to_file(const char *file_path, const void *data, size_t data_size)
  98. {
  99. Storage *storage = furi_record_open(RECORD_STORAGE);
  100. File *file = storage_file_alloc(storage);
  101. // Open the file in append mode
  102. if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_OPEN_APPEND))
  103. {
  104. FURI_LOG_E(HTTP_TAG, "Failed to open file for appending: %s", file_path);
  105. storage_file_free(file);
  106. furi_record_close(RECORD_STORAGE);
  107. return false;
  108. }
  109. // Write the data to the file
  110. if (storage_file_write(file, data, data_size) != data_size)
  111. {
  112. FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
  113. storage_file_close(file);
  114. storage_file_free(file);
  115. furi_record_close(RECORD_STORAGE);
  116. return false;
  117. }
  118. storage_file_close(file);
  119. storage_file_free(file);
  120. furi_record_close(RECORD_STORAGE);
  121. return true;
  122. }
  123. // UART worker thread
  124. /**
  125. * @brief Worker thread to handle UART data asynchronously.
  126. * @return 0
  127. * @param context The context to pass to the callback.
  128. * @note This function will handle received data asynchronously via the callback.
  129. */
  130. static int32_t flipper_http_worker(void *context)
  131. {
  132. UNUSED(context);
  133. size_t rx_line_pos = 0;
  134. char *rx_line_buffer = (char *)malloc(RX_LINE_BUFFER_SIZE);
  135. if (!rx_line_buffer)
  136. {
  137. // Handle malloc failure
  138. FURI_LOG_E(HTTP_TAG, "Failed to allocate memory for rx_line_buffer");
  139. return -1;
  140. }
  141. // Create the file path if not already set
  142. // snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps/http_received_data.fap");
  143. while (1)
  144. {
  145. uint32_t events = furi_thread_flags_wait(WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
  146. if (events & WorkerEvtStop)
  147. break;
  148. if (events & WorkerEvtRxDone)
  149. {
  150. size_t len = furi_stream_buffer_receive(fhttp.flipper_http_stream, fhttp.rx_buf, RX_BUF_SIZE, 0);
  151. // Append each received byte chunk to the file
  152. if (fhttp.save_data && !append_to_file(fhttp.file_path, fhttp.rx_buf, len))
  153. {
  154. FURI_LOG_E(HTTP_TAG, "Failed to append received data to file");
  155. }
  156. for (size_t i = 0; i < len; i++)
  157. {
  158. char c = fhttp.rx_buf[i]; // Raw byte received
  159. if (c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1)
  160. {
  161. rx_line_buffer[rx_line_pos] = '\0'; // Null-terminate the line
  162. // Invoke the callback with the complete line
  163. if (fhttp.handle_rx_line_cb)
  164. {
  165. fhttp.handle_rx_line_cb(rx_line_buffer, fhttp.callback_context);
  166. }
  167. // Reset the line buffer position
  168. rx_line_pos = 0;
  169. }
  170. else
  171. {
  172. rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
  173. }
  174. }
  175. }
  176. }
  177. // Free the allocated memory before exiting the thread
  178. free(rx_line_buffer);
  179. return 0;
  180. }
  181. // Timer callback function
  182. /**
  183. * @brief Callback function for the GET timeout timer.
  184. * @return 0
  185. * @param context The context to pass to the callback.
  186. * @note This function will be called when the GET request times out.
  187. */
  188. void get_timeout_timer_callback(void *context)
  189. {
  190. UNUSED(context);
  191. FURI_LOG_E(HTTP_TAG, "Timeout reached: 2 seconds without receiving the end.");
  192. // Reset the state
  193. fhttp.started_receiving_get = false;
  194. fhttp.started_receiving_post = false;
  195. fhttp.started_receiving_put = false;
  196. fhttp.started_receiving_delete = false;
  197. // Free received data if any
  198. if (fhttp.received_data)
  199. {
  200. free(fhttp.received_data);
  201. fhttp.received_data = NULL;
  202. }
  203. // Update UART state
  204. fhttp.state = ISSUE;
  205. }
  206. // UART RX Handler Callback (Interrupt Context)
  207. /**
  208. * @brief A private callback function to handle received data asynchronously.
  209. * @return void
  210. * @param handle The UART handle.
  211. * @param event The event type.
  212. * @param context The context to pass to the callback.
  213. * @note This function will handle received data asynchronously via the callback.
  214. */
  215. static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerialRxEvent event, void *context)
  216. {
  217. UNUSED(context);
  218. if (event == FuriHalSerialRxEventData)
  219. {
  220. uint8_t data = furi_hal_serial_async_rx(handle);
  221. furi_stream_buffer_send(fhttp.flipper_http_stream, &data, 1, 0);
  222. furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtRxDone);
  223. }
  224. }
  225. // UART initialization function
  226. /**
  227. * @brief Initialize UART.
  228. * @return true if the UART was initialized successfully, false otherwise.
  229. * @param callback The callback function to handle received data (ex. flipper_http_rx_callback).
  230. * @param context The context to pass to the callback.
  231. * @note The received data will be handled asynchronously via the callback.
  232. */
  233. bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
  234. {
  235. if (!context)
  236. {
  237. FURI_LOG_E(HTTP_TAG, "Invalid context provided to flipper_http_init.");
  238. return false;
  239. }
  240. if (!callback)
  241. {
  242. FURI_LOG_E(HTTP_TAG, "Invalid callback provided to flipper_http_init.");
  243. return false;
  244. }
  245. fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
  246. if (!fhttp.flipper_http_stream)
  247. {
  248. FURI_LOG_E(HTTP_TAG, "Failed to allocate UART stream buffer.");
  249. return false;
  250. }
  251. fhttp.rx_thread = furi_thread_alloc();
  252. if (!fhttp.rx_thread)
  253. {
  254. FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread.");
  255. furi_stream_buffer_free(fhttp.flipper_http_stream);
  256. return false;
  257. }
  258. furi_thread_set_name(fhttp.rx_thread, "FlipperHTTP_RxThread");
  259. furi_thread_set_stack_size(fhttp.rx_thread, 1024);
  260. furi_thread_set_context(fhttp.rx_thread, &fhttp);
  261. furi_thread_set_callback(fhttp.rx_thread, flipper_http_worker);
  262. fhttp.handle_rx_line_cb = callback;
  263. fhttp.callback_context = context;
  264. furi_thread_start(fhttp.rx_thread);
  265. fhttp.rx_thread_id = furi_thread_get_id(fhttp.rx_thread);
  266. // Initialize GPIO pins for UART
  267. furi_hal_gpio_init_simple(&test_pins[0], GpioModeInput);
  268. furi_hal_gpio_init_simple(&test_pins[1], GpioModeOutputPushPull);
  269. // handle when the UART control is busy to avoid furi_check failed
  270. if (furi_hal_serial_control_is_busy(UART_CH))
  271. {
  272. FURI_LOG_E(HTTP_TAG, "UART control is busy.");
  273. return false;
  274. }
  275. fhttp.serial_handle = furi_hal_serial_control_acquire(UART_CH);
  276. if (!fhttp.serial_handle)
  277. {
  278. FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL");
  279. // Cleanup resources
  280. furi_thread_free(fhttp.rx_thread);
  281. furi_stream_buffer_free(fhttp.flipper_http_stream);
  282. return false;
  283. }
  284. // Initialize UART with acquired handle
  285. furi_hal_serial_init(fhttp.serial_handle, BAUDRATE);
  286. // Enable RX direction
  287. furi_hal_serial_enable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
  288. // Start asynchronous RX with the callback
  289. furi_hal_serial_async_rx_start(fhttp.serial_handle, _flipper_http_rx_callback, &fhttp, false);
  290. // Wait for the TX to complete to ensure UART is ready
  291. furi_hal_serial_tx_wait_complete(fhttp.serial_handle);
  292. // Allocate the timer for handling timeouts
  293. fhttp.get_timeout_timer = furi_timer_alloc(
  294. get_timeout_timer_callback, // Callback function
  295. FuriTimerTypeOnce, // One-shot timer
  296. &fhttp // Context passed to callback
  297. );
  298. if (!fhttp.get_timeout_timer)
  299. {
  300. FURI_LOG_E(HTTP_TAG, "Failed to allocate HTTP request timeout timer.");
  301. // Cleanup resources
  302. furi_hal_serial_async_rx_stop(fhttp.serial_handle);
  303. furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
  304. furi_hal_serial_control_release(fhttp.serial_handle);
  305. furi_hal_serial_deinit(fhttp.serial_handle);
  306. furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop);
  307. furi_thread_join(fhttp.rx_thread);
  308. furi_thread_free(fhttp.rx_thread);
  309. furi_stream_buffer_free(fhttp.flipper_http_stream);
  310. return false;
  311. }
  312. // Set the timer thread priority if needed
  313. furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
  314. fhttp.file_path[0] = '\0'; // Null-terminate the file path
  315. fhttp.received_data = NULL;
  316. fhttp.received_bytes = NULL;
  317. fhttp.received_bytes_len = 0;
  318. return true;
  319. }
  320. // Deinitialize UART
  321. /**
  322. * @brief Deinitialize UART.
  323. * @return void
  324. * @note This function will stop the asynchronous RX, release the serial handle, and free the resources.
  325. */
  326. void flipper_http_deinit()
  327. {
  328. if (fhttp.serial_handle == NULL)
  329. {
  330. FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?");
  331. return;
  332. }
  333. // Stop asynchronous RX
  334. furi_hal_serial_async_rx_stop(fhttp.serial_handle);
  335. // Release and deinitialize the serial handle
  336. furi_hal_serial_disable_direction(fhttp.serial_handle, FuriHalSerialDirectionRx);
  337. furi_hal_serial_control_release(fhttp.serial_handle);
  338. furi_hal_serial_deinit(fhttp.serial_handle);
  339. // Signal the worker thread to stop
  340. furi_thread_flags_set(fhttp.rx_thread_id, WorkerEvtStop);
  341. // Wait for the thread to finish
  342. furi_thread_join(fhttp.rx_thread);
  343. // Free the thread resources
  344. furi_thread_free(fhttp.rx_thread);
  345. // Free the stream buffer
  346. furi_stream_buffer_free(fhttp.flipper_http_stream);
  347. // Free the timer
  348. if (fhttp.get_timeout_timer)
  349. {
  350. furi_timer_free(fhttp.get_timeout_timer);
  351. fhttp.get_timeout_timer = NULL;
  352. }
  353. // Free received data if any
  354. if (fhttp.received_data)
  355. {
  356. free(fhttp.received_data);
  357. fhttp.received_data = NULL;
  358. }
  359. // Free the last response
  360. if (fhttp.last_response)
  361. {
  362. free(fhttp.last_response);
  363. fhttp.last_response = NULL;
  364. }
  365. // FURI_LOG_I("FlipperHTTP", "UART deinitialized successfully.");
  366. }
  367. // Function to send data over UART with newline termination
  368. /**
  369. * @brief Send data over UART with newline termination.
  370. * @return true if the data was sent successfully, false otherwise.
  371. * @param data The data to send over UART.
  372. * @note The data will be sent over UART with a newline character appended.
  373. */
  374. bool flipper_http_send_data(const char *data)
  375. {
  376. size_t data_length = strlen(data);
  377. if (data_length == 0)
  378. {
  379. FURI_LOG_E("FlipperHTTP", "Attempted to send empty data.");
  380. return false;
  381. }
  382. // Create a buffer with data + '\n'
  383. size_t send_length = data_length + 1; // +1 for '\n'
  384. if (send_length > 256)
  385. { // Ensure buffer size is sufficient
  386. FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP.");
  387. return false;
  388. }
  389. char send_buffer[257]; // 256 + 1 for safety
  390. strncpy(send_buffer, data, 256);
  391. send_buffer[data_length] = '\n'; // Append newline
  392. send_buffer[data_length + 1] = '\0'; // Null-terminate
  393. if (fhttp.state == INACTIVE && ((strstr(send_buffer, "[PING]") == NULL) && (strstr(send_buffer, "[WIFI/CONNECT]") == NULL)))
  394. {
  395. FURI_LOG_E("FlipperHTTP", "Cannot send data while INACTIVE.");
  396. fhttp.last_response = "Cannot send data while INACTIVE.";
  397. return false;
  398. }
  399. fhttp.state = SENDING;
  400. furi_hal_serial_tx(fhttp.serial_handle, (const uint8_t *)send_buffer, send_length);
  401. // Uncomment below line to log the data sent over UART
  402. // FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
  403. fhttp.state = IDLE;
  404. return true;
  405. }
  406. // Function to send a PING request
  407. /**
  408. * @brief Send a GET request to the specified URL.
  409. * @return true if the request was successful, false otherwise.
  410. * @param url The URL to send the GET request to.
  411. * @note The received data will be handled asynchronously via the callback.
  412. * @note This is best used to check if the Wifi Dev Board is connected.
  413. * @note The state will remain INACTIVE until a PONG is received.
  414. */
  415. bool flipper_http_ping()
  416. {
  417. const char *command = "[PING]";
  418. if (!flipper_http_send_data(command))
  419. {
  420. FURI_LOG_E("FlipperHTTP", "Failed to send PING command.");
  421. return false;
  422. }
  423. // set state as INACTIVE to be made IDLE if PONG is received
  424. fhttp.state = INACTIVE;
  425. // The response will be handled asynchronously via the callback
  426. return true;
  427. }
  428. // Function to save WiFi settings (returns true if successful)
  429. /**
  430. * @brief Send a command to save WiFi settings.
  431. * @return true if the request was successful, false otherwise.
  432. * @note The received data will be handled asynchronously via the callback.
  433. */
  434. bool flipper_http_save_wifi(const char *ssid, const char *password)
  435. {
  436. if (!ssid || !password)
  437. {
  438. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi.");
  439. return false;
  440. }
  441. char buffer[256];
  442. int ret = snprintf(buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password);
  443. if (ret < 0 || ret >= (int)sizeof(buffer))
  444. {
  445. FURI_LOG_E("FlipperHTTP", "Failed to format WiFi save command.");
  446. return false;
  447. }
  448. if (!flipper_http_send_data(buffer))
  449. {
  450. FURI_LOG_E("FlipperHTTP", "Failed to send WiFi save command.");
  451. return false;
  452. }
  453. // The response will be handled asynchronously via the callback
  454. return true;
  455. }
  456. // Function to disconnect from WiFi (returns true if successful)
  457. /**
  458. * @brief Send a command to disconnect from WiFi.
  459. * @return true if the request was successful, false otherwise.
  460. * @note The received data will be handled asynchronously via the callback.
  461. */
  462. bool flipper_http_disconnect_wifi()
  463. {
  464. const char *command = "[WIFI/DISCONNECT]";
  465. if (!flipper_http_send_data(command))
  466. {
  467. FURI_LOG_E("FlipperHTTP", "Failed to send WiFi disconnect command.");
  468. return false;
  469. }
  470. // The response will be handled asynchronously via the callback
  471. return true;
  472. }
  473. // Function to connect to WiFi (returns true if successful)
  474. /**
  475. * @brief Send a command to connect to WiFi.
  476. * @return true if the request was successful, false otherwise.
  477. * @note The received data will be handled asynchronously via the callback.
  478. */
  479. bool flipper_http_connect_wifi()
  480. {
  481. const char *command = "[WIFI/CONNECT]";
  482. if (!flipper_http_send_data(command))
  483. {
  484. FURI_LOG_E("FlipperHTTP", "Failed to send WiFi connect command.");
  485. return false;
  486. }
  487. // The response will be handled asynchronously via the callback
  488. return true;
  489. }
  490. // Function to send a GET request
  491. /**
  492. * @brief Send a GET request to the specified URL.
  493. * @return true if the request was successful, false otherwise.
  494. * @param url The URL to send the GET request to.
  495. * @note The received data will be handled asynchronously via the callback.
  496. */
  497. bool flipper_http_get_request(const char *url)
  498. {
  499. if (!url)
  500. {
  501. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request.");
  502. return false;
  503. }
  504. // Prepare GET request command
  505. char command[256];
  506. int ret = snprintf(command, sizeof(command), "[GET]%s", url);
  507. if (ret < 0 || ret >= (int)sizeof(command))
  508. {
  509. FURI_LOG_E("FlipperHTTP", "Failed to format GET request command.");
  510. return false;
  511. }
  512. // Send GET request via UART
  513. if (!flipper_http_send_data(command))
  514. {
  515. FURI_LOG_E("FlipperHTTP", "Failed to send GET request command.");
  516. return false;
  517. }
  518. // The response will be handled asynchronously via the callback
  519. return true;
  520. }
  521. // Function to send a GET request with headers
  522. /**
  523. * @brief Send a GET request to the specified URL.
  524. * @return true if the request was successful, false otherwise.
  525. * @param url The URL to send the GET request to.
  526. * @param headers The headers to send with the GET request.
  527. * @note The received data will be handled asynchronously via the callback.
  528. */
  529. bool flipper_http_get_request_with_headers(const char *url, const char *headers)
  530. {
  531. if (!url || !headers)
  532. {
  533. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_with_headers.");
  534. return false;
  535. }
  536. // Prepare GET request command with headers
  537. char command[256];
  538. int ret = snprintf(command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers);
  539. if (ret < 0 || ret >= (int)sizeof(command))
  540. {
  541. FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers.");
  542. return false;
  543. }
  544. // Send GET request via UART
  545. if (!flipper_http_send_data(command))
  546. {
  547. FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
  548. return false;
  549. }
  550. // The response will be handled asynchronously via the callback
  551. return true;
  552. }
  553. // Function to send a GET request with headers and return bytes
  554. /**
  555. * @brief Send a GET request to the specified URL.
  556. * @return true if the request was successful, false otherwise.
  557. * @param url The URL to send the GET request to.
  558. * @param headers The headers to send with the GET request.
  559. * @note The received data will be handled asynchronously via the callback.
  560. */
  561. bool flipper_http_get_request_bytes(const char *url, const char *headers)
  562. {
  563. if (!url || !headers)
  564. {
  565. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_get_request_bytes.");
  566. return false;
  567. }
  568. // Prepare GET request command with headers
  569. char command[256];
  570. int ret = snprintf(command, sizeof(command), "[GET/BYTES]{\"url\":\"%s\",\"headers\":%s}", url, headers);
  571. if (ret < 0 || ret >= (int)sizeof(command))
  572. {
  573. FURI_LOG_E("FlipperHTTP", "Failed to format GET request command with headers.");
  574. return false;
  575. }
  576. // Send GET request via UART
  577. if (!flipper_http_send_data(command))
  578. {
  579. FURI_LOG_E("FlipperHTTP", "Failed to send GET request command with headers.");
  580. return false;
  581. }
  582. // The response will be handled asynchronously via the callback
  583. return true;
  584. }
  585. // Function to send a POST request with headers
  586. /**
  587. * @brief Send a POST request to the specified URL.
  588. * @return true if the request was successful, false otherwise.
  589. * @param url The URL to send the POST request to.
  590. * @param headers The headers to send with the POST request.
  591. * @param payload The data to send with the POST request.
  592. * @note The received data will be handled asynchronously via the callback.
  593. */
  594. bool flipper_http_post_request_with_headers(const char *url, const char *headers, const char *payload)
  595. {
  596. if (!url || !headers || !payload)
  597. {
  598. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_post_request_with_headers.");
  599. return false;
  600. }
  601. // Prepare POST request command with headers and data
  602. char command[256];
  603. int ret = snprintf(command, sizeof(command), "[POST/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  604. if (ret < 0 || ret >= (int)sizeof(command))
  605. {
  606. FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data.");
  607. return false;
  608. }
  609. // Send POST request via UART
  610. if (!flipper_http_send_data(command))
  611. {
  612. FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
  613. return false;
  614. }
  615. // The response will be handled asynchronously via the callback
  616. return true;
  617. }
  618. // Function to send a POST request with headers and return bytes
  619. /**
  620. * @brief Send a POST request to the specified URL.
  621. * @return true if the request was successful, false otherwise.
  622. * @param url The URL to send the POST request to.
  623. * @param headers The headers to send with the POST request.
  624. * @param payload The data to send with the POST request.
  625. * @note The received data will be handled asynchronously via the callback.
  626. */
  627. bool flipper_http_post_request_bytes(const char *url, const char *headers, const char *payload)
  628. {
  629. if (!url || !headers || !payload)
  630. {
  631. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_post_request_bytes.");
  632. return false;
  633. }
  634. // Prepare POST request command with headers and data
  635. char command[256];
  636. int ret = snprintf(command, sizeof(command), "[POST/BYTES]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  637. if (ret < 0 || ret >= (int)sizeof(command))
  638. {
  639. FURI_LOG_E("FlipperHTTP", "Failed to format POST request command with headers and data.");
  640. return false;
  641. }
  642. // Send POST request via UART
  643. if (!flipper_http_send_data(command))
  644. {
  645. FURI_LOG_E("FlipperHTTP", "Failed to send POST request command with headers and data.");
  646. return false;
  647. }
  648. // The response will be handled asynchronously via the callback
  649. return true;
  650. }
  651. // Function to send a PUT request with headers
  652. /**
  653. * @brief Send a PUT request to the specified URL.
  654. * @return true if the request was successful, false otherwise.
  655. * @param url The URL to send the PUT request to.
  656. * @param headers The headers to send with the PUT request.
  657. * @param payload The data to send with the PUT request.
  658. * @note The received data will be handled asynchronously via the callback.
  659. */
  660. bool flipper_http_put_request_with_headers(const char *url, const char *headers, const char *payload)
  661. {
  662. if (!url || !headers || !payload)
  663. {
  664. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_put_request_with_headers.");
  665. return false;
  666. }
  667. // Prepare PUT request command with headers and data
  668. char command[256];
  669. int ret = snprintf(command, sizeof(command), "[PUT/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  670. if (ret < 0 || ret >= (int)sizeof(command))
  671. {
  672. FURI_LOG_E("FlipperHTTP", "Failed to format PUT request command with headers and data.");
  673. return false;
  674. }
  675. // Send PUT request via UART
  676. if (!flipper_http_send_data(command))
  677. {
  678. FURI_LOG_E("FlipperHTTP", "Failed to send PUT request command with headers and data.");
  679. return false;
  680. }
  681. // The response will be handled asynchronously via the callback
  682. return true;
  683. }
  684. // Function to send a DELETE request with headers
  685. /**
  686. * @brief Send a DELETE request to the specified URL.
  687. * @return true if the request was successful, false otherwise.
  688. * @param url The URL to send the DELETE request to.
  689. * @param headers The headers to send with the DELETE request.
  690. * @param payload The data to send with the DELETE request.
  691. * @note The received data will be handled asynchronously via the callback.
  692. */
  693. bool flipper_http_delete_request_with_headers(const char *url, const char *headers, const char *payload)
  694. {
  695. if (!url || !headers || !payload)
  696. {
  697. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_delete_request_with_headers.");
  698. return false;
  699. }
  700. // Prepare DELETE request command with headers and data
  701. char command[256];
  702. int ret = snprintf(command, sizeof(command), "[DELETE/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  703. if (ret < 0 || ret >= (int)sizeof(command))
  704. {
  705. FURI_LOG_E("FlipperHTTP", "Failed to format DELETE request command with headers and data.");
  706. return false;
  707. }
  708. // Send DELETE request via UART
  709. if (!flipper_http_send_data(command))
  710. {
  711. FURI_LOG_E("FlipperHTTP", "Failed to send DELETE request command with headers and data.");
  712. return false;
  713. }
  714. // The response will be handled asynchronously via the callback
  715. return true;
  716. }
  717. // Function to handle received data asynchronously
  718. /**
  719. * @brief Callback function to handle received data asynchronously.
  720. * @return void
  721. * @param line The received line.
  722. * @param context The context passed to the callback.
  723. * @note The received data will be handled asynchronously via the callback and handles the state of the UART.
  724. */
  725. void flipper_http_rx_callback(const char *line, void *context)
  726. {
  727. if (!line || !context)
  728. {
  729. FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to flipper_http_rx_callback.");
  730. return;
  731. }
  732. // Trim the received line to check if it's empty
  733. char *trimmed_line = trim(line);
  734. if (trimmed_line != NULL && trimmed_line[0] != '\0')
  735. {
  736. fhttp.last_response = (char *)line;
  737. }
  738. free(trimmed_line); // Free the allocated memory for trimmed_line
  739. if (fhttp.state != INACTIVE && fhttp.state != ISSUE)
  740. {
  741. fhttp.state = RECEIVING;
  742. }
  743. // Uncomment below line to log the data received over UART
  744. // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
  745. // Check if we've started receiving data from a GET request
  746. if (fhttp.started_receiving_get)
  747. {
  748. // Restart the timeout timer each time new data is received
  749. furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  750. if (strstr(line, "[GET/END]") != NULL)
  751. {
  752. FURI_LOG_I(HTTP_TAG, "GET request completed.");
  753. // Stop the timer since we've completed the GET request
  754. furi_timer_stop(fhttp.get_timeout_timer);
  755. if (fhttp.received_data)
  756. {
  757. // uncomment if you want to save the received data to the external storage
  758. // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
  759. fhttp.started_receiving_get = false;
  760. fhttp.just_started_get = false;
  761. fhttp.state = IDLE;
  762. return;
  763. }
  764. else
  765. {
  766. FURI_LOG_E(HTTP_TAG, "No data received.");
  767. fhttp.started_receiving_get = false;
  768. fhttp.just_started_get = false;
  769. fhttp.state = IDLE;
  770. return;
  771. }
  772. }
  773. // Append the new line to the existing data
  774. if (fhttp.received_data == NULL)
  775. {
  776. fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
  777. if (fhttp.received_data)
  778. {
  779. strcpy(fhttp.received_data, line);
  780. fhttp.received_data[strlen(line)] = '\n'; // Add newline
  781. fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
  782. }
  783. }
  784. else
  785. {
  786. size_t current_len = strlen(fhttp.received_data);
  787. size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
  788. fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
  789. if (fhttp.received_data)
  790. {
  791. memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
  792. fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline
  793. fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator
  794. }
  795. }
  796. if (!fhttp.just_started_get)
  797. {
  798. fhttp.just_started_get = true;
  799. }
  800. return;
  801. }
  802. // Check if we've started receiving data from a POST request
  803. else if (fhttp.started_receiving_post)
  804. {
  805. // Restart the timeout timer each time new data is received
  806. furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  807. if (strstr(line, "[POST/END]") != NULL)
  808. {
  809. FURI_LOG_I(HTTP_TAG, "POST request completed.");
  810. fhttp.save_data = false;
  811. // Stop the timer since we've completed the POST request
  812. furi_timer_stop(fhttp.get_timeout_timer);
  813. if (fhttp.received_data)
  814. {
  815. // uncomment if you want to save the received data to the external storage
  816. // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
  817. fhttp.started_receiving_post = false;
  818. fhttp.just_started_post = false;
  819. fhttp.state = IDLE;
  820. return;
  821. }
  822. else
  823. {
  824. FURI_LOG_E(HTTP_TAG, "No data received.");
  825. fhttp.started_receiving_post = false;
  826. fhttp.just_started_post = false;
  827. fhttp.state = IDLE;
  828. return;
  829. }
  830. }
  831. // Append the new line to the existing data
  832. if (fhttp.received_data == NULL)
  833. {
  834. fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
  835. if (fhttp.received_data)
  836. {
  837. strcpy(fhttp.received_data, line);
  838. fhttp.received_data[strlen(line)] = '\n'; // Add newline
  839. fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
  840. }
  841. }
  842. else
  843. {
  844. size_t current_len = strlen(fhttp.received_data);
  845. size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
  846. fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
  847. if (fhttp.received_data)
  848. {
  849. memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
  850. fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline
  851. fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator
  852. }
  853. }
  854. if (!fhttp.just_started_post)
  855. {
  856. fhttp.just_started_post = true;
  857. }
  858. return;
  859. }
  860. // Check if we've started receiving data from a PUT request
  861. else if (fhttp.started_receiving_put)
  862. {
  863. // Restart the timeout timer each time new data is received
  864. furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  865. if (strstr(line, "[PUT/END]") != NULL)
  866. {
  867. FURI_LOG_I(HTTP_TAG, "PUT request completed.");
  868. // Stop the timer since we've completed the PUT request
  869. furi_timer_stop(fhttp.get_timeout_timer);
  870. if (fhttp.received_data)
  871. {
  872. // uncomment if you want to save the received data to the external storage
  873. // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
  874. fhttp.started_receiving_put = false;
  875. fhttp.just_started_put = false;
  876. fhttp.state = IDLE;
  877. return;
  878. }
  879. else
  880. {
  881. FURI_LOG_E(HTTP_TAG, "No data received.");
  882. fhttp.started_receiving_put = false;
  883. fhttp.just_started_put = false;
  884. fhttp.state = IDLE;
  885. return;
  886. }
  887. }
  888. // Append the new line to the existing data
  889. if (fhttp.received_data == NULL)
  890. {
  891. fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
  892. if (fhttp.received_data)
  893. {
  894. strcpy(fhttp.received_data, line);
  895. fhttp.received_data[strlen(line)] = '\n'; // Add newline
  896. fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
  897. }
  898. }
  899. else
  900. {
  901. size_t current_len = strlen(fhttp.received_data);
  902. size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
  903. fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
  904. if (fhttp.received_data)
  905. {
  906. memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
  907. fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline
  908. fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator
  909. }
  910. }
  911. if (!fhttp.just_started_put)
  912. {
  913. fhttp.just_started_put = true;
  914. }
  915. return;
  916. }
  917. // Check if we've started receiving data from a DELETE request
  918. else if (fhttp.started_receiving_delete)
  919. {
  920. // Restart the timeout timer each time new data is received
  921. furi_timer_restart(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  922. if (strstr(line, "[DELETE/END]") != NULL)
  923. {
  924. FURI_LOG_I(HTTP_TAG, "DELETE request completed.");
  925. // Stop the timer since we've completed the DELETE request
  926. furi_timer_stop(fhttp.get_timeout_timer);
  927. if (fhttp.received_data)
  928. {
  929. // uncomment if you want to save the received data to the external storage
  930. // flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
  931. fhttp.started_receiving_delete = false;
  932. fhttp.just_started_delete = false;
  933. fhttp.state = IDLE;
  934. return;
  935. }
  936. else
  937. {
  938. FURI_LOG_E(HTTP_TAG, "No data received.");
  939. fhttp.started_receiving_delete = false;
  940. fhttp.just_started_delete = false;
  941. fhttp.state = IDLE;
  942. return;
  943. }
  944. }
  945. // Append the new line to the existing data
  946. if (fhttp.received_data == NULL)
  947. {
  948. fhttp.received_data = (char *)malloc(strlen(line) + 2); // +2 for newline and null terminator
  949. if (fhttp.received_data)
  950. {
  951. strcpy(fhttp.received_data, line);
  952. fhttp.received_data[strlen(line)] = '\n'; // Add newline
  953. fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
  954. }
  955. }
  956. else
  957. {
  958. size_t current_len = strlen(fhttp.received_data);
  959. size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
  960. fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
  961. if (fhttp.received_data)
  962. {
  963. memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
  964. fhttp.received_data[current_len + strlen(line)] = '\n'; // Add newline
  965. fhttp.received_data[current_len + strlen(line) + 1] = '\0'; // Null terminator
  966. }
  967. }
  968. if (!fhttp.just_started_delete)
  969. {
  970. fhttp.just_started_delete = true;
  971. }
  972. return;
  973. }
  974. // Handle different types of responses
  975. if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL)
  976. {
  977. FURI_LOG_I(HTTP_TAG, "Operation succeeded.");
  978. }
  979. else if (strstr(line, "[INFO]") != NULL)
  980. {
  981. FURI_LOG_I(HTTP_TAG, "Received info: %s", line);
  982. if (fhttp.state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
  983. {
  984. fhttp.state = IDLE;
  985. }
  986. }
  987. else if (strstr(line, "[GET/SUCCESS]") != NULL)
  988. {
  989. FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
  990. fhttp.started_receiving_get = true;
  991. furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  992. fhttp.state = RECEIVING;
  993. fhttp.received_data = NULL;
  994. return;
  995. }
  996. else if (strstr(line, "[POST/SUCCESS]") != NULL)
  997. {
  998. FURI_LOG_I(HTTP_TAG, "POST request succeeded.");
  999. fhttp.started_receiving_post = true;
  1000. furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1001. fhttp.state = RECEIVING;
  1002. fhttp.received_data = NULL;
  1003. fhttp.save_data = true;
  1004. return;
  1005. }
  1006. else if (strstr(line, "[PUT/SUCCESS]") != NULL)
  1007. {
  1008. FURI_LOG_I(HTTP_TAG, "PUT request succeeded.");
  1009. fhttp.started_receiving_put = true;
  1010. furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1011. fhttp.state = RECEIVING;
  1012. fhttp.received_data = NULL;
  1013. return;
  1014. }
  1015. else if (strstr(line, "[DELETE/SUCCESS]") != NULL)
  1016. {
  1017. FURI_LOG_I(HTTP_TAG, "DELETE request succeeded.");
  1018. fhttp.started_receiving_delete = true;
  1019. furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1020. fhttp.state = RECEIVING;
  1021. fhttp.received_data = NULL;
  1022. return;
  1023. }
  1024. else if (strstr(line, "[DISCONNECTED]") != NULL)
  1025. {
  1026. FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");
  1027. }
  1028. else if (strstr(line, "[ERROR]") != NULL)
  1029. {
  1030. FURI_LOG_E(HTTP_TAG, "Received error: %s", line);
  1031. fhttp.state = ISSUE;
  1032. return;
  1033. }
  1034. else if (strstr(line, "[PONG]") != NULL)
  1035. {
  1036. FURI_LOG_I(HTTP_TAG, "Received PONG response: Wifi Dev Board is still alive.");
  1037. // send command to connect to WiFi
  1038. if (fhttp.state == INACTIVE)
  1039. {
  1040. fhttp.state = IDLE;
  1041. return;
  1042. }
  1043. }
  1044. if (fhttp.state == INACTIVE && strstr(line, "[PONG]") != NULL)
  1045. {
  1046. fhttp.state = IDLE;
  1047. }
  1048. else if (fhttp.state == INACTIVE && strstr(line, "[PONG]") == NULL)
  1049. {
  1050. fhttp.state = INACTIVE;
  1051. }
  1052. else
  1053. {
  1054. fhttp.state = IDLE;
  1055. }
  1056. }
  1057. // Function to save received data to a file
  1058. /**
  1059. * @brief Save the received data to a file.
  1060. * @return true if the data was saved successfully, false otherwise.
  1061. * @param bytes_received The number of bytes received.
  1062. * @param line_buffer The buffer containing the received data.
  1063. * @note The data will be saved to a file in the STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt" directory.
  1064. */
  1065. bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[])
  1066. {
  1067. const char *output_file_path = STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt";
  1068. // Ensure the directory exists
  1069. char directory_path[128];
  1070. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag);
  1071. Storage *_storage = NULL;
  1072. File *_file = NULL;
  1073. // Open the storage if not opened already
  1074. // Initialize storage and create the directory if it doesn't exist
  1075. _storage = furi_record_open(RECORD_STORAGE);
  1076. storage_common_mkdir(_storage, directory_path); // Create directory if it doesn't exist
  1077. _file = storage_file_alloc(_storage);
  1078. // Open file for writing and append data line by line
  1079. if (!storage_file_open(_file, output_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  1080. {
  1081. FURI_LOG_E(HTTP_TAG, "Failed to open output file for writing.");
  1082. storage_file_free(_file);
  1083. furi_record_close(RECORD_STORAGE);
  1084. return false;
  1085. }
  1086. // Write each line received from the UART to the file
  1087. if (bytes_received > 0 && _file)
  1088. {
  1089. storage_file_write(_file, line_buffer, bytes_received);
  1090. storage_file_write(_file, "\n", 1); // Add a newline after each line
  1091. }
  1092. else
  1093. {
  1094. FURI_LOG_E(HTTP_TAG, "No data received.");
  1095. return false;
  1096. }
  1097. if (_file)
  1098. {
  1099. storage_file_close(_file);
  1100. storage_file_free(_file);
  1101. _file = NULL;
  1102. }
  1103. if (_storage)
  1104. {
  1105. furi_record_close(RECORD_STORAGE);
  1106. _storage = NULL;
  1107. }
  1108. return true;
  1109. }
  1110. // Function to trim leading and trailing spaces and newlines from a constant string
  1111. char *trim(const char *str)
  1112. {
  1113. const char *end;
  1114. char *trimmed_str;
  1115. size_t len;
  1116. // Trim leading space
  1117. while (isspace((unsigned char)*str))
  1118. str++;
  1119. // All spaces?
  1120. if (*str == 0)
  1121. return strdup(""); // Return an empty string if all spaces
  1122. // Trim trailing space
  1123. end = str + strlen(str) - 1;
  1124. while (end > str && isspace((unsigned char)*end))
  1125. end--;
  1126. // Set length for the trimmed string
  1127. len = end - str + 1;
  1128. // Allocate space for the trimmed string and null terminator
  1129. trimmed_str = (char *)malloc(len + 1);
  1130. if (trimmed_str == NULL)
  1131. {
  1132. return NULL; // Handle memory allocation failure
  1133. }
  1134. // Copy the trimmed part of the string into trimmed_str
  1135. strncpy(trimmed_str, str, len);
  1136. trimmed_str[len] = '\0'; // Null terminate the string
  1137. return trimmed_str;
  1138. }
  1139. #endif // FLIPPER_HTTP_H