FlipperHTTP.h 43 KB

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