FlipperHTTPPico.h 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483
  1. /* FlipperHTTPPico.h for flipper-http-pico.ino (for the Rasberry Pi Pico W)
  2. Author: JBlanked
  3. Github: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
  4. Info: This library is a wrapper around the HTTPClient library and is used to communicate with the FlipperZero over serial.
  5. Created: 2024-10-24
  6. Updated: 2024-10-26
  7. Change Log:
  8. - 2024-10-24: Initial commit
  9. - 2024-10-25: Updated the readSerialLine method to use readStringUntil instead of reading char by char, and added Auto connect
  10. - Reading char by char worked on the dev board but was missing chars on the Pico
  11. - 2024-10-26: Updated the saveWifiSettings and loadWifiSettings methods to save and load a list of wifi networks, and added [WIFI/LIST] command
  12. */
  13. #include <WiFi.h>
  14. #include <HTTPClient.h>
  15. #include <WiFiClientSecure.h>
  16. #include <ArduinoJson.h>
  17. #include <Arduino.h>
  18. #include <LittleFS.h> // replacement for SPIFFS
  19. // Define UART parameters
  20. #define BAUD_RATE 115200
  21. #define PICO_LED LED_BUILTIN
  22. #define ON HIGH
  23. #define OFF LOW
  24. SerialPIO SerialPico(0, 1);
  25. class FlipperHTTP
  26. {
  27. public:
  28. // Constructor
  29. FlipperHTTP()
  30. {
  31. }
  32. // Main methods for flipper-http.ino
  33. void loop();
  34. void setup()
  35. {
  36. SerialPico.begin(BAUD_RATE);
  37. if (!LittleFS.begin())
  38. {
  39. SerialPico.println("An Error has occurred while mounting LittleFS. Attempting to format...");
  40. if (LittleFS.format())
  41. {
  42. SerialPico.println("File system formatted successfully. Re-mounting...");
  43. if (!LittleFS.begin())
  44. {
  45. SerialPico.println("Failed to re-mount LittleFS after formatting.");
  46. delay(1000);
  47. return;
  48. }
  49. }
  50. else
  51. {
  52. SerialPico.println("File system formatting failed.");
  53. delay(1000);
  54. return;
  55. }
  56. }
  57. this->useLED = true;
  58. this->ledStart();
  59. this->loadWifiSettings();
  60. SerialPico.flush();
  61. }
  62. // HTTP Methods
  63. String get(String url);
  64. String get(String url, const char *headerKeys[], const char *headerValues[], int headerSize);
  65. String post(String url, String payload);
  66. String post(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize);
  67. String put(String url, String payload);
  68. String put(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize);
  69. String delete_request(String url, String payload);
  70. String delete_request(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize);
  71. // stream data as bytes
  72. bool get_bytes_to_file(String url, const char *headerKeys[], const char *headerValues[], int headerSize);
  73. bool post_bytes_to_file(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize);
  74. // Save and Load settings to and from SPIFFS
  75. bool saveWifiSettings(String data);
  76. bool loadWifiSettings();
  77. // returns a string of all wifi networks
  78. String scanWifiNetworks()
  79. {
  80. int n = WiFi.scanNetworks();
  81. String networks = "";
  82. for (int i = 0; i < n; ++i)
  83. {
  84. networks += WiFi.SSID(i);
  85. if (i < n - 1)
  86. {
  87. networks += ", ";
  88. }
  89. }
  90. return networks;
  91. }
  92. // Connect to Wifi using the loaded SSID and Password
  93. bool connectToWifi();
  94. // Check if the Dev Board is connected to Wifi
  95. bool isConnectedToWifi() { return WiFi.status() == WL_CONNECTED; }
  96. // Read serial data until newline character
  97. String readSerialLine();
  98. // Clear serial buffer to avoid any residual data
  99. void clearSerialBuffer()
  100. {
  101. while (SerialPico.available() > 0)
  102. {
  103. SerialPico.read();
  104. }
  105. }
  106. // Function to flash the LED
  107. void ledAction(int timeout = 250)
  108. {
  109. digitalWrite(PICO_LED, ON);
  110. delay(timeout);
  111. digitalWrite(PICO_LED, OFF);
  112. delay(timeout);
  113. }
  114. // Display LED sequence when Wifi Board is first connected to the Flipper
  115. void ledStart()
  116. {
  117. // Initialize LED pin
  118. pinMode(PICO_LED, OUTPUT);
  119. ledAction();
  120. ledAction();
  121. ledAction();
  122. }
  123. // Starting LED (Green only)
  124. void ledStatus()
  125. {
  126. if (this->useLED)
  127. {
  128. digitalWrite(PICO_LED, ON);
  129. }
  130. }
  131. // Turn off all LEDs
  132. void ledOff()
  133. {
  134. digitalWrite(PICO_LED, OFF);
  135. }
  136. // get IP addresss
  137. String getIPAddress()
  138. {
  139. return WiFi.localIP().toString();
  140. }
  141. private:
  142. const char *settingsFilePath = "/flipper-http.json"; // Path to the settings file in the SPIFFS file system
  143. char loadedSSID[64] = {0}; // Variable to store SSID
  144. char loadedPassword[64] = {0}; // Variable to store password
  145. bool useLED = true; // Variable to control LED usage
  146. bool readSerialSettings(String receivedData, bool connectAfterSave);
  147. };
  148. // Connect to Wifi using the loaded SSID and Password
  149. bool FlipperHTTP::connectToWifi()
  150. {
  151. if (String(loadedSSID) == "" || String(loadedPassword) == "")
  152. {
  153. SerialPico.println("[ERROR] WiFi SSID or Password is empty.");
  154. return false;
  155. }
  156. WiFi.disconnect(true); // Ensure WiFi is disconnected before reconnecting
  157. WiFi.begin(loadedSSID, loadedPassword);
  158. int i = 0;
  159. while (!this->isConnectedToWifi() && i < 20)
  160. {
  161. delay(500);
  162. i++;
  163. SerialPico.print(".");
  164. }
  165. SerialPico.println(); // Move to next line after dots
  166. if (this->isConnectedToWifi())
  167. {
  168. SerialPico.println("[SUCCESS] Successfully connected to Wifi.");
  169. return true;
  170. }
  171. else
  172. {
  173. SerialPico.println("[ERROR] Failed to connect to Wifi.");
  174. return false;
  175. }
  176. }
  177. // Save WiFi settings to LittleFS
  178. bool FlipperHTTP::saveWifiSettings(String jsonData)
  179. {
  180. DynamicJsonDocument doc(1024);
  181. DeserializationError error = deserializeJson(doc, jsonData);
  182. if (error)
  183. {
  184. SerialPico.println("[ERROR] Failed to parse JSON data.");
  185. return false;
  186. }
  187. const char *newSSID = doc["ssid"];
  188. const char *newPassword = doc["password"];
  189. // Load existing settings if they exist
  190. DynamicJsonDocument existingDoc(2048);
  191. File file = LittleFS.open(settingsFilePath, "r");
  192. if (file)
  193. {
  194. deserializeJson(existingDoc, file);
  195. file.close();
  196. }
  197. // Check if SSID is already saved
  198. bool found = false;
  199. for (JsonObject wifi : existingDoc["wifi_list"].as<JsonArray>())
  200. {
  201. if (wifi["ssid"] == newSSID)
  202. {
  203. found = true;
  204. break;
  205. }
  206. }
  207. // Add new SSID and password if not found
  208. if (!found)
  209. {
  210. JsonArray wifiList = existingDoc["wifi_list"].to<JsonArray>();
  211. JsonObject newWifi = wifiList.createNestedObject();
  212. newWifi["ssid"] = newSSID;
  213. newWifi["password"] = newPassword;
  214. // Save updated list to file
  215. file = LittleFS.open(settingsFilePath, "w");
  216. if (!file)
  217. {
  218. SerialPico.println("[ERROR] Failed to open file for writing.");
  219. return false;
  220. }
  221. serializeJson(existingDoc, file);
  222. file.close();
  223. }
  224. SerialPico.println("[SUCCESS] Settings saved to LittleFS.");
  225. return true;
  226. }
  227. bool FlipperHTTP::loadWifiSettings()
  228. {
  229. File file = LittleFS.open(settingsFilePath, "r");
  230. if (!file)
  231. {
  232. return false;
  233. }
  234. String fileContent = file.readString();
  235. file.close();
  236. DynamicJsonDocument doc(2048);
  237. DeserializationError error = deserializeJson(doc, fileContent);
  238. if (error)
  239. {
  240. return false;
  241. }
  242. JsonArray wifiList = doc["wifi_list"].as<JsonArray>();
  243. for (JsonObject wifi : wifiList)
  244. {
  245. const char *ssid = wifi["ssid"];
  246. const char *password = wifi["password"];
  247. strlcpy(loadedSSID, ssid, sizeof(loadedSSID));
  248. strlcpy(loadedPassword, password, sizeof(loadedPassword));
  249. WiFi.begin(ssid, password);
  250. int attempts = 0;
  251. while (!this->isConnectedToWifi() && attempts < 4) // 2 seconds total, 500ms delay each
  252. {
  253. delay(500);
  254. attempts++;
  255. SerialPico.print(".");
  256. }
  257. if (this->isConnectedToWifi())
  258. {
  259. return true;
  260. }
  261. }
  262. return false;
  263. }
  264. String FlipperHTTP::readSerialLine()
  265. {
  266. String receivedData = "";
  267. // this worked on dev board but even with delay changes, it was missing chars
  268. // while (SerialPico.available() > 0)
  269. // {
  270. // char incomingChar = SerialPico.read();
  271. // if (incomingChar == '\n')
  272. // {
  273. // break;
  274. // }
  275. // receivedData += incomingChar;
  276. // delay(5); // Minimal delay to allow buffer to fill
  277. // }
  278. receivedData = SerialPico.readStringUntil('\n');
  279. receivedData.trim(); // Remove any leading/trailing whitespace
  280. return receivedData;
  281. }
  282. bool FlipperHTTP::readSerialSettings(String receivedData, bool connectAfterSave)
  283. {
  284. DynamicJsonDocument doc(1024);
  285. DeserializationError error = deserializeJson(doc, receivedData);
  286. if (error)
  287. {
  288. SerialPico.print("[ERROR] Failed to parse JSON: ");
  289. SerialPico.println(error.c_str());
  290. return false;
  291. }
  292. // Extract values from JSON
  293. if (doc.containsKey("ssid") && doc.containsKey("password"))
  294. {
  295. strlcpy(loadedSSID, doc["ssid"], sizeof(loadedSSID));
  296. strlcpy(loadedPassword, doc["password"], sizeof(loadedPassword));
  297. }
  298. else
  299. {
  300. SerialPico.println("[ERROR] JSON does not contain ssid and password.");
  301. return false;
  302. }
  303. // Save to SPIFFS
  304. if (!this->saveWifiSettings(receivedData))
  305. {
  306. SerialPico.println("[ERROR] Failed to save settings to file.");
  307. return false;
  308. }
  309. // Attempt to reconnect with new settings
  310. if (connectAfterSave && this->connectToWifi())
  311. {
  312. SerialPico.println("[SUCCESS] Connected to the new Wifi network.");
  313. }
  314. return true;
  315. }
  316. String FlipperHTTP::get(String url)
  317. {
  318. WiFiClientSecure client;
  319. client.setInsecure(); // Bypass certificate validation
  320. HTTPClient http;
  321. String payload = "";
  322. if (http.begin(client, url))
  323. {
  324. int httpCode = http.GET();
  325. if (httpCode > 0)
  326. {
  327. payload = http.getString();
  328. http.end();
  329. return payload;
  330. }
  331. else
  332. {
  333. SerialPico.print("[ERROR] GET Request Failed, error: ");
  334. SerialPico.println(http.errorToString(httpCode).c_str());
  335. }
  336. http.end();
  337. }
  338. else
  339. {
  340. SerialPico.println("[ERROR] Unable to connect to the server.");
  341. }
  342. // Clear serial buffer to avoid any residual data
  343. this->clearSerialBuffer();
  344. return payload;
  345. }
  346. String FlipperHTTP::get(String url, const char *headerKeys[], const char *headerValues[], int headerSize)
  347. {
  348. WiFiClientSecure client;
  349. client.setInsecure(); // Bypass certificate
  350. HTTPClient http;
  351. String payload = "";
  352. http.collectHeaders(headerKeys, headerSize);
  353. if (http.begin(client, url))
  354. {
  355. for (int i = 0; i < headerSize; i++)
  356. {
  357. http.addHeader(headerKeys[i], headerValues[i]);
  358. }
  359. int httpCode = http.GET();
  360. if (httpCode > 0)
  361. {
  362. payload = http.getString();
  363. http.end();
  364. return payload;
  365. }
  366. else
  367. {
  368. SerialPico.print("[ERROR] GET Request Failed, error: ");
  369. SerialPico.println(http.errorToString(httpCode).c_str());
  370. }
  371. http.end();
  372. }
  373. else
  374. {
  375. SerialPico.println("[ERROR] Unable to connect to the server.");
  376. }
  377. // Clear serial buffer to avoid any residual data
  378. this->clearSerialBuffer();
  379. return payload;
  380. }
  381. String FlipperHTTP::delete_request(String url, String payload)
  382. {
  383. WiFiClientSecure client;
  384. client.setInsecure(); // Bypass certificate
  385. HTTPClient http;
  386. String response = "";
  387. if (http.begin(client, url))
  388. {
  389. int httpCode = http.sendRequest("DELETE", payload);
  390. if (httpCode > 0)
  391. {
  392. response = http.getString();
  393. http.end();
  394. return response;
  395. }
  396. else
  397. {
  398. SerialPico.print("[ERROR] DELETE Request Failed, error: ");
  399. SerialPico.println(http.errorToString(httpCode).c_str());
  400. }
  401. http.end();
  402. }
  403. else
  404. {
  405. SerialPico.println("[ERROR] Unable to connect to the server.");
  406. }
  407. // Clear serial buffer to avoid any residual data
  408. this->clearSerialBuffer();
  409. return response;
  410. }
  411. String FlipperHTTP::delete_request(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize)
  412. {
  413. WiFiClientSecure client;
  414. client.setInsecure(); // Bypass certificate
  415. HTTPClient http;
  416. String response = "";
  417. http.collectHeaders(headerKeys, headerSize);
  418. if (http.begin(client, url))
  419. {
  420. for (int i = 0; i < headerSize; i++)
  421. {
  422. http.addHeader(headerKeys[i], headerValues[i]);
  423. }
  424. int httpCode = http.sendRequest("DELETE", payload);
  425. if (httpCode > 0)
  426. {
  427. response = http.getString();
  428. http.end();
  429. return response;
  430. }
  431. else
  432. {
  433. SerialPico.print("[ERROR] DELETE Request Failed, error: ");
  434. SerialPico.println(http.errorToString(httpCode).c_str());
  435. }
  436. http.end();
  437. }
  438. else
  439. {
  440. SerialPico.println("[ERROR] Unable to connect to the server.");
  441. }
  442. // Clear serial buffer to avoid any residual data
  443. this->clearSerialBuffer();
  444. return response;
  445. }
  446. String FlipperHTTP::post(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize)
  447. {
  448. WiFiClientSecure client;
  449. client.setInsecure(); // Bypass certificate
  450. HTTPClient http;
  451. String response = "";
  452. http.collectHeaders(headerKeys, headerSize);
  453. if (http.begin(client, url))
  454. {
  455. for (int i = 0; i < headerSize; i++)
  456. {
  457. http.addHeader(headerKeys[i], headerValues[i]);
  458. }
  459. int httpCode = http.POST(payload);
  460. if (httpCode > 0)
  461. {
  462. response = http.getString();
  463. http.end();
  464. return response;
  465. }
  466. else
  467. {
  468. SerialPico.print("[ERROR] POST Request Failed, error: ");
  469. SerialPico.println(http.errorToString(httpCode).c_str());
  470. }
  471. http.end();
  472. }
  473. else
  474. {
  475. SerialPico.println("[ERROR] Unable to connect to the server.");
  476. }
  477. // Clear serial buffer to avoid any residual data
  478. this->clearSerialBuffer();
  479. return response;
  480. }
  481. String FlipperHTTP::post(String url, String payload)
  482. {
  483. WiFiClientSecure client;
  484. client.setInsecure(); // Bypass certificate
  485. HTTPClient http;
  486. String response = "";
  487. if (http.begin(client, url))
  488. {
  489. int httpCode = http.POST(payload);
  490. if (httpCode > 0)
  491. {
  492. response = http.getString();
  493. http.end();
  494. return response;
  495. }
  496. else
  497. {
  498. SerialPico.print("[ERROR] POST Request Failed, error: ");
  499. SerialPico.println(http.errorToString(httpCode).c_str());
  500. }
  501. http.end();
  502. }
  503. else
  504. {
  505. SerialPico.println("[ERROR] Unable to connect to the server.");
  506. }
  507. // Clear serial buffer to avoid any residual data
  508. this->clearSerialBuffer();
  509. return response;
  510. }
  511. String FlipperHTTP::put(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize)
  512. {
  513. WiFiClientSecure client;
  514. client.setInsecure(); // Bypass certificate
  515. HTTPClient http;
  516. String response = "";
  517. http.collectHeaders(headerKeys, headerSize);
  518. if (http.begin(client, url))
  519. {
  520. for (int i = 0; i < headerSize; i++)
  521. {
  522. http.addHeader(headerKeys[i], headerValues[i]);
  523. }
  524. int httpCode = http.PUT(payload);
  525. if (httpCode > 0)
  526. {
  527. response = http.getString();
  528. http.end();
  529. return response;
  530. }
  531. else
  532. {
  533. SerialPico.print("[ERROR] PUT Request Failed, error: ");
  534. SerialPico.println(http.errorToString(httpCode).c_str());
  535. }
  536. http.end();
  537. }
  538. else
  539. {
  540. SerialPico.println("[ERROR] Unable to connect to the server.");
  541. }
  542. // Clear serial buffer to avoid any residual data
  543. this->clearSerialBuffer();
  544. return response;
  545. }
  546. String FlipperHTTP::put(String url, String payload)
  547. {
  548. WiFiClientSecure client;
  549. client.setInsecure(); // Bypass certificate
  550. HTTPClient http;
  551. String response = "";
  552. if (http.begin(client, url))
  553. {
  554. int httpCode = http.PUT(payload);
  555. if (httpCode > 0)
  556. {
  557. response = http.getString();
  558. http.end();
  559. return response;
  560. }
  561. else
  562. {
  563. SerialPico.print("[ERROR] PUT Request Failed, error: ");
  564. SerialPico.println(http.errorToString(httpCode).c_str());
  565. }
  566. http.end();
  567. }
  568. else
  569. {
  570. SerialPico.println("[ERROR] Unable to connect to the server.");
  571. }
  572. // Clear serial buffer to avoid any residual data
  573. this->clearSerialBuffer();
  574. return response;
  575. }
  576. bool FlipperHTTP::get_bytes_to_file(String url, const char *headerKeys[], const char *headerValues[], int headerSize)
  577. {
  578. WiFiClientSecure client;
  579. client.setInsecure(); // Bypass certificate
  580. HTTPClient http;
  581. http.collectHeaders(headerKeys, headerSize);
  582. if (http.begin(client, url))
  583. {
  584. for (int i = 0; i < headerSize; i++)
  585. {
  586. http.addHeader(headerKeys[i], headerValues[i]);
  587. }
  588. int httpCode = http.GET();
  589. if (httpCode > 0)
  590. {
  591. SerialPico.println("[GET/SUCCESS]");
  592. int len = http.getSize();
  593. uint8_t buff[512] = {0};
  594. WiFiClient *stream = http.getStreamPtr();
  595. // Check available heap memory before starting
  596. // size_t freeHeap = ESP.getFreeHeap();
  597. // const size_t minHeapThreshold = 1024; // Minimum heap space to avoid overflow
  598. // if (freeHeap < minHeapThreshold)
  599. // {
  600. // SerialPico.println("[ERROR] Not enough memory to start processing the response.");
  601. // http.end();
  602. // return false;
  603. // }
  604. // Stream data while connected and available
  605. while (http.connected() && (len > 0 || len == -1))
  606. {
  607. size_t size = stream->available();
  608. if (size)
  609. {
  610. int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
  611. SerialPico.write(buff, c); // Write data to serial
  612. if (len > 0)
  613. {
  614. len -= c;
  615. }
  616. }
  617. delay(1); // Yield control to the system
  618. }
  619. // freeHeap = ESP.getFreeHeap();
  620. // if (freeHeap < minHeapThreshold)
  621. // {
  622. // SerialPico.println("[ERROR] Not enough memory to continue processing the response.");
  623. // http.end();
  624. // return false;
  625. // }
  626. // Flush the serial buffer to ensure all data is sent
  627. http.end();
  628. SerialPico.flush();
  629. SerialPico.println();
  630. SerialPico.println("[GET/END]");
  631. return true;
  632. }
  633. else
  634. {
  635. SerialPico.printf("[ERROR] GET request failed with error: %s\n", http.errorToString(httpCode).c_str());
  636. }
  637. http.end();
  638. }
  639. else
  640. {
  641. SerialPico.println("[ERROR] Unable to connect to the server.");
  642. }
  643. return false;
  644. }
  645. bool FlipperHTTP::post_bytes_to_file(String url, String payload, const char *headerKeys[], const char *headerValues[], int headerSize)
  646. {
  647. WiFiClientSecure client;
  648. client.setInsecure(); // Bypass certificate
  649. HTTPClient http;
  650. http.collectHeaders(headerKeys, headerSize);
  651. if (http.begin(client, url))
  652. {
  653. for (int i = 0; i < headerSize; i++)
  654. {
  655. http.addHeader(headerKeys[i], headerValues[i]);
  656. }
  657. int httpCode = http.POST(payload);
  658. if (httpCode > 0)
  659. {
  660. SerialPico.println("[POST/SUCCESS]");
  661. int len = http.getSize(); // Get the response content length
  662. uint8_t buff[512] = {0}; // Buffer for reading data
  663. WiFiClient *stream = http.getStreamPtr();
  664. // Check available heap memory before starting
  665. // size_t freeHeap = ESP.getFreeHeap();
  666. // const size_t minHeapThreshold = 1024; // Minimum heap space to avoid overflow
  667. // if (freeHeap < minHeapThreshold)
  668. // {
  669. // SerialPico.println("[ERROR] Not enough memory to start processing the response.");
  670. // http.end();
  671. // return false;
  672. // }
  673. // Stream data while connected and available
  674. while (http.connected() && (len > 0 || len == -1))
  675. {
  676. size_t size = stream->available();
  677. if (size)
  678. {
  679. int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
  680. SerialPico.write(buff, c); // Write data to serial
  681. if (len > 0)
  682. {
  683. len -= c;
  684. }
  685. }
  686. delay(1); // Yield control to the system
  687. }
  688. // freeHeap = ESP.getFreeHeap();
  689. // if (freeHeap < minHeapThreshold)
  690. // {
  691. // SerialPico.println("[ERROR] Not enough memory to continue processing the response.");
  692. // http.end();
  693. // return false;
  694. // }
  695. http.end();
  696. // Flush the serial buffer to ensure all data is sent
  697. SerialPico.flush();
  698. SerialPico.println();
  699. SerialPico.println("[POST/END]");
  700. return true;
  701. }
  702. else
  703. {
  704. SerialPico.printf("[ERROR] POST request failed with error: %s\n", http.errorToString(httpCode).c_str());
  705. }
  706. http.end();
  707. }
  708. else
  709. {
  710. SerialPico.println("[ERROR] Unable to connect to the server.");
  711. }
  712. return false;
  713. }
  714. // Main loop for flipper-http.ino that handles all of the commands
  715. void FlipperHTTP::loop()
  716. {
  717. // Check if there's incoming serial data
  718. if (SerialPico.available() > 0)
  719. {
  720. // Read the incoming serial data until newline
  721. String _data = this->readSerialLine();
  722. this->ledStatus();
  723. // print the available commands
  724. if (_data.startsWith("[LIST]"))
  725. {
  726. SerialPico.println("[LIST],[PING], [REBOOT], [WIFI/IP], [WIFI/SCAN], [WIFI/SAVE], [WIFI/CONNECT], [WIFI/DISCONNECT], [WIFI/LIST], [GET], [GET/HTTP], [POST/HTTP], [PUT/HTTP], [DELETE/HTTP], [GET/BYTES], [POST/BYTES], [PARSE], [PARSE/ARRAY], [LED/ON], [LED/OFF], [IP/ADDRESS]");
  727. }
  728. // handle [LED/ON] command
  729. else if (_data.startsWith("[LED/ON]"))
  730. {
  731. this->useLED = true;
  732. }
  733. // handle [LED/OFF] command
  734. else if (_data.startsWith("[LED/OFF]"))
  735. {
  736. this->useLED = false;
  737. }
  738. // handle [IP/ADDRESS] command (local IP)
  739. else if (_data.startsWith("[IP/ADDRESS]"))
  740. {
  741. SerialPico.println(this->getIPAddress());
  742. }
  743. // handle [WIFI/IP] command ip of connected wifi
  744. else if (_data.startsWith("[WIFI/IP]"))
  745. {
  746. if (!this->isConnectedToWifi() && !this->connectToWifi())
  747. {
  748. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  749. this->ledOff();
  750. return;
  751. }
  752. // Get Request
  753. String jsonData = this->get("https://httpbin.org/get");
  754. if (jsonData == "")
  755. {
  756. SerialPico.println("[ERROR] GET request failed or returned empty data.");
  757. return;
  758. }
  759. DynamicJsonDocument doc(1024);
  760. DeserializationError error = deserializeJson(doc, jsonData);
  761. if (error)
  762. {
  763. SerialPico.print("[ERROR] Failed to parse JSON.");
  764. this->ledOff();
  765. return;
  766. }
  767. if (!doc.containsKey("origin"))
  768. {
  769. SerialPico.println("[ERROR] JSON does not contain origin.");
  770. this->ledOff();
  771. return;
  772. }
  773. SerialPico.println(doc["origin"].as<String>());
  774. }
  775. // Ping/Pong to see if board/flipper is connected
  776. else if (_data.startsWith("[PING]"))
  777. {
  778. SerialPico.println("[PONG]");
  779. }
  780. // Handle [REBOOT] command
  781. else if (_data.startsWith("[REBOOT]"))
  782. {
  783. this->useLED = true;
  784. // ESP.restart();
  785. }
  786. // scan for wifi networks
  787. else if (_data.startsWith("[WIFI/SCAN]"))
  788. {
  789. SerialPico.println(this->scanWifiNetworks());
  790. SerialPico.flush();
  791. }
  792. // Handle Wifi list command
  793. else if (_data.startsWith("[WIFI/LIST]"))
  794. {
  795. File file = LittleFS.open(settingsFilePath, "r");
  796. if (!file)
  797. {
  798. SerialPico.println("[ERROR] Failed to open file for reading.");
  799. return;
  800. }
  801. String fileContent = file.readString();
  802. file.close();
  803. SerialPico.println(fileContent);
  804. SerialPico.flush();
  805. }
  806. // Handle [WIFI/SAVE] command
  807. else if (_data.startsWith("[WIFI/SAVE]"))
  808. {
  809. // Extract JSON data by removing the command part
  810. String jsonData = _data.substring(strlen("[WIFI/SAVE]"));
  811. jsonData.trim(); // Remove any leading/trailing whitespace
  812. // Parse and save the settings
  813. if (this->readSerialSettings(jsonData, true))
  814. {
  815. SerialPico.println("[SUCCESS] Wifi settings saved.");
  816. }
  817. else
  818. {
  819. SerialPico.println("[ERROR] Failed to save Wifi settings.");
  820. }
  821. }
  822. // Handle [WIFI/CONNECT] command
  823. else if (_data == "[WIFI/CONNECT]")
  824. {
  825. // Check if WiFi is already connected
  826. if (!this->isConnectedToWifi())
  827. {
  828. // Attempt to connect to Wifi
  829. if (this->connectToWifi())
  830. {
  831. SerialPico.println("[SUCCESS] Connected to Wifi.");
  832. }
  833. else
  834. {
  835. SerialPico.println("[ERROR] Failed to connect to Wifi.");
  836. }
  837. }
  838. else
  839. {
  840. SerialPico.println("[INFO] Already connected to Wifi.");
  841. }
  842. }
  843. // Handle [WIFI/DISCONNECT] command
  844. else if (_data == "[WIFI/DISCONNECT]")
  845. {
  846. WiFi.disconnect(true);
  847. SerialPico.println("[DISCONNECTED] Wifi has been disconnected.");
  848. }
  849. // Handle [GET] command
  850. else if (_data.startsWith("[GET]"))
  851. {
  852. if (!this->isConnectedToWifi() && !this->connectToWifi())
  853. {
  854. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  855. this->ledOff();
  856. return;
  857. }
  858. // Extract URL by removing the command part
  859. String url = _data.substring(strlen("[GET]"));
  860. url.trim();
  861. // GET request
  862. String getData = this->get(url);
  863. if (getData != "")
  864. {
  865. SerialPico.println("[GET/SUCCESS] GET request successful.");
  866. SerialPico.println(getData);
  867. SerialPico.flush();
  868. SerialPico.println();
  869. SerialPico.println("[GET/END]");
  870. }
  871. else
  872. {
  873. SerialPico.println("[ERROR] GET request failed or returned empty data.");
  874. }
  875. }
  876. // Handle [GET/HTTP] command
  877. else if (_data.startsWith("[GET/HTTP]"))
  878. {
  879. if (!this->isConnectedToWifi() && !this->connectToWifi())
  880. {
  881. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  882. this->ledOff();
  883. return;
  884. }
  885. // Extract the JSON by removing the command part
  886. String jsonData = _data.substring(strlen("[GET/HTTP]"));
  887. jsonData.trim();
  888. DynamicJsonDocument doc(1024);
  889. DeserializationError error = deserializeJson(doc, jsonData);
  890. if (error)
  891. {
  892. SerialPico.print("[ERROR] Failed to parse JSON.");
  893. this->ledOff();
  894. return;
  895. }
  896. // Extract values from JSON
  897. if (!doc.containsKey("url"))
  898. {
  899. SerialPico.println("[ERROR] JSON does not contain url.");
  900. this->ledOff();
  901. return;
  902. }
  903. String url = doc["url"];
  904. // Extract headers if available
  905. const char *headerKeys[10];
  906. const char *headerValues[10];
  907. int headerSize = 0;
  908. if (doc.containsKey("headers"))
  909. {
  910. JsonObject headers = doc["headers"];
  911. for (JsonPair header : headers)
  912. {
  913. headerKeys[headerSize] = header.key().c_str();
  914. headerValues[headerSize] = header.value();
  915. headerSize++;
  916. }
  917. }
  918. // GET request
  919. String getData = this->get(url, headerKeys, headerValues, headerSize);
  920. if (getData != "")
  921. {
  922. SerialPico.println("[GET/SUCCESS] GET request successful.");
  923. SerialPico.println(getData);
  924. SerialPico.flush();
  925. SerialPico.println();
  926. SerialPico.println("[GET/END]");
  927. }
  928. else
  929. {
  930. SerialPico.println("[ERROR] GET request failed or returned empty data.");
  931. }
  932. }
  933. // Handle [POST/HTTP] command
  934. else if (_data.startsWith("[POST/HTTP]"))
  935. {
  936. if (!this->isConnectedToWifi() && !this->connectToWifi())
  937. {
  938. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  939. this->ledOff();
  940. return;
  941. }
  942. // Extract the JSON by removing the command part
  943. String jsonData = _data.substring(strlen("[POST/HTTP]"));
  944. jsonData.trim();
  945. DynamicJsonDocument doc(1024);
  946. DeserializationError error = deserializeJson(doc, jsonData);
  947. if (error)
  948. {
  949. SerialPico.print("[ERROR] Failed to parse JSON.");
  950. this->ledOff();
  951. return;
  952. }
  953. // Extract values from JSON
  954. if (!doc.containsKey("url") || !doc.containsKey("payload"))
  955. {
  956. SerialPico.println("[ERROR] JSON does not contain url or payload.");
  957. this->ledOff();
  958. return;
  959. }
  960. String url = doc["url"];
  961. String payload = doc["payload"];
  962. // Extract headers if available
  963. const char *headerKeys[10];
  964. const char *headerValues[10];
  965. int headerSize = 0;
  966. if (doc.containsKey("headers"))
  967. {
  968. JsonObject headers = doc["headers"];
  969. for (JsonPair header : headers)
  970. {
  971. headerKeys[headerSize] = header.key().c_str();
  972. headerValues[headerSize] = header.value();
  973. headerSize++;
  974. }
  975. }
  976. // POST request
  977. String postData = this->post(url, payload, headerKeys, headerValues, headerSize);
  978. if (postData != "")
  979. {
  980. SerialPico.println("[POST/SUCCESS] POST request successful.");
  981. SerialPico.println(postData);
  982. SerialPico.flush();
  983. SerialPico.println();
  984. SerialPico.println("[POST/END]");
  985. }
  986. else
  987. {
  988. SerialPico.println("[ERROR] POST request failed or returned empty data.");
  989. }
  990. }
  991. // Handle [PUT/HTTP] command
  992. else if (_data.startsWith("[PUT/HTTP]"))
  993. {
  994. if (!this->isConnectedToWifi() && !this->connectToWifi())
  995. {
  996. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  997. this->ledOff();
  998. return;
  999. }
  1000. // Extract the JSON by removing the command part
  1001. String jsonData = _data.substring(strlen("[PUT/HTTP]"));
  1002. jsonData.trim();
  1003. DynamicJsonDocument doc(1024);
  1004. DeserializationError error = deserializeJson(doc, jsonData);
  1005. if (error)
  1006. {
  1007. SerialPico.print("[ERROR] Failed to parse JSON.");
  1008. this->ledOff();
  1009. return;
  1010. }
  1011. // Extract values from JSON
  1012. if (!doc.containsKey("url") || !doc.containsKey("payload"))
  1013. {
  1014. SerialPico.println("[ERROR] JSON does not contain url or payload.");
  1015. this->ledOff();
  1016. return;
  1017. }
  1018. String url = doc["url"];
  1019. String payload = doc["payload"];
  1020. // Extract headers if available
  1021. const char *headerKeys[10];
  1022. const char *headerValues[10];
  1023. int headerSize = 0;
  1024. if (doc.containsKey("headers"))
  1025. {
  1026. JsonObject headers = doc["headers"];
  1027. for (JsonPair header : headers)
  1028. {
  1029. headerKeys[headerSize] = header.key().c_str();
  1030. headerValues[headerSize] = header.value();
  1031. headerSize++;
  1032. }
  1033. }
  1034. // PUT request
  1035. String putData = this->put(url, payload, headerKeys, headerValues, headerSize);
  1036. if (putData != "")
  1037. {
  1038. SerialPico.println("[PUT/SUCCESS] PUT request successful.");
  1039. SerialPico.println(putData);
  1040. SerialPico.flush();
  1041. SerialPico.println();
  1042. SerialPico.println("[PUT/END]");
  1043. }
  1044. else
  1045. {
  1046. SerialPico.println("[ERROR] PUT request failed or returned empty data.");
  1047. }
  1048. }
  1049. // Handle [DELETE/HTTP] command
  1050. else if (_data.startsWith("[DELETE/HTTP]"))
  1051. {
  1052. if (!this->isConnectedToWifi() && !this->connectToWifi())
  1053. {
  1054. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  1055. this->ledOff();
  1056. return;
  1057. }
  1058. // Extract the JSON by removing the command part
  1059. String jsonData = _data.substring(strlen("[DELETE/HTTP]"));
  1060. jsonData.trim();
  1061. DynamicJsonDocument doc(1024);
  1062. DeserializationError error = deserializeJson(doc, jsonData);
  1063. if (error)
  1064. {
  1065. SerialPico.print("[ERROR] Failed to parse JSON.");
  1066. this->ledOff();
  1067. return;
  1068. }
  1069. // Extract values from JSON
  1070. if (!doc.containsKey("url") || !doc.containsKey("payload"))
  1071. {
  1072. SerialPico.println("[ERROR] JSON does not contain url or payload.");
  1073. this->ledOff();
  1074. return;
  1075. }
  1076. String url = doc["url"];
  1077. String payload = doc["payload"];
  1078. // Extract headers if available
  1079. const char *headerKeys[10];
  1080. const char *headerValues[10];
  1081. int headerSize = 0;
  1082. if (doc.containsKey("headers"))
  1083. {
  1084. JsonObject headers = doc["headers"];
  1085. for (JsonPair header : headers)
  1086. {
  1087. headerKeys[headerSize] = header.key().c_str();
  1088. headerValues[headerSize] = header.value();
  1089. headerSize++;
  1090. }
  1091. }
  1092. // DELETE request
  1093. String deleteData = this->delete_request(url, payload, headerKeys, headerValues, headerSize);
  1094. if (deleteData != "")
  1095. {
  1096. SerialPico.println("[DELETE/SUCCESS] DELETE request successful.");
  1097. SerialPico.println(deleteData);
  1098. SerialPico.flush();
  1099. SerialPico.println();
  1100. SerialPico.println("[DELETE/END]");
  1101. }
  1102. else
  1103. {
  1104. SerialPico.println("[ERROR] DELETE request failed or returned empty data.");
  1105. }
  1106. }
  1107. // Handle [GET/BYTES]
  1108. else if (_data.startsWith("[GET/BYTES]"))
  1109. {
  1110. if (!this->isConnectedToWifi() && !this->connectToWifi())
  1111. {
  1112. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  1113. this->ledOff();
  1114. return;
  1115. }
  1116. // Extract the JSON by removing the command part
  1117. String jsonData = _data.substring(strlen("[GET/BYTES]"));
  1118. jsonData.trim();
  1119. DynamicJsonDocument doc(1024);
  1120. DeserializationError error = deserializeJson(doc, jsonData);
  1121. if (error)
  1122. {
  1123. SerialPico.print("[ERROR] Failed to parse JSON.");
  1124. this->ledOff();
  1125. return;
  1126. }
  1127. // Extract values from JSON
  1128. if (!doc.containsKey("url"))
  1129. {
  1130. SerialPico.println("[ERROR] JSON does not contain url.");
  1131. this->ledOff();
  1132. return;
  1133. }
  1134. String url = doc["url"];
  1135. // Extract headers if available
  1136. const char *headerKeys[10];
  1137. const char *headerValues[10];
  1138. int headerSize = 0;
  1139. if (doc.containsKey("headers"))
  1140. {
  1141. JsonObject headers = doc["headers"];
  1142. for (JsonPair header : headers)
  1143. {
  1144. headerKeys[headerSize] = header.key().c_str();
  1145. headerValues[headerSize] = header.value();
  1146. headerSize++;
  1147. }
  1148. }
  1149. // GET request
  1150. if (!this->get_bytes_to_file(url, headerKeys, headerValues, headerSize))
  1151. {
  1152. SerialPico.println("[ERROR] GET request failed or returned empty data.");
  1153. }
  1154. }
  1155. // handle [POST/BYTES]
  1156. else if (_data.startsWith("[POST/BYTES]"))
  1157. {
  1158. if (!this->isConnectedToWifi() && !this->connectToWifi())
  1159. {
  1160. SerialPico.println("[ERROR] Not connected to Wifi. Failed to reconnect.");
  1161. this->ledOff();
  1162. return;
  1163. }
  1164. // Extract the JSON by removing the command part
  1165. String jsonData = _data.substring(strlen("[POST/BYTES]"));
  1166. jsonData.trim();
  1167. DynamicJsonDocument doc(1024);
  1168. DeserializationError error = deserializeJson(doc, jsonData);
  1169. if (error)
  1170. {
  1171. SerialPico.print("[ERROR] Failed to parse JSON.");
  1172. this->ledOff();
  1173. return;
  1174. }
  1175. // Extract values from JSON
  1176. if (!doc.containsKey("url") || !doc.containsKey("payload"))
  1177. {
  1178. SerialPico.println("[ERROR] JSON does not contain url or payload.");
  1179. this->ledOff();
  1180. return;
  1181. }
  1182. String url = doc["url"];
  1183. String payload = doc["payload"];
  1184. // Extract headers if available
  1185. const char *headerKeys[10];
  1186. const char *headerValues[10];
  1187. int headerSize = 0;
  1188. if (doc.containsKey("headers"))
  1189. {
  1190. JsonObject headers = doc["headers"];
  1191. for (JsonPair header : headers)
  1192. {
  1193. headerKeys[headerSize] = header.key().c_str();
  1194. headerValues[headerSize] = header.value();
  1195. headerSize++;
  1196. }
  1197. }
  1198. // POST request
  1199. if (!this->post_bytes_to_file(url, payload, headerKeys, headerValues, headerSize))
  1200. {
  1201. SerialPico.println("[ERROR] POST request failed or returned empty data.");
  1202. }
  1203. }
  1204. // Handle [PARSE] command
  1205. // the user will append the key to read from the json
  1206. // example: [PARSE]{"key":"name","json":{"name":"John Doe"}}
  1207. else if (_data.startsWith("[PARSE]"))
  1208. {
  1209. // Extract the JSON by removing the command part
  1210. String jsonData = _data.substring(strlen("[PARSE]"));
  1211. jsonData.trim();
  1212. DynamicJsonDocument doc(1024);
  1213. DeserializationError error = deserializeJson(doc, jsonData);
  1214. if (error)
  1215. {
  1216. SerialPico.print("[ERROR] Failed to parse JSON.");
  1217. this->ledOff();
  1218. return;
  1219. }
  1220. // Extract values from JSON
  1221. if (!doc.containsKey("key") || !doc.containsKey("json"))
  1222. {
  1223. SerialPico.println("[ERROR] JSON does not contain key or json.");
  1224. this->ledOff();
  1225. return;
  1226. }
  1227. String key = doc["key"];
  1228. JsonObject json = doc["json"];
  1229. if (json.containsKey(key))
  1230. {
  1231. SerialPico.println(json[key].as<String>());
  1232. }
  1233. else
  1234. {
  1235. SerialPico.println("[ERROR] Key not found in JSON.");
  1236. }
  1237. }
  1238. // Handle [PARSE/ARRAY] command
  1239. // the user will append the key to read and the index of the array to get it's key from the json
  1240. // example: [PARSE/ARRAY]{"key":"name","index":"1","json":{"name":["John Doe","Jane Doe"]}}
  1241. // this would return Jane Doe
  1242. // and in this example it would return {"flavor": "red"}:
  1243. // example: [PARSE/ARRAY]{"key":"flavor","index":"1","json":{"name":[{"flavor": "blue"},{"flavor": "red"}]}}
  1244. else if (_data.startsWith("[PARSE/ARRAY]"))
  1245. {
  1246. // Extract the JSON by removing the command part
  1247. String jsonData = _data.substring(strlen("[PARSE/ARRAY]"));
  1248. jsonData.trim();
  1249. DynamicJsonDocument doc(1024);
  1250. DeserializationError error = deserializeJson(doc, jsonData);
  1251. if (error)
  1252. {
  1253. SerialPico.print("[ERROR] Failed to parse JSON.");
  1254. this->ledOff();
  1255. return;
  1256. }
  1257. // Extract values from JSON
  1258. if (!doc.containsKey("key") || !doc.containsKey("index") || !doc.containsKey("json"))
  1259. {
  1260. SerialPico.println("[ERROR] JSON does not contain key, index, or json.");
  1261. this->ledOff();
  1262. return;
  1263. }
  1264. String key = doc["key"];
  1265. int index = doc["index"];
  1266. JsonArray json = doc["json"];
  1267. if (json[index].containsKey(key))
  1268. {
  1269. SerialPico.println(json[index][key].as<String>());
  1270. }
  1271. else
  1272. {
  1273. SerialPico.println("[ERROR] Key not found in JSON.");
  1274. }
  1275. }
  1276. this->ledOff();
  1277. }
  1278. }