flipper_http.h 40 KB

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