flipper_http.h 18 KB

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