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.

583 lines
18 KiB

4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
4 days ago
  1. // Pinout configuration
  2. const int grinderPin = 9, pumpPin = 8, boilerPin = 6, powderPin = 7, wheelPin = 10, invertWheelPin = 11, powderSensorPin = 2, vaporSensorPin = 5, tempSensorPin = A7, wheelStartSensorPin = 4, wheelEndSensorPin = 3, redLED = 13, greenLED = 12;
  3. // Global string
  4. String readString;
  5. // Thermistor config
  6. // WARNING! while the old coffee machine used a PTC thermistor, this was replaced with an NTC one as it was more readily available
  7. // Code must be adjusted accordingly if a PTC resistor is used once more!
  8. int Vo;
  9. float R1 = 10000; // <-- change this value to the resistance of the fixed resistor (so don't change PLS!)
  10. float logR2, R2, T, Tc, Tp, Tstart, Told;
  11. float c1 = 8.5e-04, c2 = 1.85e-04, c3 = 2e-07; // Here's where you can perform actual calibration
  12. // Variable to store desired Max boiler Temp
  13. int desiredTemp = 70;
  14. // Variables to Store errors:
  15. int unrecoverableErr = 0;
  16. int warning = 0;
  17. int warned = 0; // support variable needed to prevent arduino from making second coffee after refill. this is a shitty hack and resets the board.
  18. // Variables for cleaning and other maintenance
  19. int dry = 0;
  20. int noCoffee = 0;
  21. // Milliseconds to delay between each cycle
  22. const int milliseconds = 10;
  23. int timeratio = 1;
  24. // pumpRatio is = seconds to pump water for and will be updated over serial
  25. int pumpRatio = 15; // warning: there is usually ~5s dead time from pump startup to water flowing out of nozzle
  26. int pumpStatus = 0; // support variable to enable pumping while heating, added feb 24
  27. // This next variable is used to get current "time" in ms and break out of while cycles that need a time limit safeguard
  28. unsigned long startTime;
  29. unsigned long pMillis;
  30. //_____________________________________________________________________________
  31. void(* resetFunc) (void) = 0; // declare reset fuction at address 0
  32. //_____________________________________________________________________________
  33. void setup() {
  34. // put your setup code here, to run once:
  35. // first we set pins as I/O and initialize outputs LOW
  36. pinMode(boilerPin, OUTPUT); pinMode(pumpPin, OUTPUT); pinMode(grinderPin, OUTPUT); pinMode(powderPin, OUTPUT); pinMode(wheelPin, OUTPUT); pinMode(invertWheelPin, OUTPUT); pinMode (redLED, OUTPUT); pinMode (greenLED, OUTPUT);
  37. pinMode(powderSensorPin, INPUT); pinMode(vaporSensorPin, INPUT); pinMode(tempSensorPin, INPUT); pinMode(wheelStartSensorPin, INPUT); pinMode(wheelEndSensorPin, INPUT);
  38. digitalWrite(grinderPin, LOW); digitalWrite(pumpPin, LOW); digitalWrite(boilerPin, LOW); digitalWrite(powderPin, LOW); digitalWrite(wheelPin, LOW); digitalWrite(invertWheelPin, LOW); digitalWrite(redLED, LOW); digitalWrite(greenLED, LOW);
  39. // timeratio easily allows to determine how many cycles are required to make 1s pass (ms * ratio = 1s)
  40. timeratio = 1000/milliseconds;
  41. // initialize serial:
  42. Serial.begin(9600);
  43. delay(100);
  44. Serial.write("Arduino running :)\n");
  45. }
  46. //_____________________________________________________________________________
  47. void loop() {
  48. // put your main code here, to run repeatedly:
  49. digitalWrite(greenLED, HIGH);
  50. // Check if unrecoverable error has occurred:
  51. if (unrecoverableErr == 1) {
  52. //startTime = millis();
  53. delay(100);
  54. Serial.write("Error has occurred.\n"); delay(100); Serial.write("Check machine and restart\n");
  55. digitalWrite(greenLED, LOW);
  56. while (true){ // lock the machine in a loop while blinking the RED LED to indicate an error
  57. digitalWrite(redLED, HIGH);
  58. delay(100);
  59. digitalWrite(redLED, LOW);
  60. delay(900);
  61. if (Serial.available() > 0) {
  62. warning = 0;
  63. warned = 0;
  64. unrecoverableErr = 0; // added on 19/11/24 to reset unrecoverable error when serial com is received
  65. break;
  66. }
  67. // reset arduino after 30s -> removed reset on 19/11/24, LED should stay blinking and machine locked until reboot or serialcom received
  68. //if (millis() - startTime == 30000) {
  69. // break;
  70. //}
  71. }
  72. //resetFunc(); //call reset.
  73. }
  74. // check if data has been sent on serial from ESP or PC:
  75. if (Serial.available() > 0) {
  76. // read the incoming data: (we only care about the first few characters, I've chosen 4)
  77. readString = "";
  78. serialRead();
  79. String incomingData = readString.substring(0,4);
  80. //if (incomingData == "read") {
  81. //digitalWrite(greenLED, HIGH);
  82. //}
  83. if (incomingData == "rist") {
  84. // set parameters for ristretto:
  85. delay(100);
  86. Serial.write("Ristretto\n");
  87. pumpRatio = 10; // updated from 15 to 10s after fixing restriction 19/11/24
  88. }
  89. if (incomingData == "espr") {
  90. // set parameters for espresso:
  91. delay(100);
  92. Serial.write("Espresso\n");
  93. pumpRatio = 15; // updated from 20 to 15s after fixing restriction 19/11/24
  94. }
  95. if (incomingData == "long") {
  96. // set parameters for lungo:
  97. delay(100);
  98. Serial.write("Lungo\n");
  99. pumpRatio = 20; // updated from 25 to 20s after fixing restriction 19/11/24
  100. }
  101. if (incomingData == "amer") {
  102. // set parameters for lungo:
  103. delay(100);
  104. Serial.write("Americano\n");
  105. pumpRatio = 60;
  106. }
  107. // check if the incoming data is "make":
  108. if (incomingData == "make") {
  109. // run the code to make coffee:
  110. delay(100);
  111. Serial.write("Making some coffee!\n");
  112. makeCoffee();
  113. }
  114. if (incomingData == "dryr") {
  115. // run the code to perform a dry run (no powder, nor water):
  116. delay(100);
  117. Serial.write("DryRun\n");
  118. dry = 1;
  119. makeCoffee();
  120. }
  121. if (incomingData == "noco") {
  122. // run the code to perform a wet run (water, but no powder):
  123. delay(100);
  124. Serial.write("NoCo\n");
  125. pumpRatio = 10;
  126. noCoffee = 1;
  127. makeCoffee();
  128. }
  129. if (incomingData == "grin") {
  130. // only grind
  131. Grind();
  132. }
  133. if (incomingData == "pump") {
  134. // run the code to just pump water:
  135. pumpRatio = 15;
  136. Pump();
  137. }
  138. if (incomingData == "pres") {
  139. // only press
  140. Press();
  141. }
  142. if (incomingData == "unpr") {
  143. // only unpress
  144. unPress();
  145. }
  146. if (incomingData == "heat") {
  147. // only heat
  148. desiredTemp = 80;
  149. Heat();
  150. }
  151. if (incomingData == "drop") {
  152. // only drop
  153. Drop();
  154. }
  155. if (incomingData == "clea") {
  156. // clean machine
  157. delay(100);
  158. Serial.write("s-clean\n");
  159. dry = 1;
  160. makeCoffee();
  161. noCoffee = 1;
  162. makeCoffee();
  163. delay(100);
  164. Serial.write("S-cleaning done.\n");
  165. }
  166. if (incomingData == "f-un") {
  167. Serial.write("F-Unpress\n");
  168. delay(1000);
  169. digitalWrite(invertWheelPin, HIGH);
  170. delay(5000);
  171. digitalWrite(invertWheelPin, LOW);
  172. }
  173. if (incomingData == "f-pr") {
  174. Serial.write("F-Press\n");
  175. delay(1000);
  176. digitalWrite(wheelPin, HIGH);
  177. delay(5000);
  178. digitalWrite(wheelPin, LOW);
  179. }
  180. }
  181. // making steam:
  182. while (digitalRead(vaporSensorPin) == HIGH) {
  183. desiredTemp = 100;
  184. Serial.write("Vapor heating\n");
  185. delay(500);
  186. Heat(); // Heat the boiler to vapor temperature
  187. //delay(20000); // no idea why if had a 20s delay originally, I attempted to remove it to see how it goes on 19/11/24
  188. delay(200); // assuming 20000 was a type error, 200ms seems sensible
  189. if (unrecoverableErr == 1) {
  190. break;
  191. }
  192. }
  193. delay(milliseconds);
  194. }
  195. //_____________________________________________________________________________
  196. // this is how we read the input
  197. void serialRead() {
  198. while (Serial.available()) {
  199. delay(10);
  200. if (Serial.available() > 0) {
  201. char c = Serial.read();
  202. readString += c;}
  203. }
  204. }
  205. //_____________________________________________________________________________
  206. void Grind() {
  207. digitalWrite(greenLED, LOW);
  208. delay(100);
  209. Serial.write("grinding...\n");
  210. digitalWrite(grinderPin, HIGH);
  211. startTime = millis();
  212. while (true) {
  213. delay(milliseconds);
  214. if (digitalRead(powderSensorPin) == HIGH) {
  215. digitalWrite(grinderPin, LOW);
  216. delay(100);
  217. Serial.write("Grinding Done\n");
  218. break;
  219. }
  220. if (millis() - startTime > 15000) {
  221. Serial.write("Warning, grinding took too long!\n");
  222. digitalWrite(grinderPin, LOW);
  223. delay(100);
  224. Serial.write("Out of Coffee?\n");
  225. warning = 1;
  226. warned = 1;
  227. break;
  228. }
  229. }
  230. digitalWrite(greenLED, HIGH);
  231. }
  232. //_____________________________________________________________________________
  233. void Drop() {
  234. digitalWrite(greenLED, LOW);
  235. delay(100);
  236. Serial.write("dropping...\n");
  237. digitalWrite(powderPin, HIGH);
  238. delay(1000);
  239. digitalWrite(powderPin, LOW);
  240. delay(100);
  241. Serial.write("Dropped\n");
  242. digitalWrite(greenLED, HIGH);
  243. }
  244. //_____________________________________________________________________________
  245. void Heat() {
  246. digitalWrite(greenLED, LOW);
  247. digitalWrite(redLED, HIGH);
  248. startTime = millis();
  249. pMillis = startTime; // value to store
  250. delay(100);
  251. Serial.write("Heating to ");
  252. delay(100);
  253. Serial.print(desiredTemp);
  254. delay(100);
  255. // initialize variables at safe values
  256. Tc = 0; // current temperature
  257. Tp = -10; // temperature at previous cycle
  258. Tstart = - 100; // temperature at start of Heat() function
  259. // monitor temperature and adjust boilerPin as needed:
  260. while (true) {
  261. // read temperature from tempSensorPin:
  262. Vo = analogRead(tempSensorPin);
  263. R2 = R1 * (1023.0 / (float)Vo - 1.0);
  264. logR2 = log(R2);
  265. T = (1.0 / (c1 + c2*logR2 + c3*logR2*logR2*logR2)); // compute temperature from NTC
  266. Tc = (T - 273.15); // convert from Kelvin to Celsius for readability
  267. if (millis() - startTime > 500 && millis() - startTime < 1500) {
  268. Tstart = Tc; // support variable to store temp at beginning, so that we can be sure it's increasing
  269. delay(1001); // make sure we only set Tstart once!
  270. }
  271. // check if temperature is within the acceptable range and break out of the loop without error if done heating:
  272. if (Tc > desiredTemp && pumpStatus == 0) {
  273. delay(100);
  274. Serial.write("reached desired temp\n");
  275. delay(100);
  276. Serial.print(Tc);
  277. break; // temperature is within range, break out of the loop:
  278. }
  279. if (Tc < -100) { // break the loop if temperature < -100, can only happen if cable gets unplugged
  280. delay(100);
  281. Serial.write("u-Thermocouple: unplugged or failed\n");
  282. unrecoverableErr = 1;
  283. break;
  284. }
  285. if (millis() - startTime > 20000 && Tc - Tstart < 1 && pumpStatus == 0) { // break the loop if temperature is not increasing
  286. delay(100);
  287. Serial.write("p-Thermocouple: not detecting heating, positioning or relay fault\n");
  288. unrecoverableErr = 1;
  289. break;
  290. }
  291. if (millis() - startTime > 90000 && pumpStatus == 0) { // break out of the loop after 60s if the boiler is not yet hot
  292. delay(100);
  293. Serial.write("h-taking too long, continuing...\n");
  294. break;
  295. }
  296. // actual heater logic follows:
  297. // check aprox. derivative every second and shut off heater if temperature is increasing too quickly!
  298. // conversely, heater is turned on if temperature is not incresing quickly enough
  299. if (millis() > (pMillis + 1000)){
  300. // initially run the heater on fully. eg: if we set the number to 30 and start with
  301. // Tambient = 20C and desiredTemp = 90 the heater will stay fully on until 90-30 = 60C
  302. //if (Tc < (desiredTemp - 30)) { // old if with variable temp
  303. // new if with hard-coded minimum pulsing enable temperature of 70C
  304. if (Tc < 70 || Tc < (desiredTemp - 20)) {
  305. digitalWrite(boilerPin, HIGH);
  306. }
  307. // change this value for proportional behaviour: lower values will cycle the relay more often.
  308. // Cycling often reduces temperature undershoot, but will shorten contact lifespan
  309. else if ((Tc - Tp) < 0.6) {
  310. digitalWrite(boilerPin, HIGH);
  311. }
  312. //else if ((Tc - Tp) > 1) {
  313. // digitalWrite(boilerPin, LOW);
  314. //}
  315. else {
  316. digitalWrite(boilerPin, LOW);
  317. // delay(1000); // extra 1s delay to keep boiler off for 2s total (delay + millis())
  318. }
  319. // this next if lets us call us for pumping while still monitoring heat
  320. if (pumpStatus == 1 && ((millis() - startTime)/1000) < pumpRatio) {
  321. digitalWrite(pumpPin, HIGH);
  322. }
  323. else if (pumpStatus == 1 && ((millis() - startTime)/1000) > pumpRatio) {
  324. digitalWrite(pumpPin, LOW);
  325. break;
  326. }
  327. else {
  328. //digitalWrite(pumpPin, LOW);
  329. //pumpStatus = 0;
  330. }
  331. Tp = Tc;
  332. pMillis = millis();
  333. }
  334. delay(milliseconds);
  335. }
  336. digitalWrite(boilerPin, LOW);
  337. digitalWrite(redLED, LOW);
  338. digitalWrite(greenLED, HIGH);
  339. }
  340. //_____________________________________________________________________________
  341. void Press() {
  342. digitalWrite(greenLED, LOW);
  343. delay(100);
  344. Serial.write("pressing...\n");
  345. // Slightly move the press in the opposite direction to make sure the motor's commutator engages
  346. digitalWrite(invertWheelPin, HIGH);
  347. delay(100);
  348. digitalWrite(invertWheelPin, LOW);
  349. digitalWrite(wheelPin, HIGH);
  350. startTime = millis();
  351. while (true) {
  352. delay(milliseconds);
  353. if (digitalRead(wheelEndSensorPin) == HIGH) {
  354. delay(90); // extra delay added to compensate for slightly incorrect endstop placement after repair
  355. digitalWrite(wheelPin, LOW);
  356. delay(100);
  357. Serial.write("Pressed\n");
  358. break;
  359. }
  360. if (millis() - startTime > 20000) { // changed from 15s to 20s to account for motor slowdown over the years
  361. delay(100);
  362. Serial.write("p-end of pressing not detected\n");
  363. digitalWrite(wheelPin, LOW);
  364. unrecoverableErr = 1; //temporarily disabled because of failing sensor(s)
  365. break;
  366. }
  367. }
  368. digitalWrite(greenLED, HIGH);
  369. }
  370. //_____________________________________________________________________________
  371. void unPress() {
  372. digitalWrite(greenLED, LOW);
  373. delay(100);
  374. Serial.write("unPressing...\n");
  375. // Slightly move the press in the opposite direction to make sure the motor's commutator engages
  376. digitalWrite(wheelPin, HIGH);
  377. delay(100);
  378. digitalWrite(wheelPin, LOW);
  379. digitalWrite(invertWheelPin, HIGH);
  380. startTime = millis();
  381. while (true) {
  382. delay(milliseconds);
  383. if (digitalRead(wheelStartSensorPin) == HIGH) {
  384. delay(1000);
  385. digitalWrite(invertWheelPin, LOW);
  386. delay(100);
  387. Serial.write("UnPressed\n");
  388. break;
  389. }
  390. if (millis() - startTime > 20000) { // changed from 15s to 20s to account for motor slowdown over the years
  391. delay(100);
  392. Serial.write("u-end of unPressing not detected\n");
  393. digitalWrite(invertWheelPin, LOW);
  394. //unrecoverableErr = 1; //temporarily disabled because of failing sensor(s)
  395. break;
  396. }
  397. }
  398. digitalWrite(greenLED, HIGH);
  399. }
  400. //_____________________________________________________________________________
  401. void Pump() {
  402. digitalWrite(greenLED, LOW);
  403. delay(100);
  404. Serial.write("pumping Water...\n");
  405. pumpStatus = 1; // set pumpStatus variable to 1 to tell Heat() function to pump; this is so that Proportional heating control can still function while pumping, preventing overheat
  406. Heat(); // call Heat function with current parameters
  407. pumpStatus = 0; // set pumpStatus variable to 0
  408. // OLD PUMPING LOGIC BEFORE MOVING INSIDE Heat()
  409. //digitalWrite(pumpPin, HIGH);
  410. //while (pumpRatio > 1) {
  411. // pumpRatio--;
  412. // delay(1000);
  413. //}
  414. //digitalWrite(pumpPin, LOW);
  415. delay(100);
  416. Serial.write("Pumping water done\n");
  417. digitalWrite(greenLED, HIGH);
  418. }
  419. //_____________________________________________________________________________
  420. void makeCoffee() {
  421. // code to make coffee goes here...
  422. // It would be much smarter to break down complicated functions into subroutines: press/unpress etc...
  423. // Hence that's what we'll have done by the time the first release goes public ;)
  424. // makeCoffee() can still run all the same, but clean() can use the same press/unpress code!
  425. // care must be taken to place these functions earlier in the code, or the compiler will rightfully freak out
  426. while (true) {
  427. desiredTemp = 80;
  428. Heat(); // First we pre-heat the boiler
  429. if (unrecoverableErr == 1) {
  430. break;
  431. }
  432. if (dry == 0 && noCoffee == 0) {
  433. Grind(); // Grinding some nice roasted coffee beans!
  434. }
  435. // If grinder won't stop, coffee probably needs to be refilled.
  436. if (warning == 1) {
  437. delay(100);
  438. Serial.write("refill coffee and send cont command\n");
  439. while (true) {
  440. // check if data has been sent on serial from ESP or PC:
  441. if (Serial.available() > 0) {
  442. // read the incoming data: (we only care about the first few characters, I've chosen 4)
  443. readString = "";
  444. serialRead();
  445. String incomingData = readString.substring(0,4);
  446. if (incomingData == "cont") {
  447. // coffee refilled, making is allowed to continue
  448. Serial.write("Refilled\n");
  449. warning = 0;
  450. Grind(); // Grinding some nice roasted coffee beans!
  451. if (warning == 1) {
  452. unrecoverableErr = 1;
  453. Serial.write("g-failure: second grinding failure, check grinder!\n");
  454. }
  455. break;
  456. }
  457. }
  458. }
  459. }
  460. // Stop if grinding failed a second time
  461. if (unrecoverableErr == 1) {
  462. break;
  463. }
  464. delay(1000); // this delay is mandatory to prevent powder spillage caused by grinder inertia.
  465. // Slightly move the press in the opposite direction to make sure the press sensor detects contact
  466. digitalWrite(wheelPin, HIGH);
  467. delay(1000);
  468. digitalWrite(wheelPin, LOW);
  469. unPress(); // unpressing the press to reset the machine
  470. if (unrecoverableErr == 1) {
  471. break;
  472. }
  473. Drop(); // Dropping the powder in the press
  474. delay(1000);
  475. Press(); // Self explainatory
  476. if (unrecoverableErr == 1) {
  477. break;
  478. }
  479. desiredTemp = 90;
  480. Heat(); // First we pre-heat the boiler
  481. if (unrecoverableErr == 1) {
  482. break;
  483. }
  484. if (dry == 0) {
  485. //digitalWrite(boilerPin, HIGH);
  486. delay(100);
  487. desiredTemp = 95; // temperature to try and maintain while making coffee
  488. Pump(); // Pump ratio (in seconds) will be read from serial input in final release
  489. digitalWrite(boilerPin, LOW);
  490. }
  491. // Slightly move the press back to create a way for vapor to escape (added 19/11/24)
  492. digitalWrite(invertWheelPin, HIGH);
  493. delay(3000);
  494. digitalWrite(invertWheelPin, LOW);
  495. delay(3000); // Allow pressure to wane
  496. unPress(); // unpressing the press to reset the machine
  497. if (unrecoverableErr == 1) {
  498. break;
  499. }
  500. delay(100);
  501. Serial.write("Complete!\n");
  502. delay(1000);
  503. dry = 0;
  504. noCoffee = 0;
  505. if (warned == 1) {
  506. resetFunc(); //call reset. I am ashamed of this, but it works.
  507. }
  508. break;
  509. }
  510. }