The pool controller logic is implemented in Node Red on a Raspberry Pi. Node Red is a system that allows you to create logic flows graphically. The graphical representation makes it easy to understand the flow. You have to do a bit of programming in the nodes but that is not hard.
The UI is implemented with a set of Node Red nodes called the Node Red Dashboard. These nodes provide the dropdown that lets you choose pool, spa or off, the switches to turn on the cleaner, blower, lights and heater, the gauges to show water and air temperature, and the numeric input that lets you set the pool and spa temperatures. It automatically formats the various controls so it looks nice and works properly on a desktop computer web browser or on a smartphone browser.
The model for this flow is that inputs that come in via the serial port from the Arduino and from the various Node Red Dashboard controls set the state of the system. Periodically, a separate process reads the current state of the system, figures out what needs to be set on the Arduino and sends the appropriate commands as a serial message to the Arduino.
For example, there is a switch in the UI called Cleaner. When that switch is turned on, it sets a variable called CleanerSetting to true. There is a node called PoolControlHearbeat that reads the value of CleanerSetting. If it is true, it sends a message to the Arduino to turn on the relay for the cleaner.
If you have not used a Raspberry Pi before, there are many sources that describe how to get one up and running, like this Instructables class. You need to install Raspbian with the desktop so that you can perform the following steps and you will need to connect it to a monitor, keyboard and mouse to set this up, but after it is all set up you will not need the monitor, keyboard or mouse. You probably also want to set it to have a static IP so that you can access it from other devices in your home easily, and if you are going to run it headless, i.e., without monitor, keyboard or mouse, then you will need to enable ssh access (select Raspberry Pi config from the main menu to find these options) so you can access it if you ever need to power it down cleanly (it's not a good idea to just pull the power plug on a Raspberry Pi). For security reasons, I set mine up so that it is only accessible from my home intranet (not from the outside internet). Also, use Raspberry Pi config to ensure logins over the serial port are disabled (for two reasons: you will be using it to communicate with the Arduino so you do not want it to be sending login prompts to the serial port, and so that someone cannot unplug the USB cable and login to it from outside your house).
Lastly, install the Chromium browser and use that for Node Red; as of this writing, Chromium works best with Node Red. After you have set everything up and are logged in, check the main menu - Node Red should be an option on the Programming menu. Select that and let it start to make sure it works. You will need to perform a step to make Node Red start when the Raspberry Pi is booted (so you will not need to start it from the menu every time you restart the Raspberry Pi). Execute the following command in a Terminal window:
sudo systemctl enable node-red.service
Then, reboot. (Note: you need to start it as a service before you import the flow below, otherwise it will not find the flow you imported, so enable it as a service as just described before continuing.) Open the Chromium browser and connect to the address http://localhost:1880. That will open the Node Red workspace in your browser. Check if Node Red Dashboard is installed because it might not be installed by default. Open the menu (hamburger icon at the upper right), then select Manage Palette. Under Nodes, search for node-red-dashboard. If it is not there, go to Install, search for and install it. Once it is installed, you will see a set of nodes under the category dashboard with names like button, switch, gauge, chart.
You can now import the entire flow shown graphically in the attached picture by importing the file attached to this Instructable is called PoolControlFlow.txt. Download it and put it on a USB drive that you insert into your Raspberry Pi. Go to the Node Red menu on the browser page and choose Import. Navigate to the PoolControlFlow.txt file and select it. That will import the entire flow that runs this pool controller, so you don't have to program it all yourself.
Test that you can access the UI is accessible from the browser, too. Open the URL http://localhost:1880/ui. If you set a static IP, try accessing the UI from another computer or smartphone by substituting the IP address for the word localhost in the URL. If you have an Arduino on a breadboard for testing as described earlier, then you can connect it with a USB cable and try the various controls to see the LEDs go on and off (note that and LED on corresponds to the relay being off, and vice versa - that's how the relay board works). You can also connect a couple of spare thermistors for testing that the temperature gauges work.
Node Red Flow Description
If all is working, then you don't need to read this section, but if you are interested, this explains how the various parts of the Node Red flow work.
The ArduinoSerialGet node listens on the serial port for messages from the Arduino. Those messages have a keyword, one of H2O or AIR, followed by a colon and a number that is the value. The SerialMsgRouter node routes the message to one of two nodes corresponding to the keyword. The string is converted to a value that is sent to a gauge on the UI and a node that sets the value of a variable, H2OTemp or AIRTemp.
There are two nodes that set the default temperature settings for the pool, DefaultPoolTemperature (set in the node to 82) and DefaultSpaTemperature (set in the node to 100). Those values feed into the numeric controls on the UI so they have an initial value and also into the PoolTempSetter and the SpaTempSetter which set variables with the values. These are like thermostat settings, they are the temperature you want the pool water to be, while the H2OTemp is the temperature that the pool water is. The RelaySetter (described below) uses these to decide when to turn on the heater.
The Light switch turns the lights in the pool and spa on or off. The can be turned on whether the pool or spa is running or not.
The HeaterEnable switch does not directly turn on the heater. If it is off, then heater will not turn on in any situation. If it is on, then the heater will turn on if needed (see below). This allows you to maintain your temperature settings but not heat the pool until you decide to enable the heater.
The Cleaner switch turns on the automatic cleaner which is driven by a water line from the pump. That is controlled by a valve actuator.
The Blower switch turns on the air blower which adds bubbles to the spa. The blower is an electric motor.
The dropdown Off/Pool/Spa can be set to one of three values: Off, Pool or Spa. This sets a variable, PoolSpaMode to one of those values. This mode is also part of what is used in the RelaySetter to determine when to turn on the heater. The mode also enables or disables certain controls. If the pool is on, then you do not want the air blower for the spa to be turned on. Conversely, if the spa is on, then you do not want the cleaner to be turned on. So, whenever the pool is turned on, it turns off the blower switch and disables it, and also enables the cleaner switch. Whenever the spa is turned on, it turns off the cleaner and disables it, and also enables the air blower. Whenever the Off setting is chosen, it turns off both the cleaner and blower.
The PoolControlHeartbeat periodically sends a message to the RelaySetter which triggers it to check the settings of the cleaner, blower, lights and send the corresponding relay settings to the Arduino. The RelaySetter turns on the pump relay if the mode is Pool or Spa and turns it off if the mode is Off. The RelaySetter also turns on the heater if necessary. It does this by first checking to see if the HeaterSetting is on, i.e., if the heater is enabled. If the heater is enabled, then it determines what is the appropriate temperature setting - if the mode is Pool, then it uses the PoolTempSetting value; if the mode is Spa, then it uses the SpaTempSetting value. It compares that value to the H2OTemp value, i.e., the current water temperature. If the current water temperature is below the setting, then it turns on the relay for the heater, otherwise it turns it off. Lastly, the RelaySetter sets the intake and return valve actuator relays to off if the mode is Pool and on if the mode is Spa.
Once all relay values are known, a single byte is created where each bit corresponds to a relay and where the bit is set (value 1) to turn on the relay and the bit is unset (value 0) to turn off the relay. This byte is sent as a serial message to the Arduino by the ArduinoSerialSet node.