-->
Page 1 of 2

PWM problems

PostPosted: Sun Oct 09, 2016 8:48 pm
by Dwprice
I've got a nodemcu 12 using Arduino IDE to control an espresso machine solenoid and pump.

If i use digitalwrite, i can successfully run solenoid and pump, turning on and off several times in a row to complete a sequence.

With PWM using gpio4 and 5, have weird behaviour where second time i try to engage pwm it will fail, regardless of where in the sequence, but it will run pwm the next time after the failure. Examples:

Run pwm on pump ok
Stop pwm and wait
Run pwm x% fails
Run pwm y% works

Or

Run pwm on pump ok
Stop pwm and wait
Run Digitalwrite on pump ok
Run pwm y% fails

Or

Run pwm on pump ok
Stop pwm and wait
Run Digitalwrite on pump ok
Run Digitalwrite on pump ok
Then when press button again to do second drink.....
Run pwm on pump Fails
Stop pwm and wait
Run Digitalwrite on pump ok
Run Digitalwrite on pump ok

Or

Run pwm on pump ok
Stop pwm and wait
Run Digitalwrite on pump ok
Run Pwm on pump fails
Then when press button again to do second drink.....
Run pwm on pump ok
Stop pwm and wait
Run Digitalwrite on pump ok
Run Digitalwrite on pump ok

So the code is executed.... just pwm function fails every second call

Using Arduino IDE and upgraded to latest
Nodemcu esp8266 12

Re: PWM problems

PostPosted: Wed Oct 12, 2016 3:38 pm
by ChrisH
Could you post some code (it makes it easier to help..).

How does it "fail"?

Re: PWM problems

PostPosted: Thu Oct 13, 2016 7:11 pm
by Dwprice
Fail means the pump doesn't run at all

Code: Select all// NOTE NOTE remove delay() on line 14 by replacing with millis() routine

// This code is to run a Gaggia Classic espresso machine pump using an SSR
// This code runs a preinfusion and then a shot using state machines and includes variable pump settings and a buzzer
// This code is by David William Price 2016
// This code is released under Creative Commons Attribution-ShareAlike CC BY-SA
// so you can use and adapt but must credit me and release your adaptation under the same license to share with others
// USE AT YOUR OWN RISK - NO REPRESENTATIONS OR WARRANTIES EXPRESS OR IMPLIED - NO LIABILITY
// IF YOU FRY YOURSELF OR YOUR MACHINE THAT IS YOUR PROBLEM

// ACKNOWLEDGEMENTS
// adapts 2006 button debounce code from David A. Mellis, Limor Fried, Mike Walters
// adapts 2005 state machine code from David Mellis and Paul Stoffregen from "blink without delay" example
// adapted slider code and interpretation code... too many influences to document

// FEATURES
// Hardware
// 1. The design takes power from AC after power switch, converts it through 110-5VDC power buck to power Arduino when switch flipped
// 2. SSRS: Two SSRs control pump and solenoid separately. This allows us to open solenoid, preinfuse, stop and wait, run shot, THEN close solenoid
// which ensures the Gaggia doesn't suck all the water out of the coffee during the wait period
// 3. Force sensor: foam + wires force sensor under water tank stops system if tank is missing, empty, or low
// 3A. Force sensor was NOT reliable -- will be replacing it with an ultrasonic range sensor
// 4. RJ45 connectors hooked up so all sensors and power (except ground Serial block) routed through cable to Arduino for easy hookup/disconnect
//
// Software
// 5. Menu system to view and change settings
// 6. Serial notifications for each step of the process and prompts
// 7. EEPROM permanent saving, updating, loading of settings
// 8. Integration with ESP8266 server for OPTIONAL complete control via web page
// 9. Divides a pour into 4 phases: preinfusion, soak, pour start, pour end. Power of pump is different at each state to give a profile
// 10. Uses different pump power levels via PWM to provide pressure profiling over the phases of the shot
// Z. To save memory, variables with value 0-255 use "byte", strings are retrieved from Flash (not RAM), and use functions instead of repeating text

// NOTE ON TIMING!!!!
// State Machines are SWITCHES.... so you must ensure that each machine creates a state HIGH or LOW so it will only run once
// Then you create a paired state machine with the opposite (HIGH or LOW) which also flips a state so it only runs once

// SETUP the Pump control and state machine variables
const byte pumpPin = 5; // the number of the Pump control pin (for analogue, use PWM pin on controller:3,5,6,9,10,11)
byte infuseState = LOW; // infusion running initial state
byte infuseStatus = 0; // infusion initial completion status
byte shotState = LOW; // shot running initial state
byte shotStatus = 0; // shot initial completion status
byte soakState =LOW;
byte infuseWaitStatus = 0;
byte shotEndState = LOW;
byte shotEndStatus = 0;

// SETUP PWM to run pump and different levels of power
// For ARDUINO 0 to 255 is normal as it's an 8bit ADC so you can use "byte"
// BUT ESP8266 is 10 bit so 1023 is full duty cycle -- NOTE-- change frequency of PWM to multiple of AC 60hz current below to avoid stutters
// THIS MEANS FOR ESP8266 we CANNOT use BYTE! We must use INT!!!
unsigned int pumpPowerInfuse = 400; // set from 0-1023 to adjust PWM power going to pump using analogWrite for infusion
unsigned int pumpPowerShot = 900; // set from 0-1023 to adjust PWM power going to pump using analogWrite for shot
unsigned int pumpPowerShotEnd = 600; // Set pump power for last part of shot
float shotStartPCT = 0.7; // Set percentage of shot at beginning pump setting
float shotEndPCT = 0.3; // Set percentage of shot at end pump setting

// SETUP the Solenoid control and state machine variables
const byte solenoidPin = 4; // the number of the Solenoid control pin

// SETUP the timing mechanism
unsigned long previousMillis = 0; // store last time TIMING was put
unsigned long previousMenuMillis = 0; // store last time TIMING was put

// SETUP the pushbutton control
const byte buttonPin = 12; // the number of the pushbutton pin
byte buttonState; // variable for reading the pushbutton status
byte lastButtonState = LOW; // the previous reading from the input pin
int reading = LOW;
byte goState = 0; // trigger (for hardware or software buttons) to run Preinfusion and Pour-- set up initial waiting state

// SETUP the buzzer
const byte buzzerPin = 14; // the number of the buzzer/speaker pin

// SETUP button debounce variables
// the following variables are longs because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 50; // the debounce time; increase if the output flickers

// SETUP LED pin
// const int LEDPin = 13; // the number of the LED pin

// SETUP EEPROM For settings storage
#include <EEPROM.h> // grab eeprom header file
const byte infuseTimeEEstore = 1; // setup EEPROM address to store and retrieve variable
const byte infuseWaitTimeEEstore = 2; // setup EEPROM address to store and retrieve variable
const byte shotTimeEEstore = 3; // setup EEPROM address to store and retrieve variable
const byte shotCounterEEstore = 4; // setup EEPROM address to store and retrieve variable
// NOTE: ensure all references to setting variables are same variable type (eg. "byte") or when store and read back, gets corrupted into 1000s
// NOTE: store all setting variables in seconds to save EEPROM storage space, then when use them in code multiple by 1000 to convert to milliseconds
byte infuseTime = 1; // seconds of preinfusion-time (value gets replaced from EEPROM)
byte infuseWaitTime = 2; // seconds of wait-time (value gets replaced from EEPROM)
byte shotTime = 3; // seconds of shot-time (value gets replaced from EEPROM)
byte shotCounter = 0; // initialize variable for shotcounter (will be replaced by EEPROM stored value)

// ESP8266 WIFI STUFF
#include <ESP8266WiFi.h> // ESP8266 header file
const char* ssid = "XXXXXX"; // SSID for wifi network to connect to
const char* password = "XXXXXXXXXXXXX"; // password for the wifi network
WiFiServer server(80);
int infuseVal, infuseWaitVal, shotVal; // initialize variables for processing web settings sliders
byte updateFlag=0; // this flag triggers refresh to home page
byte updateWeb=0; // this flag triggers refresh to home page

// HARDWARE NOTES
// Created for Arduino and Gaggia Classic
// For whole system:
// we take AC- to power buck, and AC+ from the power switch to the power buck to generate 5VDC
// we put 5VDC ground to Serial strip to share with all DC components
// we put 5VDC+ to power Arduino, and through Arduino, force sensor
// For Pump: SSR1 takes DC-ground from Serial strip, 5vDC+ from Arduino pumpPin, and takes AC+ from Gaggia power button and routes to pump
// For Solenoid: SSR2 takes DC-ground from Serial strip, 5vDC+ from Arduino solenoidPin, and takes AC+ from Gaggia power button and routes to solenoid
// For button: one line goes to GND and other goes to buttonPin on controller (using internal pullup resistor)
// for buzzer: one line goes to GND and other goes to buzzerPin on controller
// For force sensor: we take DC-ground from Serial strip through 330ohm resistor, 5vDC from Arduino, and send sensor data to Arduino forceSensorPin

void setup()
{
// SETUP EEPROM for reading and writing settings vaues from storage
EEPROM.begin(512);
GetSettingsStore(); // get settings from EEPROM

// for PUMP set the digital pin as output:
pinMode(pumpPin, OUTPUT);

// Adjust PWM frequency from default 1000 to multiple of AC 60HZ
// This makes a huge difference BUT if in sync with AC great, if not you get NOTHING. Need zero-cross detector
//analogWriteFreq(1000); // Set PWM frequency to a multiple of 60Hz (AC frequency) to avoid pump stuttering

// for SOLENOID set the digital pin as output:
pinMode(solenoidPin, OUTPUT);
// for PUSHBUTTON initialize the digital pin as an input:
pinMode(buttonPin, INPUT_PULLUP);
// for PUSHBUTTON digitalWrite(pin, HIGH) to input pin to turn on internal 20K pullup resistor
// allows use of switch without external resistor but also means HIGH and LOW are swapped
//digitalWrite(buttonPin, HIGH);
// for ONBOARD LED initialize the digital pin as an output.
//pinMode(LEDPin, OUTPUT);

// SETUP Serial monitor
Serial.begin(115200); // See the connection status in Serial Monitor

// ESP8266 can run AP mode (establishing a wifi network) AND station mode (offer web pages) at the same time
// we only want station mode -- AP mode interferes with our existing wifi network AND we don't need to waste processing resources
// Reset wifi and switch off AP mode so don't run a competing network
Serial.println("Reset wifi");
WiFi.disconnect();
Serial.println("Switch off AP mode"); // we don't want ESP running an access point competing wifi network
WiFi.softAPdisconnect(true);
Serial.println("Switch on STATION mode"); // we just want ESP serving up webpage on our existing wifi network
WiFi.mode(WIFI_STA);

// OK Now Connect to wifi network
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { // need to change this so can run without wifi connection
delay(500); // need to change this to get rid of delay function
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");

// Start the web server
server.begin();
Serial.println("Server started");

// Print the web server IP address
Serial.print("Use this URL to connect: ");
Serial.print("http://");
Serial.print(WiFi.localIP());
Serial.println("/");

}

// START THE LOOP

void loop()
{

// Watch hardware button to see if should activate pouring
// WAITING for pushbutton, using debounce, if get pushbutton then activate goState
// digitalWrite(LEDPin, LOW); // Turn off LED for waiting State

// read the state of the switch into a local variable:
reading = digitalRead(buttonPin);

// check if you pressed button (input went from LOW to HIGH) and waited long enough since last press to ignore noise
// If button state changed due to noise or pressing, then reset debouncing timer
if (reading != lastButtonState)
{
yield(); // allow ESP to run background tasks without reset
lastDebounceTime = millis();
}
// if reading been there longer than debounce delay, use it as actual current state:
if ((millis() - lastDebounceTime) > debounceDelay)
{
// Run web menu to check for software buttons AND also check hardware button
WebSettingMenu(); // in same time interval checking hardware button, run the websetting menu and look at its buttons

// if the button state has changed:
if (reading != buttonState)
{
yield(); // allow ESP to run background tasks without reset
buttonState = reading;
// if we are sure (using debounce) that button was really pressed, switch on goState to activate pouring
if (buttonState == LOW) // remember using internal pullup resistor swaps HIGH and LOW states
{
goState = 1; // activate goState
// Serial.println(F(">> Brew state START"));
// Serial.flush();
}
}
}
// END OF WAITING FOR HARDWARE PUSHBUTTON


// PREINFUSION AND POUR SUBROUTINE

// TO ADD - ultrasonic water tank status function
// NOTE: Force check of watertankStatus (see bottom) by setting it as 0 in variables at top of code
// do not start any preinfusion or pour if watertankStatus is not 1
// this should mean system will not continue unless watertankStatus is ok

if (goState == 1) // if goState activated, then run the whole preinfusion and pouring routine
{
//digitalWrite(LEDPin, HIGH); // Turn on LED to show goState engaged
tone(buzzerPin,3800,1000); // announce with tone: preinfusion and shot routine starting

// set up timing
unsigned long currentMillis = millis(); // grab current amount of time controller board has been running up until button press

// remember that millis() gives amount of time since controller was turned on (also rolls over after X days)
// very first time need to set up previousMillis so subroutines will run even if controller board running for awhile before press button
// otherwise state machines for preinfusion and pour will not run as they will think time has already passed

// NOTE: must ensure that this is ONLY run first time... so exclude its use unless ALL state machines are LOW
// otherwise you'll get stuck in a loop where the time is reset here so state machine counters won't work
if ((soakState==LOW) && (infuseState == LOW) && (shotState == LOW) && (shotEndState == LOW)) // must use state not status or will have neverending loop in state machines as timing always reset
{
previousMillis = currentMillis; // Set this up for the first time this loop is run when button is pressed
// digitalWrite(LEDPin, LOW); // Turn off LED to show timing setup
}

// NOTE: All state machines should be in pairs. One says if we haven't done this yet, set the switch to DOING. Second says, keep counting until DOING
// has been going X seconds, then switch DOING off and set switch to DONE. That triggers the next machine, and so on.

// PREINFUSION ROUTINE for infuseWaitTime
// first check if preinfusion state is complete -- if no, then continue
// then check if already preinfusing -- if no, then switch on
// if already preinfusing check how long it's been -- if > infuseTime, stop and set preinfusion status done

if ((infuseStatus == 1) && (soakState==HIGH) && (infuseWaitStatus == 0) && (currentMillis - previousMillis >= infuseWaitTime*1000))
{
Serial.println(F(">> Soak DONE"));
Serial.flush();
previousMillis = currentMillis; // Remember the time
infuseWaitStatus = 1; // set infusion state machine complete
soakState=LOW;
yield(); // allow ESP to run background tasks without reset
}

else if ((infuseStatus == 1) && (soakState==LOW) && (infuseWaitStatus == 0) && (currentMillis - previousMillis <= infuseWaitTime*1000))
{
Serial.print(F(">> Soak START for: "));
Serial.println(infuseWaitTime);
Serial.flush();
soakState=HIGH;
previousMillis = currentMillis; // Remember the time
}

if ((infuseStatus == 0) && (infuseState == HIGH) && (infuseWaitStatus == 0) && (currentMillis - previousMillis >= infuseTime*1000))
{
Serial.println(F(">> Preinfuse DONE"));
Serial.flush();
infuseState = LOW; // Turn preinfusion status off for output
infuseStatus = 1; // set infusion state machine complete
previousMillis = currentMillis; // Remember the time
//digitalWrite(pumpPin, infuseState); // Stop preinfusion digital
analogWrite(pumpPin, 0); // Stop preinfusion analog
// digitalWrite(LEDPin, LOW); // turn off LED to show preinfusion done
yield(); // allow ESP to run background tasks without reset
}
else if ((infuseStatus == 0) && (infuseState == LOW) && (infuseWaitStatus == 0) && (currentMillis - previousMillis <= infuseTime*1000))
{
Serial.print(F(">> Preinfuse START for: "));
Serial.println(infuseTime);
Serial.print(F(">> Power: "));
Serial.println(pumpPowerInfuse);
Serial.flush();
infuseState = HIGH; // turn preinfusion status on
previousMillis = currentMillis; // Remember the time
// digitalWrite(LEDPin, HIGH); // turn on LED during preinfusion
digitalWrite(solenoidPin, HIGH); // open solenoid
// DIGITAL VERSION FOR FULL ON OR OFF
//digitalWrite(pumpPin, infuseState); // (option) Run preinfusion as digital for 100% on or off
// ANALOG VERSION FOR PRESSURE DIFFERENCE
analogWrite(pumpPin, pumpPowerInfuse); // Run preinfusion at pressure level determined by pumpPowerInfuse using PWM
}


// SHOT START ROUTINE for shotTime
// first check if shot state is complete -- if no, then continue
// then check if already doing shot -- if no, then switch on
// if already doing shot check how long it's been -- if > shotTime, stop and set shot status done

if ((shotStatus == 0) && (infuseStatus == 1) && (infuseWaitStatus == 1) && (shotState == HIGH) && (currentMillis - previousMillis >= (shotTime*shotStartPCT*1000)))
{
shotState = LOW; // Turn shot status off for output
shotStatus = 1; // set shot state machine complete
Serial.println(F(">> Shot begin DONE"));
digitalWrite(pumpPin, shotState); // (option) Run shot as digital for 100% on or off
//analogWrite(pumpPin, 0); // End shot start part by switching off pump
previousMillis = currentMillis; // Remember the time
Serial.flush();
yield(); // allow ESP to run background tasks without reset
}
else if ((shotStatus == 0) && (infuseStatus == 1) && (infuseWaitStatus == 1) && (shotState == LOW) && (currentMillis - previousMillis <= (shotTime*shotStartPCT*1000)))
{
Serial.print(F(">> Shot begin for: "));
Serial.println(shotTime*shotStartPCT);
Serial.print(F(">> Power: "));
Serial.println(pumpPowerShot);
Serial.flush();
shotState = HIGH; // turn shot status on
previousMillis = currentMillis; // Remember the time
// DIGITAL VERSION FOR FULL ON OR OFF
digitalWrite(pumpPin, shotState); // (option) Run shot as digital for 100% on or off
// ANALOG VERSION FOR PRESSURE DIFFERENCE
//analogWrite(pumpPin, 0); // Stop preinfusion analog
//analogWrite(pumpPin, pumpPowerShot); // Run shot at pressure level determined by pumpPowerShot using PWM
// digitalWrite(LEDPin, HIGH); // turn on LED during pour
}

// SHOT END ROUTINE for shotTime
// Finish shot at lower pump power

if ((shotEndStatus == 0) && (shotStatus == 1) && (infuseStatus == 1) && (infuseWaitStatus == 1) && (shotEndState == HIGH) && (currentMillis - previousMillis >= (shotTime*shotEndPCT*1000)))
{
shotEndState = LOW; // Turn shot status off for output
shotEndStatus = 1; // set shot state machine complete
//shotCounter ++; // increment shot counter
//EEPROM.write(4, shotCounter); //update shotcounter value in EEPROM store
//EEPROM.commit();
Serial.println(F(">> Shot COMPLETE"));
previousMillis = currentMillis; // Remember the time
//digitalWrite(pumpPin, shotEndState); // Stop shot digital
analogWrite(pumpPin, 0); // Stop shot analog
digitalWrite(solenoidPin, LOW); // close solenoid
goState = 0; // Reset go status to drop out of loop and wait for button again
// digitalWrite(LEDPin, LOW); // turn off LED to show pour is done
Serial.flush();
yield(); // allow ESP to run background tasks without reset
}
else if ((shotEndStatus == 0) && (shotStatus == 1) && (infuseStatus == 1) && (infuseWaitStatus == 1) && (shotEndState == LOW) && (currentMillis - previousMillis <= (shotTime*shotEndPCT*1000)))
{
Serial.print(F(">> Shot finish for: "));
Serial.println(shotTime*shotEndPCT);
Serial.print(F(">> Power: "));
Serial.println(pumpPowerShotEnd);
Serial.flush();
shotEndState = HIGH; // turn shot status on
previousMillis = currentMillis; // Remember the time
// DIGITAL VERSION FOR FULL ON OR OFF
//digitalWrite(pumpPin, shotEndState); // (option) Run shot as digital for 100% on or off
// ANALOG VERSION FOR PRESSURE DIFFERENCE
//analogWrite(pumpPin, 0); // Stop preinfusion analog
analogWrite(pumpPin, pumpPowerShotEnd); // Run shot at pressure level determined by pumpPowerShot using PWM
// digitalWrite(LEDPin, HIGH); // turn on LED during pour
}
}


// RESET STATE MACHINE FLAGS
// If we successfully preinfused AND poured, we need to reset all the states to allow for the next shot
if ((infuseStatus == 1) && (infuseWaitStatus == 1) && (shotStatus == 1) && (shotEndStatus == 1))
{
yield(); // allow ESP to run background tasks without reset
ResetFlags();
}

// BUTTON DEBOUNCE save reading. Next loop, it'll be the lastButtonState:
lastButtonState = reading;


// THIS IS END OF LOOP CODE
}

// THESE ARE ALL FUNCTIONS CALLED FROM WITHIN THE CODE

void WebSettingMenu()
{
// This function makes web menu and watches software sliders and buttons

// Create web menu and watch software web buttons to activate settings changes or pouring
// present menu for Settings Mode
// display current settings and prompt for choosing item to change
// Setup client
WiFiClient client = server.available();
if (client) {
// This section sets up the HTML webpage for the menu
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println(""); // do not forget this one
client.println("<!DOCTYPE HTML>");
client.println("<html>");

client.println("<div style=\"user-select: none; -webkit-user-select: none; \">"); // disable copy/paste in the user interface so you can use sliders more easily on tablet/phone

// This section shows the menu header and shot counter
client.println("<font style='font-family: Arial; font-size:25px'>");
client.println("&nbsp;&nbsp;/\\_/\\");
client.println("<br>( o.o ) Neko Espresso");
client.println("<br>&nbsp;> ^ < </font>");

client.println("<hr> <font style='font-family: Arial; font-size:30px'> ");
client.print(F("<table width='720'; border='0'> <tr><td width='200'>Shot Count</td><td>"));
client.print(shotCounter);
client.print("</td></tr></div>");

// This section provides HTML Sliders to adjust settings, and requires pressing a button to trigger update based on those settings

client.println("<div style=\"user-select: none; -webkit-user-select: none; \">"); // disable copy/paste in the user interface so you can use sliders more easily on tablet/phone

//first slider INFUSE TIME
client.print("<tr>");
client.print("<td>Infuse Time</td><td><form method=get><input type='range' min='1' max='10' data-highlight='true' name=infuseValX style='width: 400px; height: 40px;' value=");
client.print(infuseTime);
client.print(" oninput='showValue0(this.value)'>");
client.print("<span id='range'>");
client.print(infuseTime);
client.print(" </span>");
client.print("<script type='text/javascript'>\r\nfunction showValue0(newValue)\r\n{\r\ndocument.getElementById('range').innerHTML=newValue;\r\n}\r\n</script>\r\n");
client.print("</td></tr>");

//second slider SOAK TIME
client.print("<tr>");
client.print("<td>Soak Time</td><td><form method=get><input type='range' min='1' max='20' data-highlight='true' name=QsoakValY style='width: 400px; height: 40px;' value=");
client.print(infuseWaitTime);
client.print(" oninput='showValue1(this.value)'>");
client.print("<span id='range1'>");
client.print(infuseWaitTime);
client.print(" </span>");
client.print("<script type='text/javascript'>\r\nfunction showValue1(newValue)\r\n{\r\ndocument.getElementById('range1').innerHTML=newValue;\r\n}\r\n</script>\r\n");
client.print("</td></tr>");

//third slider SHOT TIME
client.print("<tr>");
client.print("<td>Pour Time</td><td><form method=get><input type='range' min='1' max='35' data-highlight='true' name=JpourValZ style='width: 400px; height: 40px;' value=");
client.print(shotTime);
client.print(" oninput='showValue2(this.value)'>");
client.print("<span id='range2'>");
client.print(shotTime);
client.print(" </span>");
client.print("<script type='text/javascript'>\r\nfunction showValue2(newValue)\r\n{\r\ndocument.getElementById('range2').innerHTML=newValue;\r\n}\r\n</script>\r\n");
client.print("</td></tr>");
client.print("</table>");

// This section sets up buttons to update, brew, stop, reset counter, refresh home page
// Update button
client.print("<input type=submit value='Update Settings' style=\"-webkit-appearance: none; border-radius: 0; border:none; font-size:35px; font-weight:700; height:100px;width:720px;background-color:Orange\"></form>");
// Brew button
client.print("<br><a href=\"/BREW=ON\"><button style=\"border:none; font-size:35px;font-weight:700;height:180px;width:180px;background-color:SpringGreen\">Brew</button></a>");
// Emergency stop button
client.print("<a href=\"/STOP=ON\"><button style=\"border:none; font-size:35px;font-weight:700;height:180px;width:180px;background-color:Pink\">Stop</button></a>");
// Shot counter reset button
client.print("<a href=\"/RESET=ON\"><button style=\"border:none; font-size:35px;font-weight:700;height:180px;width:180px;background-color:LightSkyBlue\">Shot>0</button></a>");
// Refresh to home page button
client.print("<a href=\"http://10.0.1.6/\"><button style=\"border:none; font-size:35px;font-weight:700;height:180px;width:180px;background-color:Yellow\">Home</button></a>");

client.print(F("</font><br>"));

client.println("</div>"); // end of disable copy/paste in the user interface so you can use sliders more easily on tablet/phone
client.print("</html>");

// Processing web interactions using the webpage header for all sliders and buttons
while (client.connected()) {
if (client.available()) {
String request = client.readStringUntil('\r'); // Read the first line of the request
Serial.println(request); // show us the header we got by sending to serial monitor
client.flush();

parseHeader(request); // go parse the header for changes in sliders and buttons and trigger actions and update flag if changes made
if (updateFlag==1 || updateWeb==1) // if our update flag was triggered by parseheader() or elsewhere like brew finished, refresh web page to show updated values
{
client.print("<script type=\"text/javascript\"> window.location.href = \"http://10.0.1.6/\" </script>");
updateFlag=0; // reset our update flag so web page won't update unless receives slider changes
updateWeb=0; // reset our update flag so web page won't update unless triggered again by finish brew or whatever
}
client.stop(); // close the connection:
}
yield(); // we're in a while loop, so use yield() to let ESP do background tasks without resetting itself
}
yield(); // we're in a if loop, so use yield() to let ESP do background tasks without resetting itself
}
}

void parseHeader(String str) {
// This function parses web header generated by web menu
// to parser looks to see if sliders or buttons used and thus trigger actions

// First we parse for changes in sliders triggered by update button on web page

updateFlag=0; // reset our update flag so web page won't update unless receives slider changes

// Parsing data from header for first slider
int startIndex = str.indexOf("X"); // based on header string returned by web page, look for letter BEFORE value we want
int endIndex = str.indexOf("Q"); // based on header string returned by web page, look for letter AFTER value we want
String infuseStr = str.substring(startIndex + 2, endIndex - 1); // set indexes so we isolate the value we want
char infuseTemp[4];
infuseStr.toCharArray(infuseTemp, sizeof(infuseTemp));
infuseVal = atoi(infuseTemp); // grab that isolated value

// Parsing data from header for second slider
startIndex = str.indexOf("Y"); // based on header string returned by web page, look for letter BEFORE value we want
endIndex = str.indexOf("J"); // based on header string returned by web page, look for letter AFTER value we want
String infuseWaitStr = str.substring(startIndex + 2, endIndex -1); // set indexes so we isolate the value we want
char infuseWaitTemp[4];
infuseWaitStr.toCharArray(infuseWaitTemp, sizeof(infuseWaitTemp));
infuseWaitVal = atoi(infuseWaitTemp); // grab that isolated value

// Parsing data from header for third slider
startIndex = str.indexOf("Z"); // based on header string returned by web page, look for letter BEFORE value we want
endIndex = str.indexOf("H"); // based on header string returned by web page, look for letter AFTER value we want
String shotStr = str.substring(startIndex + 2, endIndex -1); // set indexes so we isolate the value we want
char shotTemp[4];
shotStr.toCharArray(shotTemp, sizeof(shotTemp));
shotVal = atoi(shotTemp); // grab that isolated value

// Print out the values we isolated on serial monitor
Serial.println("Parsed values");
Serial.println("New Infuse" + infuseStr + " New Soak " + infuseWaitStr + " New Pour " + shotStr);

// Here we save settings for Infusion, Wait Time (soak), and Pour

if(infuseStr.toInt()!=0) // if setting updated (not 0) update EEPROM with new setting
{
infuseTime=infuseStr.toInt();
EEPROM.write(infuseTimeEEstore, infuseTime);
EEPROM.commit();
updateFlag=1;
}

if(infuseWaitStr.toInt()!=0) // if setting updated (not 0) update EEPROM with new setting
{
infuseWaitTime=infuseWaitStr.toInt();
EEPROM.write(infuseWaitTimeEEstore, infuseWaitTime);
EEPROM.commit();
updateFlag=1;
}

if(shotStr.toInt()!=0) // if setting updated (not 0) update EEPROM with new setting
{
shotTime=shotStr.toInt();
EEPROM.write(shotTimeEEstore, shotTime);
EEPROM.commit();
updateFlag=1;
}

// Here we parse for web button presses and trigger actions

if (str.indexOf("/BREW=ON") != -1) { // if brew button pressed, start brew
shotCounter ++; // increment shot counter
EEPROM.write(4, shotCounter); //update shotcounter value in EEPROM store
EEPROM.commit();
goState = 1; // Activate brewing process as if hardware button pressed
updateFlag=1; // refresh webpage to show updated shot count and prep for next shot
}

if (str.indexOf("/STOP=ON") != -1) { // if stop button pressed, stop shot immediately
Serial.println("!!!EMERGENCY STOP!!!");
analogWrite(pumpPin, 0); // Stop shot analog
digitalWrite(solenoidPin, LOW); // close solenoid
goState = 0; // Reset go status to drop out of loop and wait for button again
updateFlag=1; // set trigger for web refresh to home page
ResetFlags(); // Reset all state machine flags
}

if (str.indexOf("/RESET=ON") != -1) { // if reset button pressed, set shot counter back to 0
Serial.println("Resetting Shot Counter");
shotCounter=0;
EEPROM.write(shotCounterEEstore, 0); // resetting shot counter to zero
EEPROM.commit();
updateFlag=1; // set trigger for web refresh to home page
}
}


void GetSettingsStore()
// This function grabs stored settings data from EEPROM to set our settings variables
{
infuseTime =(EEPROM.read(infuseTimeEEstore));
infuseWaitTime =(EEPROM.read(infuseWaitTimeEEstore));
shotTime =(EEPROM.read(shotTimeEEstore));
shotCounter =(EEPROM.read(shotCounterEEstore));
}


void ResetFlags()
// This function resets all state machine flags to allow next shot
// this function is used after brewing complete OR on emergency stop
{
infuseStatus = 0; // reset preinfusion state machine
infuseWaitStatus = 0; // reset soak state machine
shotStatus = 0; // reset shot state machine
shotEndStatus = 0; // reset shot state machine
soakState=0; // reset soak state flag -- maybe not needed
tone(buzzerPin,1200,2000); // announce with tone: shot process complete or emergency stopped
}

Re: PWM problems

PostPosted: Fri Oct 14, 2016 1:46 pm
by Dwprice
Repeated