New repository for old PyCoffee Machine.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

692 lines
19 KiB

1 month ago
1 month ago
1 month ago
  1. // LAST UPDATE: NOVEMBER 2024
  2. #include <Arduino.h>
  3. #include <ESP8266WiFi.h>
  4. #include <ESPAsyncTCP.h>
  5. #include <ESPAsyncWebServer.h>
  6. #include <AsyncElegantOTA.h>
  7. #include <Wire.h>
  8. #include <LiquidCrystal.h>
  9. #include <EEPROM.h> // to store number of coffees made and pumped water
  10. AsyncWebServer server(80);
  11. // coffee times (in seconds) and temperature (in C) settings:
  12. // Warning! these are currently hard-coded in the arduino nano
  13. //int rist = 10;
  14. //int espr = 15;
  15. //int lung = 20;
  16. //int amer = 60;
  17. //int desiredTemp = 70;
  18. // support variables used by checktraywater() to interact with EEPROM
  19. int mem0 = 0;
  20. int mem1 = 0;
  21. // This next variable is used to get current "time" in ms and break out of while cycles that need a time limit safeguard
  22. unsigned long startTime;
  23. // WiFi settings
  24. const char* ssid = "LILiK_WiFi"; //replace with your SSID
  25. const char* password = "pippopippo"; //password
  26. String hostname = "PyCoffee";
  27. int wifiStatus = 0; // variable to store status (connected/disconnected, used by LCDprint)
  28. // string to store coffee style from HTML
  29. String input_field = ""; // string for coffee type
  30. String input_field2 = ""; // string for serial commands
  31. // HTML WEB interface
  32. const char index_html[] PROGMEM = R"rawliteral(
  33. <!DOCTYPE HTML><html><head>
  34. <title>PyCoffee HTML interface</title>
  35. <meta name="viewport" content="width=device-width, initial-scale=1">
  36. <style>
  37. html {display: inline-block; text-align: center;}
  38. </style>
  39. </head><body>
  40. <a>Ciao! Sono la macchina del caffe' della 117bis!</a><br>
  41. <a>Seleziona il tipo di caffe' e conferma</a><br><br>
  42. <form action="/get">
  43. <select name="coffee_input">
  44. <option value="Ristretto">Ristretto</option>
  45. <option value="Espresso">Espresso</option>
  46. <option value="Lungo">Lungo</option>
  47. <option value="Americano">Americano</option>
  48. </select><br><br>
  49. <input type="submit" value="Conferma">
  50. </form>
  51. <br><br>
  52. <a>Puoi mandare comandi a Arduino via seriale</a><br>
  53. <a>usa 'exit' per uscire dall'ultimo comando seriale</a><br><br>
  54. <form action="/serial">
  55. <select name="serial_input">
  56. <option value="exit">Exit Command</option>
  57. <option value="dryr">PerformDryRun</option>
  58. <option value="noco">NoCoffeeRun</option>
  59. <option value="clea">Self-Clean</option>
  60. <option value="make">Make Coffee</option>
  61. <option value="pump">pumpWater</option>
  62. <option value="pres">Press</option>
  63. <option value="unpr">un-Press</option>
  64. <option value="f-pr">Force-Press</option>
  65. <option value="f-un">Force-unpress</option>
  66. <option value="heat">Heat Boiler</option>
  67. <option value="grin">GrindPowder</option>
  68. <option value="drop">Drop Powder</option>
  69. <option value="rist">setRistretto-15s</option>
  70. <option value="espr">setEspresso-20s</option>
  71. <option value="long">setLungo-25s</option>
  72. <option value="amer">setAmericano-60s</option>
  73. </select><br><br>
  74. <input type="submit" value="Invia Comando">
  75. </form>
  76. <br><br>
  77. <a>Consulta la pagina Gitea per maggiori informazioni</a><br><br>
  78. <a>Aggiornamento firmware:</a> <br>
  79. <a href='/update'>&#11014;&#65039; OTA Firmware_Update &#11014;&#65039;</a><br><br>
  80. <a>Codice sorgente (su LILiK projects):</a> <br>
  81. <a href='https://projects.lilik.it/LILiK/PyCoffee_ESP8266'>&#9749;Gitea&#9749;</a><br><br>
  82. </body></html>)rawliteral";
  83. // input parameter for html server
  84. const char* input_paramter1 = "coffee_input";
  85. const char* input_paramter2 = "serial_input";
  86. // enable http coffee making on server request
  87. int httpCoffee = 0;
  88. int serialCommand = 0;
  89. // button pins
  90. const int sel = 0, enter = 2;
  91. // Global string, allows serial in
  92. String readString;
  93. // coffee type variable
  94. String type = "Espresso";
  95. // number of coffees made and seconds of pumped water since last tray empty/water refill
  96. // used by checkTrayWater() function
  97. int coffeesDone = 0;
  98. int pumpedS = 0;
  99. const int MaxCoffees = 10;
  100. const int MaxPumped = 500;
  101. // Support string to store stuff to be sent over serial
  102. String serialOUTbuffer = "none";
  103. // Variables to store lines to be printed on LCD:
  104. String line1 = "";
  105. String line2 = "";
  106. // only refresh LCD when needed
  107. int printed = 0;
  108. // LCD pins <--> ESP8266 LOLIN D1 pins
  109. const int e_RS = 4, e_EN = 5, e_D6 = 12, e_D7 = 13, e_D5 = 14, e_D8 = 15;
  110. LiquidCrystal lcd(e_RS, e_EN, e_D6, e_D7, e_D5, e_D8);
  111. // Custom character matrix (coffee cup left side)
  112. byte cupL[8] = {0b00001,0b00010,0b00010,0b00001,0b00001,0b00000,0b00111,0b00100};
  113. // Custom character matrix (coffee cup center)
  114. byte cupC[8] = {0b00100,0b00101,0b01001,0b01000,0b00100,0b00000,0b11111,0b00000};
  115. // Custom character matrix (coffee cup right side)
  116. byte cupR[8] = {0b10000,0b00000,0b00000,0b10000,0b10000,0b00000,0b11000,0b01110};
  117. // Custom character matrix (coffee cup left side line 2)
  118. byte cupL1[8] = {0b00100,0b00100,0b00100,0b00010,0b01111,0b01000,0b00111,0b00000};
  119. // Custom character matrix (coffee cup center line 2)
  120. byte cupC1[8] = {0b00000,0b00000,0b00000,0b00000,0b11111,0b00000,0b11111,0b00000};
  121. // Custom character matrix (coffee cup right side line 2)
  122. byte cupR1[8] = {0b01010,0b01010,0b01110,0b10000,0b11100,0b00100,0b11000,0b00000};
  123. // Custom character matrix (WiFi connected/disconnected)
  124. byte wifiok[8] = {0b00000,0b01110,0b10001,0b00100,0b01010,0b00000,0b00000,0b00100};
  125. byte wifino[8] = {0b00000,0b10001,0b01010,0b00100,0b01010,0b10001,0b00000,0b00100};
  126. void notFound(AsyncWebServerRequest *request) {
  127. request->send(404, "text/plain", "Not found");
  128. }
  129. //_____________________________________________________________________________
  130. // the setup function runs once when you press reset or power the board
  131. void setup(void) {
  132. // check how many coffees were done and seconds of pumped water from EEPROM
  133. mem0 = (EEPROM.read(0));
  134. mem1 = (EEPROM.read(1));
  135. // protect against under/over-flow due to errors or memory location never having been used
  136. if (mem0 < 0 or mem0 > MaxCoffees + 1) {
  137. EEPROM.write(0, 0);
  138. mem0 = 0;
  139. }
  140. if (mem1 < 0 or mem1 > MaxPumped + 50) {
  141. EEPROM.write(1, 0);
  142. mem1 = 0;
  143. }
  144. coffeesDone = mem0;
  145. pumpedS = mem1;
  146. // Set buttons as inputs with internal pull-ups
  147. pinMode(enter, INPUT_PULLUP);
  148. pinMode(sel, INPUT_PULLUP);
  149. // initialize serial:
  150. Serial.begin(9600);
  151. // Make custom characters available to LCD to paint a cute coffee cup
  152. lcd.createChar(0, cupL);
  153. lcd.createChar(1, cupC);
  154. lcd.createChar(2, cupR);
  155. lcd.createChar(3, cupL1);
  156. lcd.createChar(4, cupC1);
  157. lcd.createChar(5, cupR1);
  158. lcd.createChar(6, wifiok);
  159. lcd.createChar(7, wifino);
  160. lcd.begin(16, 2); // set up number of columns and rows
  161. lcd.clear(); // Clear LCD
  162. // Show boot animation on LCD
  163. bootAnimation();
  164. WiFi.mode(WIFI_STA);
  165. // WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
  166. WiFi.setHostname(hostname.c_str()); //define hostname
  167. WiFi.begin(ssid, password);
  168. connectedTo();
  169. // Send web page with input fields to client
  170. server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
  171. request->send(200, "text/html", index_html);
  172. });
  173. // Get data from menu
  174. // coffee
  175. server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
  176. if (request->hasParam(input_paramter1)) {
  177. type = request->getParam(input_paramter1)->value();
  178. httpCoffee = 1;
  179. }
  180. request->send_P(200, "text/html", index_html); // Re-send main page, with data!
  181. });
  182. // serial commands
  183. server.on("/serial", HTTP_GET, [] (AsyncWebServerRequest *request) {
  184. if (request->hasParam(input_paramter2)) {
  185. if (serialOUTbuffer != "") {
  186. serialOUTbuffer = request->getParam(input_paramter2)->value();
  187. serialCommand = 1;
  188. httpCoffee = 0;
  189. }
  190. }
  191. request->send_P(200, "text/html", index_html); // Re-send main page, with data!
  192. });
  193. // in case of error:
  194. server.onNotFound(notFound);
  195. AsyncElegantOTA.begin(&server); // Start ElegantOTA
  196. server.begin();
  197. Serial.println("HTTP server started");
  198. // Print selection screen
  199. printed = 1;
  200. // tell the Arduino we're ready
  201. serialOUTbuffer = "read";
  202. delay(100);
  203. writeSerial();
  204. type = "Espresso";
  205. line1 = "Selection";
  206. line2 = type;
  207. LCDprint();
  208. delay(500);
  209. }
  210. //_____________________________________________________________________________
  211. void loop() {
  212. // the loop function runs over and over again forever
  213. delay(10);
  214. serialFromHTTP();
  215. // select type of brew by pressing the sel button
  216. if (digitalRead(sel) == LOW && type == "Espresso") {
  217. type = "Lungo";
  218. line1 = "Selection";
  219. line2 = type;
  220. LCDprint();
  221. delay(500);
  222. }
  223. else if (digitalRead(sel) == LOW && type == "Lungo") {
  224. type = "Americano";
  225. line1 = "Selection";
  226. line2 = type;
  227. LCDprint();
  228. delay(500);
  229. }
  230. else if (digitalRead(sel) == LOW && type == "Americano") {
  231. type = "Ristretto";
  232. line1 = "Selection";
  233. line2 = type;
  234. LCDprint();
  235. delay(500);
  236. }
  237. else if (digitalRead(sel) == LOW && type == "Ristretto") {
  238. type = "Espresso";
  239. line1 = "Selection";
  240. line2 = type;
  241. LCDprint();
  242. delay(500);
  243. }
  244. else if (Serial.available() > 0) {
  245. // read the incoming data: (we only care about the first few characters, I've chosen 4)
  246. readString = "";
  247. serialRead();
  248. String incomingData = readString.substring(0,4);
  249. // are we heating the boiler to vapor temperature?
  250. if (incomingData == "Vapo") {
  251. line1 = "Heating";
  252. line2 = "Vapor";
  253. LCDprint();
  254. delay(1000);
  255. }
  256. if (incomingData == "reac") {
  257. line1 = "Vapor";
  258. line2 = "ok";
  259. LCDprint();
  260. delay(1000);
  261. printed = 1;
  262. delay(10000);
  263. serialOUTbuffer = "unpr";
  264. writeSerial();
  265. delay(100);
  266. }
  267. }
  268. refreshMenu();
  269. delay(100);
  270. // check if data has been sent on serial from Arduino,
  271. // send commands to arduino and display corresponding messages on LCD
  272. // this is the main function dedicated to making coffee and reporting errors.
  273. serialCoffeeCommandLCDInterface();
  274. }
  275. //_____________________________________________________________________________
  276. // this is how we read the input
  277. void serialRead() {
  278. while (Serial.available()) {
  279. delay(1);
  280. if (Serial.available() > 0) {
  281. char c = Serial.read();
  282. readString += c;}
  283. }
  284. }
  285. //_____________________________________________________________________________
  286. void bootAnimation() {
  287. delay(1000);
  288. line1 = "ESP_Coffee";
  289. line2 = "Release 1.1e";
  290. LCDprint();
  291. delay(1000);
  292. line1 = "JIMBO";
  293. line2 = "";
  294. LCDprint();
  295. delay(1000);
  296. lcd.setCursor(11,0); //print coffee cup top using uint8 method
  297. lcd.write((uint8_t)0);
  298. delay(10);
  299. lcd.write((uint8_t)1);
  300. delay(10);
  301. lcd.write((uint8_t)2);
  302. delay(10);
  303. lcd.setCursor(11,1); //print coffee cup bottom using uint8 method
  304. lcd.write((uint8_t)3);
  305. delay(10);
  306. lcd.write((uint8_t)4);
  307. delay(10);
  308. lcd.write((uint8_t)5);
  309. delay(2000);
  310. }
  311. //_____________________________________________________________________________
  312. void connectedTo() {
  313. startTime = millis();
  314. while (WiFi.status() != WL_CONNECTED) {
  315. delay(500);
  316. Serial.print(".");
  317. if (millis() - startTime > 60000) {
  318. Serial.println("WiFi connection timeout");
  319. line1 = "Timeout at SSID";
  320. line2 = ssid;
  321. LCDprint();
  322. delay(10000);
  323. break;
  324. }
  325. else {
  326. wifiStatus = 1;
  327. }
  328. }
  329. Serial.println("");
  330. Serial.println("WiFi connected");
  331. Serial.println("IP address: ");
  332. Serial.println(WiFi.localIP()); //You can get IP address assigned to ESP
  333. line1 = "SSID";
  334. line2 = ssid;
  335. LCDprint();
  336. delay(2000);
  337. line1 = "Connected to IP:";
  338. line2 = "";
  339. LCDprint();
  340. delay(10);
  341. lcd.setCursor(0, 1); // move cursor to (0, 1)
  342. lcd.print(WiFi.localIP()); // re-print line2 or IP won't print
  343. delay(2000);
  344. }
  345. //_____________________________________________________________________________
  346. void refreshMenu() {
  347. if (line1 == "Selection" && printed == 1) {
  348. lcd.clear();
  349. lcd.setCursor(0, 0); // move cursor to (0, 0)
  350. lcd.print(line1); // print line1
  351. delay(10);
  352. lcd.setCursor(15, 0); // move cursor to (14, 0)
  353. // print wifi logo depending on connection starus
  354. if (wifiStatus == 1) {
  355. lcd.write((uint8_t)6);
  356. delay(10);
  357. }
  358. else if (wifiStatus == 0) {
  359. lcd.write((uint8_t)7);
  360. delay(10);
  361. }
  362. lcd.setCursor(0, 1); // move cursor to (0, 1)
  363. lcd.print(line2); // print line2
  364. delay(10);
  365. printed = 0;
  366. }
  367. }
  368. //_____________________________________________________________________________
  369. void LCDprint() {
  370. lcd.clear();
  371. lcd.setCursor(0, 0); // move cursor to (0, 0)
  372. lcd.print(line1); // print line1
  373. delay(10);
  374. lcd.setCursor(15, 0); // move cursor to (14, 0)
  375. // print wifi logo depending on connection starus
  376. if (wifiStatus == 1) {
  377. lcd.write((uint8_t)6);
  378. delay(10);
  379. }
  380. else if (wifiStatus == 0) {
  381. lcd.write((uint8_t)7);
  382. delay(10);
  383. }
  384. lcd.setCursor(0, 1); // move cursor to (0, 1)
  385. lcd.print(line2); // print line2
  386. delay(10);
  387. }
  388. //_____________________________________________________________________________
  389. void writeSerial() {
  390. Serial.println(serialOUTbuffer);
  391. }
  392. //_____________________________________________________________________________
  393. void serialFromHTTP() {
  394. if (serialCommand == 1){
  395. serialCommand = 0;
  396. line1 = "serialCom";
  397. line2 = serialOUTbuffer;
  398. LCDprint();
  399. delay(1000);
  400. writeSerial();
  401. while (serialOUTbuffer != "exit" && digitalRead(sel) == HIGH ) {
  402. if (Serial.available() > 0) {
  403. // read the incoming data: (we only care about the first few characters, I've chosen 4)
  404. readString = "";
  405. serialRead();
  406. String incomingData = readString.substring(0,16);
  407. Serial.print("serial put from Arduino:\n");
  408. Serial.print(incomingData);
  409. Serial.print("\n");
  410. if (serialOUTbuffer != "none") {
  411. line1 = serialOUTbuffer;
  412. }
  413. line2 = incomingData;
  414. LCDprint();
  415. delay(100);
  416. serialOUTbuffer = "none";
  417. writeSerial();
  418. }
  419. }
  420. line1 = "Selection";
  421. line2 = type;
  422. LCDprint();
  423. delay(500);
  424. }
  425. }
  426. //_____________________________________________________________________________
  427. void checkTrayWater() {
  428. // section dedicated to empty tray and refill water messages
  429. if (coffeesDone == MaxCoffees) {
  430. lcd.clear();
  431. while (digitalRead(enter) == HIGH) {
  432. line1 = "Empty Tray";
  433. line2 = "Then hold Make";
  434. delay(500);
  435. LCDprint();
  436. }
  437. coffeesDone = 0;
  438. EEPROM.write(0, 0);
  439. line1 = "Tray emptied";
  440. line2 = "continuing...";
  441. delay(1000);
  442. LCDprint();
  443. }
  444. if (pumpedS > MaxPumped) {
  445. lcd.clear();
  446. while (digitalRead(enter) == HIGH) {
  447. line1 = "Refill Water";
  448. line1 = "Then hold Make";
  449. delay(500);
  450. LCDprint();
  451. }
  452. pumpedS = 0;
  453. EEPROM.write(1, 0);
  454. line1 = "Water Refilled";
  455. line2 = "continuing...";
  456. delay(1000);
  457. LCDprint();
  458. }
  459. delay(500);
  460. printed = 1;
  461. EEPROM.commit();
  462. line1 = "Selection";
  463. line2 = type;
  464. }
  465. //_____________________________________________________________________________
  466. void serialCoffeeCommandLCDInterface() {
  467. if (digitalRead(enter) == LOW or httpCoffee == 1) {
  468. httpCoffee = 0;
  469. if (type == "Espresso") {
  470. serialOUTbuffer = "espr";
  471. pumpedS = pumpedS + 20;
  472. }
  473. else if (type == "Lungo") {
  474. serialOUTbuffer = "long";
  475. pumpedS = pumpedS + 25;
  476. }
  477. else if (type == "Ristretto") {
  478. serialOUTbuffer = "rist";
  479. pumpedS = pumpedS + 15;
  480. }
  481. else if (type == "Americano") {
  482. serialOUTbuffer = "amer";
  483. pumpedS = pumpedS + 60;
  484. }
  485. writeSerial();
  486. delay(500);
  487. serialOUTbuffer = "make";
  488. writeSerial();
  489. }
  490. if (Serial.available() > 0) {
  491. // read the incoming data: (we only care about the first few characters, I've chosen 4)
  492. readString = "";
  493. serialRead();
  494. String incomingData = readString.substring(0,4);
  495. Serial.print(incomingData);
  496. Serial.print("\n");
  497. if (incomingData == "Maki") {
  498. // Display making coffee message
  499. line1 = "Making";
  500. line2 = type;
  501. LCDprint();
  502. delay(10);
  503. lcd.setCursor(11,0); //print coffee cup top using uint8 method
  504. lcd.write((uint8_t)0);
  505. delay(10);
  506. lcd.write((uint8_t)1);
  507. delay(10);
  508. lcd.write((uint8_t)2);
  509. delay(10);
  510. lcd.setCursor(11,1); //print coffee cup bottom using uint8 method
  511. lcd.write((uint8_t)3);
  512. delay(10);
  513. lcd.write((uint8_t)4);
  514. delay(10);
  515. lcd.write((uint8_t)5);
  516. delay(10);
  517. }
  518. if (incomingData == "u-Th") {
  519. Serial.print("Thermocouple: unplugged or failed\n");
  520. while (true) {
  521. // Unrecoverable error has occurred during heating, lock the machine
  522. line1 = "ThermoCouple err";
  523. line2 = "Unplugged/failed";
  524. LCDprint();
  525. delay(10000);
  526. }
  527. }
  528. if (incomingData == "p-Th") {
  529. Serial.print("Thermocouple: positioning or relay fault\n");
  530. while (true) {
  531. // Unrecoverable error has occurred during heating, lock the machine
  532. line1 = "ThermoCouple err";
  533. line2 = "Fallen/relayFail";
  534. LCDprint();
  535. delay(10000);
  536. }
  537. }
  538. if (incomingData == "Erro") {
  539. Serial.print("Unrecoverable error in Arduino subsystem. Please restart\n");
  540. while (true) {
  541. // Unrecoverable error has occurred during heating, lock the machine
  542. line1 = "Enrecoverable";
  543. line2 = "Error. Restart.";
  544. LCDprint();
  545. delay(10000);
  546. }
  547. }
  548. if (incomingData == "h-ta") {
  549. Serial.print("took too long to warm boiler, this is just a warning. Everything should be fine\n");
  550. line1 = "Heating too long";
  551. line2 = "Continuing...";
  552. LCDprint();
  553. }
  554. if (incomingData == "p-en") {
  555. Serial.print("Unable to Press. Please restart\n");
  556. while (true) {
  557. // Unrecoverable error has occurred while pressing, lock the machine
  558. line1 = "Press failed";
  559. line2 = "Error. Restart.";
  560. LCDprint();
  561. delay(10000);
  562. }
  563. }
  564. if (incomingData == "u-en") {
  565. Serial.print("Unable to unPress. Please restart\n");
  566. while (true) {
  567. // Unrecoverable error has occurred while unpressing, lock the machine
  568. line1 = "unPress failed";
  569. line2 = "Error. Restart.";
  570. LCDprint();
  571. delay(10000);
  572. }
  573. }
  574. if (incomingData == "refi") {
  575. Serial.print("refill coffee and send cont command\n");
  576. line1 = "Refill Coffee";
  577. line2 = "then tap make";
  578. LCDprint();
  579. while (true) {
  580. delay(10);
  581. if (digitalRead(enter) == LOW) {
  582. line1 = "Refilled";
  583. line2 = "continuing";
  584. LCDprint();
  585. serialOUTbuffer = "cont";
  586. writeSerial();
  587. break;
  588. }
  589. }
  590. }
  591. if (incomingData == "g-fa") {
  592. Serial.print("g-failure: second grinding failure, check grinder!\n");
  593. while (true) {
  594. // Unrecoverable error has occurred during grinding, lock the machine
  595. line1 = "Grinding failed";
  596. line2 = "Check Machine";
  597. LCDprint();
  598. delay(10000);
  599. }
  600. }
  601. if (incomingData == "Comp") {
  602. coffeesDone++;
  603. Serial.print("Made coffee\n");
  604. line1 = "Coffee Done :)";
  605. line2 = "Enjoy!";
  606. LCDprint();
  607. delay(5000);
  608. line1 = "Selection";
  609. delay(500);
  610. }
  611. }
  612. }