All water tank sensors for portafilter based espresso makers have an accuracy issue when detecting the water being dispensed into the espresso shot glass. The issue is that they must account for the water that is absorbed into the portafilter’s coffee grinds and not dispensed into the espresso cup. An easy method to account for this is to estimate a fixed amount of water lost every shot, but this will change with the coffee, how fine the coffee grind is, the tamper pressure, the portafilter size, and the initial water level in the boiler. A direct but more expensive way because it needs another sensor is to measure the weight of the water dispensed into the espresso cup. This accuracy issue was the same with the sensor designed and described in the last blog: A water Tank Sensor for the Rancilio Silvia Espresso Machines. Fortunately, within the open source EspressoConnect code that you can download for free is an adaptive R2 (R-squared) based algorithm that provides the solution. This blog will describe how this adaptive algorithm uses R2 to accurately detect the water dispensed into the espresso cup by just measuring the water tank level during the shot. This sensor and algorithm are part of the machine learning espresso project described in the blog titled: An Espresso Maker that Knows when You Want Some.

Description of the system and variables

For most single boiler systems including the Rancilio Silvia, when espresso shot starts, the water pump will pull water from the water tank and push the water through the safety valve, into the boiler, through the portafilter and then finally into to espresso cup.

Water Flow in a Single Boiler Espresso Maker

As described above, the amount of water between the pump and the espresso cup may changes every shot. The obvious variable is the amount of water that may fill up the portafilter. Another variable is the coffee itself since the coffee amount and grind density are things that may change in search for the perfect shot. Another variable that must be considered is the water in the boiler. This level will also change if the boiler is used to steam milk before the next shot since the water pump is not used during the steaming event. Therefore, we need an algorithm that can account for these variables.

How to detect the espresso in the cup

The simple explanation is that when the espresso starts to flow into the cup is the moment the sensor velocity slows down. When the pump starts, the pressure between the pump and the protafilter is relatively low and the water flow is constant and at its fastest rate. However, once the water fills up the portafilter and the boiler, the pressure increases until the safety valve pressure is reached and a new lower flow (velocity) is measured as a portion of the pump water volume is recycled back into the tank. For most systems, this safety threshold is set to 9 bars of pressure. Therefore, for an espresso shots, there are effectively two velocities. An example of the sensors position versus time is shown in the image below and you can clearly see the two flow rates or sensor velocities.

An Example of a 24 Second Shot Time

While quite easy to tell when this change in velocity occurs with the eye, It is more difficult for the computer to do it real time while the espresso is being pulled. Below is the example of an espresso shot profile taken after the boiler was used for steaming milk. The initial velocity lasted quite a bit longer since water was refilling the boiler in addition to the portafilter.

Espresso Shot Data after steaming milk

In addition, a direct calculation of water flow rate (shown as velocity in the graph) has quite a bit of noise. The algorithm needs to be able to detect this change of flow/velocity reliably in the presence of noise and over different start and stop levels and pressures.

Using R2 to detect a change in velocity

In the real world, sensor inputs often have uncertainty and noise. Fortunately, there are ways to handle this uncertainty. For this algorithm, the linear regression parameter R2 is used to determine real time when the initial constant velocity changes due to the system pressure. When the water pump is started, the algorithm in its simplest form samples the data and calculates a new R2 with each new data sample. When R2 of the dataset becomes less than a threshold, the algorithm can detect the change in velocity which corresponds to the moment the espresso starts to flow into the cup. This simple algorithm along with some additional exception handling results in an accurate real time sensor in the presence of uncertainty and noise.

R2 is a statistical measure that represents the proportion of the collected data variance that is explained by a variable in a regression model. In our case, the model is a linear regression of the data. The linear regression algorithm finds the line that best represents the data. Mathematically R2 is represented by the following equation:

The UnexplainedVariation represents the total squared error the data has to the linear regression line fi . The TotalVariation is the variance of the data, where ybar represents the average of the data. The smaller the error is relative to a linear regression line, the closer R2 is to one and the better the data can be represented by a single regression line. In addition, the slope of this line represents water flow rate or velocity. This is exactly what we are looking for in a detection trigger. By detecting a drop on R2 we can detect the change in velocity since one single line no longer represents the data well.

The shot start is determined when one regression line no longer represents data well

Handling waves, startup vibration, and velocity increases

Small waves in the tank are caused by the startup vibration of the motor and then when water flows into the tank from the safety valve return tube. During the shot, the sensor is measuring a very small amount of water movement (i.e. approximately 4ml for a 50ml shot size), so while these waves are easily seen with the eye, the are there. One hardware way to eliminate waves, like all harbors, is to block the waves with a wall. The float track provides this blocking function. In the first version of the sensor track, the windows in the track allowed for the water waves to reach the sensor. The new track, as shown in the last blog, without windows reduced but did not eliminate this effect.

The waves at startup were handled by first using a five sample moving average filter and then an adaptable R2 threshold that depends on data set size. This allows for more start up noise, but increases R2 as more data is collected to improve detection accuracy. The table below shows the value of R2 vs data set size.

Samples less or greater than the value below R2 Threshold
<= 80.9
<= 12 0.94
<= 300.98
> 300.995
R2 Threshold as a function of samples

The final consideration in the algorithm was the direction of the velocity change. Sometimes the pump takes a second or two to prime itself to provide full sucking power from the water tank. The effect shows itself as a velocity increase at startup that should not cause R2 to trip. R2 does not have polarity so it cannot accurately reject this event. To avoid tripping under this condition, the velocity is also calculated and if R2 drops due to a velocity increase, it is ignored.

A JupyterLab notebook shot detection simulator

JupyterLab is a great environment for data analysis and algorithm development and it was used extensively in the testing of the current R2 based algorithm. Within the Espresso Connect code, the shot detection algorithm was implemented in the file where the class ShotSensor() is defined. The algorithm resides in the def isShotDone() method. During a shot, all the sample data is collected and stored in the ShotSample.csv file and allows the use of the Jupyterlab shot detection simulator to analyze and adjust the algorithm post espresso shot. The JupyterLab ShotStartAlgoRev1.ipynb simulator can be downloaded to view the algorithm in action. This simulator is run on another computer and it copies the ShotSample.csv file from the Raspberry Pi computer so the last shot can be viewed.

JupyterLab Shot Detection Algorithm Simulator

While the notebook comes with a set of shot data that can be used for off-line analysis, the script uses the following code to transfer the ShotSample.csv file from the Espresso Connect Raspberry pi during operation.

The number of past shots can be adjusted in the script along with the filtering parameters. The filtering parameters are initially set to match the code. The MINSAMPLE, LONGSHOTSAMPLES, and SHOTSAMPLES should be two less than the code parameters to match functionality. This difference is due to fact we store a few extra items in the code array.

Running the simulator will graph the past shots and show how accurate the algorithm performed.

The experimental results

Experimental results shown in the figure below demonstrate how this R2 based algorithm along with the sensor accuracy results in nice water tank sensor performance. For the experiments a bunch of shots were taken and then measured with a weight scale and compared to a target 50ml shot size. The median error is a just -1ml and the standard deviation is 3.38ml, which is dominated by one outlier in this data set. Future algorithm enhancements should improve this outlier event and in actual daily use the results have been great. This same set of data is provided with the Jupyterlab simulator for readers that want to perform some additional analysis and even algorithm enhancements.

Target Shot Size was 50ml


The actual moment espresso starts to fill the cup can be detected by a change in system velocity. However, instead of using an actual velocity threshold value which may change from system to system and shot to shot, the algorithm uses a more general R2 parameter that is a function of the actual system parameters. Besides being simple in nature, when the system parameters change or the a different machine is used, the basic algorithm will also work. Are you ready to try this out on your espresso maker? As a start, you can download the JupyterLab file and use the provided ShotSamples.csv file to try it out for yourself. The JupyterLab file and complete project is available at the following links:

Do you have a suggested improvement? Thanks for reading and let me know your comments below. The surf was fun last weekend.


  1. Definition of R2: Jason Fernando, Investopedia, Sept 21, 2021,
  2. JupyterLab :, An open source data analysis environment for python