Solar Hydronic Project Overview
I built a solar hydronic floor heating system for our house last fall, and after some testing and fine-tuning it’s ready and working great.
The system consists of:
- Four 4x10’ flat panel solar collectors
- A repurposed 80g water heater tank
- Two cirulator pumps: one for the solar panels, and one for the floor hydronics
- An ESP32 controller connected to various temp sensors, light sensor and relays
- An LCD remote display inside the house
- Logging and monitoring sent to ThingSpeak via wifi
System Description and Site
The system heats the 5” concrete slab on the first floor of our small house. Combined with the passive solar design of the house, the thermal mass of concrete walls, plus R-21 to R-52 insulation in the walls and ceiling, this system heats the house without any other heat source required. We’re on a south-facing slope at 4000’ in the Sierra Nevada foothills in California, so we have snow and acutal seasons, but the winters are on the mild-to-medium side.
The panels we used are two different kinds (different manufacturers, slightly different sizes), and 3 of them had the glass either completely or partially broken out. I cobbled together glass from old sliding doors and combined that with some left over double-walled acrylic greenhouse wall material to re-seal the panels. They’re placed at 45° tilt matching the orientation of our house, about 172° AZ. We live in a conifer forest, so trees to block the panels early and late in the day, but overall the site was chosen for excellent exposure. I built a custom rack out of scrap steel to hold the panels.
Drainback Design & Plumbing
I designed this to be a non-pressurized drainback system, meaning that it uses normal water (no anti-freeze) that doesn’t stay in the panels when the sun isn’t shining—but drains back into an insulated tank to avoid freezing and breaking pipes. For this to work, the panels are set at a higher elevation than the tank or the hosue, plus the rack and the bottom of the manifolds in each of the panels are carefully aligned and tilted about 1° down toward the supply side of the plumbing so that gravity will carry all water back downhill when the pump turns off.
The ~80g water tank is from an old Heathkit system—ironically designed for a solar system, but I’m not using it as intended; an old electric water heater tank would work just as well. There are two independent circulation loops in/out of the tank, each with it’s own pump: one loop to and from the solar panels to collect heat, and one to and from the pex tubing in the concrete floor to heat the house. There were two heater element ports in the side of the tank that I removed to use as the outputs for each of the loops: colder water off the bottom of the tank to go up to the panels, and hotter water off the top of the tank to go to the floor. Both loops return to the tank through the ports in the top. There’s also a hot water manifold in the house to distribute and balance the flows through the various zones in the floor.
Controller
The controller is based on an ESP32 with a custom-built shield. There are eight DS18B20 temperature sensors used to monitor heat at various locations; there’s also a photoresistor at the panels to provide sunlight intensity. Two SSRs turn the pumps on and off based on conditions and settings in the logic of the software. There’s a remote display with a 4x20 LCD, two LEDs (showing pump states), a master switch and a ‘house’ temperature sensor. CAT5 connectors and cables are used to make the system a bit more modular during install and testing.
Here’s a list of the sensors:
At the solar panel array:
• Panels interior temperature (inside insulated panel enclosure, not touching copper)
• Ambient exterior temperature (behind panel, in shadow, sheltered from wind)
• Diffused direct sunlight value (inside sealed enclosure with a frosted window)
Inside the mechanical room:
• Water tank interior temperature (hanging inside tank, immersed near top of water)
• Panels loop send temperature (epoxied to brass fitting near exit from tank)
• Paenls loop return temperature (epoxied to copper return line near tank)
• Floor loop send temperature (epoxied to brass fitting near exit from tank)
• Floor loop return temperature (epoxied to brass fitting near tank)
Inside the house:
• Interior air temperature (at the LCD remote)
Software & Logic Design
In theory, this setup is pretty simple: read temperatures from various sensors and decide when to turn on either of the two independent pumps to collect, store or distribute heat. But since I’m not a programmer, and reality is always messier than theory, it took some tweaking and adding some extra temp sensors to arrive at the best working version of this system.
The floor loop was definitely the simpler of the two, so I’ll start with that. Basically the logic looks to see if a) the house needs heat, and b) there’s heat available in the tank; it then turns on the pump (regardless of the state of sunshine or heat in the panels). It uses software-assigned temperature differentials to trigger the pump, and keep it from triggering too often. It uses just two sensors: tank temperature and house temperature. There are copious notes in the sketch explaining this.
The panels loop is more nuanced, with more sophisticated logic to determine when to turn the pump on, when to leave it on, and when to turn it off. It looks for a minimum sunlight value and higher temperatures in the panels than in the tank; once the pump turns on, it will stay on if those conditions persist, but it will stay on as long as the water returning from the panels is hotter than the water going in. Again extensive comments in the software on this.
There are some hard limits on the system running; for example, there’s a master ON/OFF switch at the remote which will shut the whole thing down. There’s also a maximum tank temperature setting, currently 150°F at which point the panels pump will not run under any circumstances.
Logging data is sent to ThingSpeak over wifi. The most relevant eight sensor and SSR values are stored at ~3 minute intervals for remote analysis and review.
Conclusions & Further Thoughts
Basically this system works great. When the sun shines, it works, the house gets warm, and we don’t have to light a fire or think further about it. It’s a magical kind of heat, and lasts for a long time.
Initially I didn’t have temperature sensors on the panels loop; adding those, with the condition that ‘once the pump is on, keep it on as long as the temperature returning from the panels is warmer than the water going in’, really smoothed out frequent pump cycling due to clouds passing, a bit of shadow, etc.
In our case, our house has huge thermal mass with concrete floors and walls; as such, it’s unlikely that we’ll overheat the concrete. Also, I did not install a temperature sensor in the slab itself, which I had intended to do. I can see there would have been issues with it; like how close to set it to existing pipes (a bit of a hazard when installing it to be sure), and how the slow change in heat in that mass would affect pump cycling, etc.
Potential problems: if the sun is shining, but the panels pump isn’t running, the panels can get incredibly hot; if the pump suddenly kicks in, the water will boil in the pipes and there’s a tremendous hammering and pressure buildup as the water returns to the tank. It might be necessary to install some kind of heat diverter at some point, or allow the tank temperature to rise higher—say 180°F; or at least have ThingSpeak send an email when temps rise too high. By this point, if we have these conditions frequently, it’s probably time to just turn the system off for the season: basically covering up the panels with a big piece of shade cloth to avoid cooking everything.
Hope it’s helpful, send me any questions! I'll attach a screenshot of typical ThingSpeak logging, as well as anonymized code for my latest working sketch.
Controller Wiring Diagram
Note that the white LED in the upper left is actually a photoresistor.
Example ThingSpeak Data
Arduino IDE Sketch
/*
This sketch is designed for an ESP32 controller based solar hot water heating system.
The system employs Dallas Temperature sensors to accurately measure temperatures from various components,
including solar panels, a drainback tank, in/out points on the tank and ambient air inside a house.
The system includes a remote display with a 20x4 LCD for real-time information and master on/off switch.
GPIO pins control crucial components such as pumps, LEDs, and the master switch, providing effective system management.
WiFi connectivity enables data transmission to the ThingSpeak platform, where temperatures from different sources,
pump states, and other critical information are systematically analyzed. The code integrates debugging features
to offer comprehensive monitoring through the Serial monitor, ensuring robust performance and operational insights.
Key Components:
- Dallas Temperature sensors for precise temperature measurements.
- Exponentially Weighted Moving Average for temperature data smoothing.
- 20x4 LCD display for real-time information.
- GPIO pins control pumps, LEDs, and the master switch.
- WiFi connectivity for data transmission to ThingSpeak.
- Debugging features for monitoring through the Serial monitor.
Note: The code emphasizes modularity, readability, and efficient task handling to create
a reliable and responsive solar heating system.
ChatGPT assisted in writing this summary, and with cleaning up the code. Damned you AI.
*/
// Set Debug state
const byte Debug = 1; // set to "0" to skip Serial output code of diagnostics.
// WiFi Setup
#include <WiFi.h>
WiFiClient client;
int number = 0; // Counter variable
char ssid[] = "XXXXXXXX"; // Your network SSID (name)
char pass[] = "XXXXXXXX"; // Your network password
// ThingSpeak Setup
#include "ThingSpeak.h"
unsigned long myChannelNumber = XXXXXXXX; // Replace the 0 with your channel number
const char * myWriteAPIKey = "XXXXXXXX"; // Paste your ThingSpeak Write API Key between the quotes
// OneWire and Dallas Temperature Setup
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 21
#define TEMPERATURE_PRECISION 9 // Set resolution on temp sensors: 9 = 0.5°, 10 = 0.25°
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire); // Pass our oneWire reference to Dallas Temperature.
// Exponentially Weighted Moving Average Setup
#include <Ewma.h>
Ewma adcFilterFast(.95); // Range is 0-1, 0 is slow, smooth, 1 is faster, noisier
Ewma adcFilterSlow(0.05); // Range is 0-1, 0 is slow, smooth, 1 is faster, noisier
// 20x4 LCD Setup
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#define SDA 32
#define SCL 33
LiquidCrystal_I2C lcd(0x27, 20, 4); // lcd2004 uses PCF8574T, IIC's address is 0x27
// GPIO Pins
const int SOLAR_PUMP = 23; // Pin for SSR for pump to the solar array
const int SOLAR_PUMP_LED = 19; // Pin for LED indicator for pump to the solar array
const int FLOOR_PUMP = 22; // Pin for SSR for pump to the floor hydronics
const int FLOOR_PUMP_LED = 18; // Pin for LED indicator for pump to the floor hydronics
const int MASTER_SWITCH = 5; // Pin for master ON/OFF switch
#define LIGHT_SENSOR 35 // Set pin for photoresistor
// Temperature Sensors Addresses
uint8_t PANELS_SENSOR[8] = {0x28, 0x39, 0x18, 0x58, 0xD4, 0xE1, 0x3C, 0xC8}; // Inside top of flat panel collector -- not touching plates
uint8_t ARRAY_SENSOR[8] = {0x28, 0xDC, 0x6E, 0x58, 0xD4, 0xE1, 0x3C, 0xFB}; // Shaded exterior under collectors
uint8_t FLOOR_SEND_SENSOR[8] = {0x28, 0xBF, 0xE6, 0x58, 0xD4, 0xE1, 0x3C, 0x94}; // Water pipe leaving mechanical room to floor
uint8_t FLOOR_RETURN_SENSOR[8] = {0x28, 0x1C, 0xE4, 0x58, 0xD4, 0xE1, 0x3C, 0x12}; // Water pipe returning from floor
uint8_t PANELS_SEND_SENSOR[8] = {0x28, 0x6F, 0xF4, 0x80, 0xE3, 0xE1, 0x3C, 0x16}; // Water pipe leaving mechanical room to collectors
uint8_t PANELS_RETURN_SENSOR[8] = {0x28, 0xFA, 0xB6, 0x80, 0xE3, 0xE1, 0x3C, 0x26}; // Water pipe returning from collectors
uint8_t TANK_SENSOR[8] = {0x28, 0xAA, 0x7A, 0x53, 0x4D, 0x14, 0x01, 0xA5}; // Water in drainback tank
uint8_t HOUSE_SENSOR[8] = {0x28, 0x56, 0xC1, 0x58, 0xD4, 0xE1, 0x3C, 0x82}; // Ambient air inside the house
// Other Constants
int PANELS_DIFF; // Stores changing panel temp differential
int FLOOR_DIFF; // Stores changing floor temp differential
const int PANELS_LOOP_DIFF = 3; // °F diff required between panels send/return temps to keep solar pump on
const int PANELS_THRESHOLD = 90; // Temp above which SOLAR_HEAT_AVAILABLE check is simplified, almost forced to 1
const int FLOOR_DIFF_OFFSET = 1; // Amount temp differential will vary up/down during pump state changes
const int FLOOR_DIFF_DEFAULT = 8; // Default temp diff
const unsigned long REFRESH_DELAY = 3000; // Delay between main loop repeats
const int TS_COUNTER_MAX = 225; // Countdown timer total; compounds with REFRESH_DELAY to set Thingspeak trigger
const int TARGET_TEMP = 90; // Max interior house temp
const int TANK_TEMP_MAX = 150; // Sets upper cutoff temp for tank
const int LIGHT_MIN = 1000; // Minimum amount of sunlight to activate panels
// Variables
int PANELS_TEMP_RAW; // Current temp of collectors - sensor
int PANELS_TEMP; // Current temp of collectors - filtered
int ARRAY_TEMP; // Current temp of shaded exterior
int FLOOR_SEND_TEMP; // Current temp of water exiting tank to floor
int FLOOR_RETURN_TEMP; // Current temp of water returning from floor
int PANELS_SEND_TEMP; // Current temp of water exiting tank to panels
int PANELS_RETURN_TEMP; // Current temp of water returning from panels
int TANK_TEMP_RAW; // Current temp of water in drainback tank - sensor
int TANK_TEMP; // Current temp of water in drainback tank - filtered
int HOUSE_TEMP; // Current temp of ambient house air
int TS_COUNTER; // Used as a ThingSpeak polling countdown timer variable
int FLOOR_PUMP_STATE; // Store for pump state
int SOLAR_PUMP_STATE; // Store for pump state
int MASTER_SWITCH_STATE; // Store for switch state
int LIGHT_VALUE_RAW; // Set variable for raw intensity of daylight
int LIGHT_VALUE; // Filtered version of above
byte SOLAR_HEAT_AVAILABLE; // Flag to indicate if PANELS are hot enough to provide heat
byte TANK_HEAT_AVAILABLE; // Flag to indicate if water in TANK is hot enough to provide heat to the house
byte HEAT_WANTED; // Flag to indicate if heat is wanted in the house
void setup() { // Setup runs once at powerup or after RESET:
Serial.begin(19200); // Initialize serial communication at X bits per second if Debug is ON
WiFi.mode(WIFI_STA); // Set Wifi Mode
ThingSpeak.begin(client); // Initialize ThingSpeak
pinMode(SOLAR_PUMP, OUTPUT); // Set mode of SSR I/O pin
pinMode(SOLAR_PUMP_LED, OUTPUT); // Set mode of LED I/O pin
pinMode(FLOOR_PUMP, OUTPUT); // Set mode of SSR I/O pin
pinMode(FLOOR_PUMP_LED, OUTPUT); // Set mode of LED I/O pin
digitalWrite(SOLAR_PUMP, LOW); // Start with pumps and LEDs off
digitalWrite(SOLAR_PUMP_LED, LOW); // Start with pumps and LEDs off
digitalWrite(FLOOR_PUMP, LOW); // Start with pumps and LEDs off
digitalWrite(FLOOR_PUMP_LED, LOW); // Start with pumps and LEDs off
pinMode(MASTER_SWITCH, INPUT_PULLUP); // Set master switch internal pull up register
FLOOR_DIFF = FLOOR_DIFF_DEFAULT; // Establish base heat differential for pump trigger
SOLAR_HEAT_AVAILABLE = 0; // Start with SOLAR_HEAT_AVAILABLE in the Off condition
HEAT_WANTED = 0; // Start with HEAT_WANTED in the Off condition
TANK_HEAT_AVAILABLE = 0; // Start in the OFF condition
SOLAR_PUMP_STATE = 0; // Start with pumps off
FLOOR_PUMP_STATE = 0; // Start with pumps off
TS_COUNTER = TS_COUNTER_MAX; // Reset countdown timer for Thingspeak
Wire.begin(SDA, SCL); // attach the IIC pin for LCD
lcd.init(); // LCD driver initialization
lcd.noBacklight(); // Turn OFF LCD backlight
ReadSensors(); // Read sensors
delay(1000); // Wait a sec
}
void loop() {
// Main program loops forever
connectWifi(); // Connect or re-connect to WiFi
ReadSensors(); // Runs sub-routine to read sensors and store current temps @ °F
CheckHeatStatus(); // Check heat: house need and panel heat availability
if (MASTER_SWITCH_STATE == LOW) {
// Run pumping assessment ONLY if master switch is ON (LOW)
if ((LIGHT_VALUE > 100) || (FLOOR_PUMP_STATE == 1)) {
// Turn on the backlight during daylight or if floor pump is still on
lcd.backlight(); lcd.display();
} else {
// Otherwise turn it off to show nothing is running and it's dark out
lcd.noBacklight(); lcd.noDisplay();
}
if (SOLAR_PUMP_STATE == 0) {
// If pump isn't running, perform unique checks to see if conditions warrant turning it on
if (SOLAR_HEAT_AVAILABLE && (TANK_TEMP < TANK_TEMP_MAX)) {
// If heat is available and tank isn't at max, turn on solar pump & LED
digitalWrite(SOLAR_PUMP, HIGH); // Turn on pump, LED, state variable
digitalWrite(SOLAR_PUMP_LED, HIGH);
if (SOLAR_PUMP_STATE == 0) {
// If pump state is changing, logs with Thingspeak
sendtoThingSpeak();
if (Debug) {
Serial.println("Turning SOLAR_PUMP on");
}
}
SOLAR_PUMP_STATE = 1;
delay(120000);
// Delay allows water to circulate and panel send/return temps to equalize
} else {
digitalWrite(SOLAR_PUMP, LOW); // Turn off pump, LED, state variable
digitalWrite(SOLAR_PUMP_LED, LOW);
if (SOLAR_PUMP_STATE == 1) {
// If pump state is changing, logs with Thingspeak
sendtoThingSpeak();
if (Debug) {
Serial.println("Turning SOLAR_PUMP off");
}
}
SOLAR_PUMP_STATE = 0;
}
} else {
// Since solar pump is already running, check unique conditions to see if it should turn off
if ((TANK_TEMP <= TANK_TEMP_MAX) || (PANELS_RETURN_TEMP >= (PANELS_SEND_TEMP + PANELS_LOOP_DIFF))){
// If tank is at max temp, or the difference between the panels send/return temp drops below threshhold, turn off pump
digitalWrite(SOLAR_PUMP, LOW); // Turn off pump, LED, state variable
digitalWrite(SOLAR_PUMP_LED, LOW);
if (SOLAR_PUMP_STATE == 1) {
// If pump state is changing, logs with Thingspeak
sendtoThingSpeak();
if (Debug) {
Serial.println("Turning SOLAR_PUMP off");
}
}
SOLAR_PUMP_STATE = 0;
}
}
if (HEAT_WANTED && TANK_HEAT_AVAILABLE) {
// If tank is warmer than house, and heat is wanted, turn on FLOOR_PUMP
digitalWrite(FLOOR_PUMP, HIGH); // Turn on pump, LED, state variable
digitalWrite(FLOOR_PUMP_LED, HIGH);
if (FLOOR_PUMP_STATE == 0) {
// If pump state is changing, logs with Thingspeak
sendtoThingSpeak();
if (Debug) {
Serial.println("Turning FLOOR_PUMP on");
}
}
FLOOR_PUMP_STATE = 1;
FLOOR_DIFF = (FLOOR_DIFF_DEFAULT - FLOOR_DIFF_OFFSET); // Offsets temp at which pump will switch, reducing cycling
} else {
digitalWrite(FLOOR_PUMP, LOW); // Turn off pump, LED, state variable
digitalWrite(FLOOR_PUMP_LED, LOW);
if (FLOOR_PUMP_STATE == 1) {
// If pump state is changing, logs with Thingspeak
sendtoThingSpeak();
if (Debug) {
Serial.println("Turning FLOOR_PUMP off");
}
}
FLOOR_PUMP_STATE = 0;
FLOOR_DIFF = (FLOOR_DIFF_DEFAULT + FLOOR_DIFF_OFFSET); // Offsets temp at which pump will switch, reducing cycling
}
if (TS_COUNTER == 0) {
// Reset the counter once it reaches zero
TS_COUNTER = TS_COUNTER_MAX;
}
TS_COUNTER = TS_COUNTER - 1; // Reduce countdown timer for thingspeak
} else {
// This happens if Master Switch is OFF -- basically the system sleeps
lcd.noBacklight(); lcd.noDisplay(); // Turn OFF LCD backlight & display
digitalWrite(SOLAR_PUMP, LOW); // Confirm pump & LED off
digitalWrite(SOLAR_PUMP_LED, LOW);
SOLAR_PUMP_STATE = 0;
digitalWrite(FLOOR_PUMP, LOW); // Confirm pump & LED off
digitalWrite(FLOOR_PUMP_LED, LOW);
FLOOR_PUMP_STATE = 0;
FLOOR_DIFF = FLOOR_DIFF_DEFAULT; // Reset base heat differential for pump trigger
if (Debug) {
Serial.println("Master Switch is OFF");
}
}
UpdateLCD(); // Update remote display
delay (REFRESH_DELAY); // Delay between repeats of program loop
if (Debug) {
DebugVerbose();
} // Send debug data to serial monitor
if ((MASTER_SWITCH_STATE == LOW) && (TS_COUNTER == 0)) {
// Transmit to ThingSpeak when switch is on and countdown timer is zero
sendtoThingSpeak();
// This allows LCD refresh to happen at a different rate
TS_COUNTER = TS_COUNTER_MAX; // Reset counter
lcd.init(); // Reset LCD driver to fix any I2C comms corruption
}
}
// ------------------------- Functions below ---------------------------------
void ReadSensors() {
// Read current sensors and store values in Temp variables in °F
MASTER_SWITCH_STATE = digitalRead(MASTER_SWITCH); // Check if Master Switch is on or off
sensors.requestTemperatures(); // Reads all temp sensors using Dallas Temp Library
PANELS_TEMP_RAW = sensors.getTempF(PANELS_SENSOR); // Read and store raw PANELS temp
PANELS_TEMP = adcFilterFast.filter(PANELS_TEMP_RAW); // Filter temp
ARRAY_TEMP = sensors.getTempF(ARRAY_SENSOR); // Read and store ARRAY temp
FLOOR_SEND_TEMP = sensors.getTempF(FLOOR_SEND_SENSOR); // Read and store floor SEND temp
FLOOR_RETURN_TEMP = sensors.getTempF(FLOOR_RETURN_SENSOR); // Read and store floor RETURN temp
PANELS_SEND_TEMP = sensors.getTempF(PANELS_SEND_SENSOR); // Read and store panels SEND temp
PANELS_RETURN_TEMP = sensors.getTempF(PANELS_RETURN_SENSOR); // Read and store panels RETURN temp
TANK_TEMP_RAW = sensors.getTempF(TANK_SENSOR); // Read and store raw TANK temp
TANK_TEMP = adcFilterFast.filter(TANK_TEMP_RAW); // Filter temp
//SLAB_TEMP = sensors.getTempF(SLAB_SENSOR); // Read and store SLAB temp
HOUSE_TEMP = sensors.getTempF(HOUSE_SENSOR); // Read and store HOUSE temp
LIGHT_VALUE_RAW = analogRead(LIGHT_SENSOR); // Read and store raw light value
LIGHT_VALUE = adcFilterSlow.filter(LIGHT_VALUE_RAW); // Filter light value
}
void CheckHeatStatus() {
// Performs checks to see what heat is available and what is needed
// First determine house heat need status
if (HOUSE_TEMP < TARGET_TEMP) {
// If house temp is below target set heat wanted flag
HEAT_WANTED = 1;
} else {
// House is hot enough, no more heat needed
HEAT_WANTED = 0;
}
// Next determine panels heat status
if (((PANELS_TEMP >= PANELS_THRESHOLD) && (LIGHT_VALUE > LIGHT_MIN)) || ((PANELS_TEMP > (TANK_TEMP + PANELS_DIFF)) && (LIGHT_VALUE > LIGHT_MIN))) {
// If panels are hotter than threshold temp & light OR hotter than the tank & differential & light
SOLAR_HEAT_AVAILABLE = 1; // Set SOLAR_HEAT_AVAILABLE flag to 1, or YES
} else {
// Panels aren't hot enough, no heat available
SOLAR_HEAT_AVAILABLE = 0; // Set SOLAR_HEAT_AVAILABLE flag to 0, or NO
}
// Lastly determine tank heat status
if (TANK_TEMP > (HOUSE_TEMP + FLOOR_DIFF)) {
// If TANK temp is higher than the house, heat is availabe
TANK_HEAT_AVAILABLE = 1;
} else {
// TANK isn't hot enough
TANK_HEAT_AVAILABLE = 0;
}
}
void UpdateLCD() {
// Refresh LCD Display
lcd.setCursor(0, 0); lcd.print("SUN: ");
lcd.setCursor(5, 0); lcd.print(" ");
lcd.setCursor(5, 0); lcd.print(LIGHT_VALUE);
lcd.setCursor(12, 0); lcd.print("OUT: ");
lcd.setCursor(17, 0); lcd.print(" ");
lcd.setCursor(17, 0); lcd.print(ARRAY_TEMP);
lcd.setCursor(0, 1); lcd.print("PN:");
lcd.setCursor(3, 1); lcd.print(" ");
lcd.setCursor(3, 1); lcd.print(PANELS_TEMP);
lcd.setCursor(7, 1); lcd.print("TK:");
lcd.setCursor(10, 1); lcd.print(" ");
lcd.setCursor(10, 1); lcd.print(TANK_TEMP);
lcd.setCursor(14, 1); lcd.print("HS:");
lcd.setCursor(17, 1); lcd.print(" ");
lcd.setCursor(17, 1); lcd.print(HOUSE_TEMP);
lcd.setCursor(0, 2); lcd.print("Panels Loop:");
lcd.setCursor(13, 2); lcd.print(" /");
lcd.setCursor(13, 2); lcd.print(PANELS_SEND_TEMP);
lcd.setCursor(17, 2); lcd.print(" ");
lcd.setCursor(17, 2); lcd.print(PANELS_RETURN_TEMP);
lcd.setCursor(0, 3); lcd.print("Floors Loop:");
lcd.setCursor(13, 3); lcd.print(" /");
lcd.setCursor(13, 3); lcd.print(FLOOR_SEND_TEMP);
lcd.setCursor(17, 3); lcd.print(" ");
lcd.setCursor(17, 3); lcd.print(FLOOR_RETURN_TEMP);
}
void DebugVerbose() {
// Function prints debug data from sensors and various stored states to serial monitor
Serial.print("Master Switch = "); Serial.println(byte(MASTER_SWITCH_STATE));
Serial.print("Sunlight Value = "); Serial.println(int(LIGHT_VALUE));
Serial.print("Panels Temp = "); Serial.println(int(PANELS_TEMP));
Serial.print("Array Temp = "); Serial.println(int(ARRAY_TEMP));
Serial.print("Tank Temp = "); Serial.println(int(TANK_TEMP));
Serial.print("Pnl Send Temp = "); Serial.println(int(PANELS_SEND_TEMP));
Serial.print("Pnl Rtn Temp = "); Serial.println(int(PANELS_RETURN_TEMP));
Serial.print("Flr Send Temp = "); Serial.println(int(FLOOR_SEND_TEMP));
Serial.print("Flr Rtn Temp = "); Serial.println(int(FLOOR_RETURN_TEMP));
Serial.print("House Temp = "); Serial.println(int(HOUSE_TEMP));
Serial.print("Solar Heat? = "); Serial.println(byte(SOLAR_HEAT_AVAILABLE));
Serial.print("Tank Heat? = "); Serial.println(byte(TANK_HEAT_AVAILABLE));
Serial.print("Heat Wanted? = "); Serial.println(byte(HEAT_WANTED));
Serial.print("Solar Pump = "); Serial.println(byte(SOLAR_PUMP_STATE));
Serial.print("Floor Pump = "); Serial.println(byte(FLOOR_PUMP_STATE));
Serial.print("Countdown = "); Serial.println(int(TS_COUNTER));
Serial.print("Floor Diff = "); Serial.println(int(FLOOR_DIFF));
Serial.println("———————————————————————————");
}
void connectWifi() {
// Connects ESP32 to wifi
if (WiFi.status() != WL_CONNECTED) {
if (Debug) Serial.print("Attempting to connect to SSID: ");
if (Debug) Serial.println(ssid);
while (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid, pass);
if (Debug) Serial.print(".");
delay(5000);
}
if (Debug) Serial.println("\nConnected.");
}
}
void sendtoThingSpeak() {
// Send various values and states to ThinkSpeak channel
ThingSpeak.setField(1, PANELS_TEMP);
ThingSpeak.setField(2, TANK_TEMP);
ThingSpeak.setField(3, HOUSE_TEMP);
ThingSpeak.setField(4, PANELS_SEND_TEMP);
ThingSpeak.setField(5, PANELS_RETURN_TEMP);
ThingSpeak.setField(6, SOLAR_PUMP_STATE);
ThingSpeak.setField(7, FLOOR_PUMP_STATE);
ThingSpeak.setField(8, LIGHT_VALUE);
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey); // write to the ThingSpeak channel
if (x == 200) {
if (Debug) Serial.println("Channel update successful.");
} else {
if (Debug) Serial.println("Problem updating channel. HTTP error code " + String(x));
}
}