Mobile Blazor Bindings allows you to write applications in HTML, CSS and C# that run inside a web browser, on iOS, macOS, Android, Windows with a single code base.
This was something we did not have in .NET until MBB came along, so I’m very grateful to Steve and Eilon for this. Based on MBB, Bootstrap and Blazorise I was able to quickly whip up this mock-up that is already componentized. It will serve as the basis for the UI of the machine and is pretty close to the sketches I drew earlier:
Another pet peeve of mine is that I like functional and reactive programming and have to bring it up in every programming conversation. This won’t be any different. It’s so powerful to leave the imperative world and think in composing higher order functions or data streams. For this project I’ll be building the app with ReactiveUI (by Anaïs Bets) as there is a lot of real-time data that flows from the machine and needs to be displayed in a pretty way. ReactiveUI can transparently signal Blazor witch parts of the UI need to redraw, which ideally should save me some headache there.
ReactiveUI works best with an MVVM tiering which is what I will be using for the app too. Using ViewModels and Interfaces I’ll also be much more able to write unit tests for the application and create a simulation in C# of the electronics before I actually start working on them.
So to sum up the challenge:
- Use Mobile Blazor Bindings
- Use Bootstrap / Font Awesome (yes, uninspiring, but it will suffice).
- Use ReactiveUI
- Use ReactiveX (reactive programming library for i.a. C#)
- Use MVVM pattern
- Unit test everything.
- Create a simulation of the electronics in C#.
Doesn’t sound too difficult right?
The Service interfaces
I’ve decided on representing the different parts of the espresso machine in .NET even though technically the app just talks against an MQTT broker and a web server. This allows me to functionally group and control parts of the machine that belong together. The parts of the espresso machine (and grinder) that I want to control are:
- The power circuit that will turn on/off the machine and allows me to get the power usage and energy consumption.
- The boiler circuit that will allow me to control boiler temperature and pressure. I might add heat exchanger temperature as a sensor to this.
- The grinder circuit that will be responsible for turning the grinder on and off and allows me to get power usage and energy consumption again.
- The maintenance circuit that will control blind flushes and group head flushes.
- The scale circuit that will control the electronic scale that will be integrated into the drip tray.
- The espresso circuit that will control the group head temperature, preinfusion time, monitor water pressure, take you through the cooling flush and finally make an espresso based on the provided parameters.
In addition to the machine it self we also need a place to store and interact with the recipes. The recipe and photo services will take care of this. This leads to the following class diagram.
I’ll discuss each interface and code below:
will provide observables for the power state (on/off) and power usage (in watts). It will also provide the methods to turn the machine on or off. The cancellation tokens will appear frequently in all method calls. All methods are asynchronous because of the multiple boundaries a method call will cross. The methods only return when confirmation is received of retrieval of the command by the hardware. If for any reason this communication hangs (i.e. no confirmation received), the cancellation token provides a way out by cancelling the command after three seconds.
provides observables for boiler pressure, target pressure, boiler temperature, protection and heating state (and a few offsets for calibration). The Nuova Simonelli Mac is an heat exchanger machine. This means that there is a single boiler that provides steam for cappuccino. To heat the water for espresso, fresh water is transported through pipes inside the boiler that will flash-heat it to the 93-ish °C. The group head will provide the final heat up / cool down to the right temperature to achieve temperature stability. This means that you only have indirect control of the brew temperature by either adjusting the boiler temperature (typically 124°C) or cooling down the group head by flushing water through it (called a cooling flush). And since measuring temperature accurately inside a boiler is no easy feat (at least it wasn’t 20 years ago when this machine was designed), the designers of my machine (and many others) used a bit of physics to control another variable that is related to temperature: pressure. I’ll devote some more time to the relation between water temperature and steam pressure in a future post about the electronics simulation. For now it is enough to know that inside the boiler a temperature directly corresponds to a pressure. My machine uses a pressure switch to control pressure, which in turn controls boiler temperature, which indirectly controls brew temperature. 1.2barG (I’ll get to barG in a subsequent post as well) is the default pressure for espresso machines. The disadvantage of the mechanical pressure switch and especially my aging switch is a considerable dead band (between 1.0 and 1.5 actually, used to be 1.1 and 1.3). This would correspond to temperature changes between 120 and 128 °C. These boiler temperature fluctuations are dampened a bit by the large group head, but by no means is an 8° difference ideal. This is why I want to use a PID algorithm to control the steam pressure. I might end up controlling the boiler water temperature directly in the future, in which case I’d have to change the
TargetPressure property and
SetTargetPressure method to
The arduino will limit the pressure to 1.99 barG and 133 °C (whichever comes first) during normal operation. You’d probably never want to reach these temperatures because it will probably burn the coffee. The electronics circuit will trigger a soft protection once the boiler water reaches 135°C. This soft protection can be reset by calling
ResetProtection on this interface. Hopefully the large copper surface of the boiler and tubing will prevent the thermal circuit breaker on the outside of the boiler (rated at 135°C) from triggering before the software protection inside the Ardiuno triggers.
Stay tuned for part 2 of this post.