flipper_http.h 39 KB

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