Project showcase
LoadMaster XP - A Smart PV MPPT Solar Hot Water Controller

LoadMaster XP - A Smart PV MPPT Solar Hot Water Controller © GPL3+

A solid-state, low-cost, reliable solution for solar hot water from PV. Zero maintenance & CO2, connected, smart combi pre-heat control.

  • 1,994 views
  • 8 comments
  • 17 respects

Components and supplies

Apps and online services

About this project

Introduction

Solar heating of domestic hot water has traditionally been achieved using ‘Solar Thermal’ technologies (Flat plate panels or evacuated tube collectors). Whilst Solar thermal is more space efficient than Photo-voltaic (PV) in terms of the captured energy per unit area, it typically has high installation costs, is less effective during cold weather or overcast skies, has a shorter life span and can present a number of ongoing maintenance challenges and costs (mechanical pumps, antifreeze & corrosion chemicals, leaks, positioning in roof space and routing of pipework etc).

Where a larger solar collection area is available (rarely a limitation in the majority of installations), then the use of PV panels now offers a more cost effective, near zero maintenance and better performing ‘solid state’ alternative approach to solar water heating. The rapid and continued decline in PV panel costs has been instrumental in making PV hot water an attractive and economically viable approach. Loadmaster is designed to sit between an array of PV panels and a conventional electric immersion heater and is designed to moderate the load impedance of the heating element in order to maximise the collection efficiency of PV electrical energy regardless of the sky conditions.

PV systems are also more versatile than solar thermal ones, any excess electrical energy may easily be used for a variety of other purposes. LoadMaster includes an optional second output allowing excess solar energy to be diverted to electric baseboard heaters (cheap) and used for space heating once the water has reached its maximum temperature.

Where a property has a full 'Grid tied' solar electric installation, a number of commercial products are available to divert surplus electricity to the water immersion heater rather than feed it into the grid. The complexity and costs of a grid-tied system lengthens the pay back time and for many people connecting to the grid is beyond the scope of a DIY project. Any Solar system is much more expensive if you pay someone else to install it!

The LoadMaster PV hot water approach shown here offers a significantly simpler and cheaper solution ideally suited to competent DIY-er. This is a 'Fit and Forget' project with virtually zero maintenance, occasional cleaning of panels being the only maintenance need.

The LoadMaster project discussed here has two additional 'add-on' project pages, - one project adds remote internet connectivity using a DT-06 (or ESP8266-01S) Wifi-Serial terminal, and another project details the methods used to remotely display Loadmaster's operating status on a remote located, wireless connected Nextion display:-

PV Hot Water, MPPT basics!

To heat water, the electrical energy generated by the PV panels must be delivered to the electric heating element(s) in a standard hot water cylinder. Sounds simple? well, unfortunately for efficient and safe operation it's not quite as simple as connecting a heating element (and thermostat) to the DC output of your solar array.

Connecting the high voltage DC output from your PV array to a standard AC rated mechanical thermostat may lead to arcing and welding of contacts, potentially seriously compromising the safety of a hot water cylinder. Additionally, in real-world varying and non perfect sky conditions (i.e most of the time!) there will frequently be a major mismatch of the heaters load resistance to the arrays ideal max power load. This results in a loss of potential heating power, a very poor efficiency and is a ridiculous waste of your investment in PV panels'.

Let's explain the importance of maximum power point tracking using my my 3kW array as an example (2 parallel strings each comprised of 5x REC Solar 300W TP2 panels in series):-

In full sunshine the array will have a voltage to current relationship similar to that shown by the blue curve in the graph below (the V-I response graph for a single panel is usually shown in it's data sheet). The ratio of Voltage to Current at any point on the graph implies a connected load resistance of V/I ohms as shown by the green trace.

Multiplying Voltage and Current at all points along the V – I curve will show the array’s output power curve (Red trace). We can clearly see there is a peak in the arrays output power (at the vertical line) corresponding to a specific voltage and current. This is called the maximum power point. In this example, to operate at the maximum power point and thus deliver 3kW we require a load of 162V / 18.48A = 8.8Ω.

So, now lets look at the array's characteristic at about half the solar intensity (i.e it's cloudy, not mid-day etc):-

This time we can see the peak power is delivered at a slightly lower voltage and the current is approximately half of that during full sunshine. During this reduced sunshine condition, for the array to operate at its peak power point and deliver 1.47kW we now require a matched load impedance of 160V / 9.24A = 17.3Ω .

If we still had the original 8.8Ω load connected then from this graph we can see an 8.8Ω load will pull the array voltage down to only 85V and will only produce an output power of approx 834W in this 8.8Ω load instead of the 1.47kW we could potentially have if a matched load of 17.3Ω was connected !!. We have just lost 636W due to having the wrong load impedance to suit the reduced solar conditions, - the power of equivalent to two of my 300W PV panels has simply been lost by having the wrong load impedance!.

The effective loss in power due to a mis-matched load becomes extreme when solar conditions are not at maximum, (i.e when it isn't noon on a clear sunny day, - which is most of the time!! ).

Circuits such as the LoadMaster are designed to provide Maximum Power Point Tracking (MPPT), they continuous adjust the effective load impedance presented to the solar panels such that the maximum available power is always extracted under any solar conditions and at any time of the day.

LoadMaster has and will continue to give us free hot water for a total outlay of << £1, 800 (3kW of new PV panels, 150L cylinder, roof rails etc plus some bargain hunting). PV Panels have a 25 year life guarantee and there is no reason this systems will not last beyond 25 years.

My system is configured to divert water <40°C via a pre-heat compatible LPG Combi boiler. Even in November in the UK, a 3kW array provides a high percentage of our hot water usage (2 person household). Of course, on heavily overcast, rainy days solar output will be low.

Hot water typically represents about 25-30% of a households total annual energy budget. This is a straight-forward, cost effective project for PV Hotwater, It has no Grid connection issues and is within the capability of someone competent DIYer with some electronics skills. LoadMaster is small step to protecting our fragile and over populated planet. Our planets climate is on fire. What are you doing to help?

A spreadsheet can be downloaded (at end) to estimate your likely hot water performance for a given PV array size and your location.

Loadmaster Specifics

The LoadMaster Project is based upon an Arduino Nano and the circuits basic architecture is shown below:-

C1 is a high voltage film type capacitor effectively connected across the solar array. A small value shunt resistor and voltage divider enable the Arduino to monitor the current and voltage (and hence power) being generated by the PV array.

As we know, the impedance presented by a capacitor depends upon it's state of charge. When an uncharged capacitor is initially connected it will have a high charge current and hence a low impedance, once fully charged a capacitor will present a high impedance. Ultimately for MPPT operation we wish hold C1 at a state of charge which presents an impedance perfectly matched to the prevailing maximum power point conditions of the PV array. To achieve this, the Arduino rapidly switches the load on and off (using PWM at 5kHz) varying the ON:OFF ratio to effectively 'lighten the load' when sky conditions are less than optimal. In technical terms, the ON:OFF switching times of the heater load are adjusted such that just the right amount of charge (I x t) is removed from C1 to hold the array at the maximum power point. LoadMaster monitors the PV output Voltage and Current ( V x I = Power) and adjusts the ON:OFF ratio 10 times a second using a 'Perterb an Observe' (P&O) method to track the maximum power under any sky conditions.

To minimise losses, the MOSFET is switched on and off ‘hard’ (using a strong gate drive). Such fast switching edges are notorious for radiating RF noise. You certainly can’t connect such high power, sharp switching edges to a length of cable (i.e an antenna) without the risk of upsetting your wifi, local radio station, neighbors or radio regulatory body.

The Inductor and C2 provide filtering to limit dI/dt and dV/dt on the external cables. D1 is used to catch the back emf spike and energy released by the inductor when the MOSFET switches off. Whilst this arrangement may look like a low side switched buck, it isn't!. This arrangement is sometimes referred to as a Linear Current Booster - it maximises the output current without excessively loading the input. With some tweaks, this circuit could easily be used to maximise the current (i.e torque) delivered to say a DC Solar irrigation pump.

The use of high voltage DC electricity on an AC specified mechanical thermostat may lead to arcing and welding of contacts, potentially seriously compromising the safety of a hot water cylinder. Loadmaster modulates its electrical output waveform by forcing a brief period of zero load current (extinguishing any arc) at an interval of 30Hz. No evidence of arcing has been observed using this technique (at 3kW & Vmp 155V). It should also be noted that during normal operation the immersion thermostats are not used to control the cylinder temperatures, - they are adjusted to open a higher temperature simply as a safety back up. Temperature control in normal operation is achieved using sensors (DS18B20) and software.

Loadmaster Specifications

Power Supply:- 12VDC, 100mA. 2.1mm power connector centre +Ve

PV Input:- max Current 23A, max Vmp 260V, Suggest array PMax =3.8kW

Relay Output x 1 (For Combi Pre-Heat / Diverter valve control, below a specified temperature water can be diverted to a Combi Inlet)

Opto isolated logic input - For future connectivity or spare I/O

RGB Status LED - Provides 'at a glance' indication of current operating status

Single push button control - short press = display toggle. Long press = ON/OFF.

20x4 I2C Graphics LCD

Connectivity:- Serial interface is made available at a connector. Tested with HM-10 (BLE), DT-06 (Wifi) and (ESP8266-01S). A serial menu driven terminal interface provides access to all monitoring and control functions. Wifi Modules enable LAN or Internet remote access (Telnet). Highly recommended are the Android Serial Terminal apps by Kai Morich.

Temperature Sensors:- Default DS18B20 sensors are used for Heatsink, Water Cylinder top and bottom temperatures. (Alternatively Analog sensors, eg MCP9701 may also be used for cables >12m)

2nd Load Output - a second MOSFET channel is included on the hardware which can be used to dump excess power into a second load, for example space heating (currently not implemented in code).

PV Load Capacitor:

This should be a Film type capacitor with suitable voltage and ripple current ratings (typ >350VDC and >15A RMS). Suitable capacitors are frequently employed in Motor run or 'DC link applications'. (Consider caps such as EPCOS B32363 series offering M10 stud terminals, over pressure disconnect and specifically suited to high pulse applications. These are often used in large DC supply or solar installations - look for ebay bargains!). Electrolytic caps are not suitable.

The capacitance value influences the amplitude of ripple in the PV current and voltage around the peak power point. The higher the value the less ripple. Recommended value is from 200 to 400uF.

How much hot water will I get?

The average amount of energy (KWhrs) delivered into your water tank every day obviously depends on your location, size of the PV array, time of year and other efficiency losses such as shading, panel angle and direction etc. In winter months some days will be significantly higher or lower than the average, this is called weather. In the Downloads section (at end) a LoadMaster design spreadsheet includes an approximate performance calculator. The following examples show typical performance for UK and USA locations:-

An efficient 150L hot water cylinder may have a standing loss of <=50w. Overnight it may cool by 3-4°C. The above final temperature values shown above assume the cylinder starts at 15°C every morning. In reality even in mid winter, if usage is low for a day or two then stored energy will accumulate to reach the set temperature limit (in my case 60°C). A larger cylinder may be used to effectively accumulate more energy on good days. Heated water rises and fills the top of the cylinder by stratification. Whilst a larger cylinder may capture more energy and hold more hot water, in poorer solar conditions its standing losses impact the net solar gain. There is a balancing act to be made.

LoadMaster has very low losses. The MOSFET power loss (Rds ON =17mΩ) depends mostly on the Heater load current, I^2 x Rds Watts (plus some small switching losses) will result in a MOSFET loss I.R.O 2 to 10W. Capacitor loss is negligible. There is approx 2.5W lost in the CR Snubber resistors, (an unavoidable consequence of dissipating unwanted 'Ringing' energy caused by switching and stray inductance). A total power loss of 15W to deliver 2.985kW from 3kWs of PV input equates to >99% efficiency.

During winter months PV Hot water systems have been shown to out perform solar thermal. PV panels are more efficient in cold conditions, with approx 10% greater power at -12°C compared to 25°C.

Thermal systems become less efficient in cold ambient conditions due to increased heat loss. Small temperature differentials mean the circulating pump is activated less and so less energy is delivered to the water.

With PV hot water, even at dawn or dusk or in terrible weather you will still see LoadMaster pushing every available watts of energy into the water, even if this is 80W, water is still being heated whilst a solar thermal system would be totally inactive.

Matching Heater Resistance & PV Array

One key consideration with this design is the selection of a heating element (power rating & resistance) to match the connected PV Array's specifications.

It can be seen that at the maximum solar array output, the PWM will be 100%, and the MOSFET will be continuously conducting. The load resistance presented to the PV array will be that of the heating elements resistance. During lower solar conditions, MPPT will adjusts the PWM's ON:OFF ratio such that LoadMaster presents a higher impedance to match the PV array.

Domestic immersion elements are commonly 3kW, 240V ( R=V^2/P = 19.2Ω). The ideal array to suit such an element would obviously produce Pmp 3kW @ 240Vmp, which is of course unrealistic plus some applications may only required a lower power solution. In all cases the PV array should ideally have a Pmp and Vmp relationship such that Vmp^2/Pmp = R heater.

In the ideal world we should aim for a Heater resistance which equates to or is just slightly lower than the array's maximum Vmp / Imp.

The LoadMaster design spreadsheet includes a worksheet ('Load Matching Configs') which can help you review possible PV specs, array configurations and heater choices.

If using a single 3kW 240V heating element then a good array option may be 4S x2P using say Silevo Triex U285 (96 cell) panels, each 285W @ Vmp =56.6V. The resulting array is 2280W @ 226Vmp =22Ω. If you wish to use a single standard 240V / 3kw 19.2Ω heating element, 96 Cell panels are good option to consider but are not so common.

My system uses 2 x 240V 3kW immersion heaters in parallel (i.e 9.6Ω) and has a 5S x 2P array comprised of REC 300W (60 Cell) panels with Vmp =32.5V creating 3kW@ Vmp =162.5V which represents a matched load of 8.8Ω. This is a close enough match to the heater. In hind sight! I wish I had used a 6S x 2P array configuration (i.e 3.6kW @195Vmp equates to a 10.56Ω Mppt load, the lower 9.6Ω resistance of my parallel elements would be perfectly suited.

My Direct 150L cylinder has the two Immersion heater elements wired in parallel. Each 3kw heater element only sees a maximum power of 1.5kw and should last many years. My surplus PV in summer months is to be used for battery charging.

Some may wish to use LoadMaster in smaller, lower voltage applications and for this a range of DC heaters with a range of resistances and power ratings are available.

!SAFETY!

Hopefully by now you have spotted that this project includes:-

  • High Voltages, High Current, High Power (Electrocution and fire risks)
  • Hot Water (Scalding Risks & understanding the safety aspects of pressurized hot water storage systems)
  • Capacitors with high charge energy (NEVER short this or any high Voltages out - burns, sparks, eye injury etc)
  • Installing solar panels at height
  • etc

Like any commercial high power solar inverter or charger, the electronics should be housed inside a sealed, metal, earthed enclosure which provides some level of fire protection. If you apply relevant safety precautions and common sense this project is as safe as any other mains powered project. I accept no liability for this project! If you doubt your knowledge or appreciation for the safety aspects involved then it is best to consider a different project.

The power side of this circuit was modeled using Ti Tina.

Downloads

Build Instructions may be downloaded from here

Click to download Circuit Schematic & other useful design files.

Code

LoadMaster - MPPT PV Hot Water Controller V80Arduino
This code is for PCB Rev4 and uses DS18D20 temperature sensors. Functionality for Single Hot Water Output only. Includes Software Serial data transmission for remote Nextion 3.2" enhanced Display.
/* PV HOT WATER CONTROLLER
  An MPPT based hot water controller

  by Steve Tearle
  Dated : 27th June 2020 2020
  Compatible to LoadMmaster XP PCB Rev 4.0

  V80 onwards for PCB V4.0

  Specifications :  //////////////////////////////////////////////////////////////////////////

    1.Solar panel power = 3500W
    2.Vmppt Full Scale approx 190V or 256VDC (Check PCB resistor values R7, R8,R9 & R10, plus Scale factor in this code)
    3.Maximum PV current =  20A


  ////////////////////////////////////////////////////////////////////////////////////////////
*/

#include <DS3231.h>                 //Using DS3231 RTS library from http://www.rinkydinkelectronics.com/library.php?id=73  by Henning Karlsen 
//#include <LiquidCrystal.h>          //Arduino LCD Lib
#include <TimerOne.h>               //https://www.arduinolibraries.info/libraries/timer-one
#include <EEPROM.h>                 //1024 Byte EEPROM
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SoftwareSerial.h>

#include <Wire.h> // For I2C RTC ad LCD
//#include <LCD.h> // For LCD
#include <LiquidCrystal_I2C.h> // Added library*   Within Francisco Malpartida  New LiquidCrystalhttps://www.instructables.com/id/How-to-Use-I2C-Serial-LCD-20X4-Yellow-Backlight/

//Set the pins on the I2C to Parallel chip used for LCD connections
//ADDR,EN,R/W,RS,D4,D5,D6,D7
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // 0x27 is the default I2C bus address of the I2C PCF8574 interface PCB...Can be different though!

SoftwareSerial mySerial(15, 17); // RX, TX

DS3231  rtc(SDA, SCL);
//LiquidCrystal lcd(12, 10, 5, 4, 6, 2);

//int HeatSink_ADC = A0;      // Heat Sink Temp Sensor
// A1 D15                    // Spare IO D15 used as software serual Rxd

int SupplyVin = A2;         //PCB 4 moved to A2. Monitor 12V Supply, Used to set SystemOn flag false if <10V

// A3/D17                   // spare input . D17 used for software serial Tx
//A4 = SDA for RTC
//A5 = SCL for RTC

int PVAmps_ADC = A6;        // PCB4 moved to A6 PV amps
int PVVolts_ADC = A7;       // PCB4 moved to A7, Solar panel PV Volts

//D0 - used by RXD
//D1 - used by TXD

byte Bott_DS18B20 = 2;     // Water lower temp sensor, DS18B20 onewire port
byte PWM_EN_Pin = 3;         // D3 & D11 may be controlled by Timer2 at 30Hz, or set High / Low to enable PWM Gate Driver IC
byte Top_DS18B20 = 4;        // Water Top temp sensor, DS18B20 onewire port

byte PushButton = 5;         // Push button on D8

byte Mid_DS18B20 = 6;        // Water Mid temp sensor, DS18B20 onewire port= D6

byte  GreenLED = 7;          //Green LED Pin
byte  BlueLED = 8;           //Blue LED Pin

byte PWMPin = 9;             //PWM Output Drives D9            // Pins D9 and D10 use Timer 1 - This can be driven by the Timer one library for more flexible PWM frequency selection.

byte RedLED = 10;            //RED LED pin

byte PWM_EN_OP2 = 11;       // D3 & D11 may be controlled by Timer2 at 30Hz, or set High / Low to enable PWM Gate Driver ICD

//int OptoInput = 12;        //Opto isolated I/O Input
int Aux_Relay = 13;           //Open Drain Fet Driver, Hi = Boiler ON, or use to Enable Charger etc

byte Heatsink_DS18B20 = 14;  // Heatsink temp sensor, DS18B20 onewire port

// DS18B20 Temperature Sensors - adopts a'MULTIBUS' approach - Each DS18B20 sensor is allocated a seperate
// digital data pin for its onewire comms, this helps to reduce signal reflection & improve comms reliability.
// It also eliminates sensor addressing issues associated to using multiple sensors on a single bus.

OneWire ds18x20[] = {Bott_DS18B20, Mid_DS18B20, Top_DS18B20, Heatsink_DS18B20};
const int oneWireCount = sizeof(ds18x20) / sizeof(OneWire);
DallasTemperature sensor[oneWireCount];


//---------------------------------------------------------------------------------
///////////////////////DECLARATION OF FONT BIT MAP ARRAYS  ////////////////////////
//---------------------------------------------------------------------------------
//see https://www.instructables.com/id/ARDUINO-SOLAR-CHARGE-CONTROLLER-Version-20/

byte solar[8] =                //solar panel icon #1
{
  0b11111,
  0b10101,
  0b11111,
  0b10101,
  0b11111,
  0b10101,
  0b11111,
  0b00000,
};


byte tankbot[8] =              // water tank icon #2
{
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11101,
  0b11001,
  0b11101,
  0b11111,
};

/*

  byte battery[8] =              //Battery Icon #2
  {
  0b01110,
  0b11011,
  0b10001,
  0b10001,
  0b10001,
  0b10001,
  0b10001,
  0b11111,
  };
*/

byte energy[8] =               // Power flash #3
{
  0b00010,
  0b00100,
  0b01000,
  0b11111,
  0b00010,
  0b00100,
  0b01000,
  0b00000,
};

byte tanktop[8] =              // water tank icon #4
{
  0b01110,
  0b11111,
  0b11101,
  0b11001,
  0b11101,
  0b11111,
  0b11111,
  0b11111,
};


byte temp[8] =              // thermometer icon #5
{
  0b01110,
  0b10001,
  0b10011,
  0b10001,
  0b10001,
  0b10001,
  0b10001,
  0b11111,
};

byte powerplug[8] =         // Power Plug Icon  #6
{
  0b01010,
  0b11111,
  0b10001,
  0b10001,
  0b10001,
  0b01110,
  0b00100,
  0b00100,
};


byte DegC[8] =         //Deg C in one char  #7
{
  0b01000,
  0b00000,
  0b00011,
  0b00100,
  0b00100,
  0b00100,
  0b00011,
  0b00000,
};

//-----------------------------------------
//////////// GLOBAL VARIABLES
//-----------------------------------------
float PVVolts = 0;
float PVAmps = 0;
float PVWatts = 0;
float Watthrs = 0;
float WattSecs = 0;

float BottomTemp = 0;
float FET_Temp = 0;
float TopTemp = 0;
float MidTemp = 0;

byte ErrorCode = 0;

boolean DS1820ReadError = false;
byte DS1820ErrorCount = 0;        // count the number of consecutive DS1820 reading failures

unsigned long msec = 0;
unsigned long msec_out = 0;           //Used in tesing routing entry to exit times
unsigned long last_msec = 0;
unsigned long elapsed_msec = 0;
unsigned long LastMPPTmsec = 0;

byte MPPTcount = 0;

byte MpptUpdate = 100;                  //msec between input signal scan and MPPT algorithm update

unsigned long LastScanmsec = 0;         //Scan is a 1 minute check interval - For FET testing, Logging check

// For PWM & Sweep Test
unsigned long TimeoutStart;              //loaded with millis used to set a timeout for PWM tests (Dont indefinately lock out main loop Temp controls etc!!

int maxWatts = 0;       //holds max watts in a PWM sweep test

int maxPWM = 0;
float maxVolts = 0;
float maxAmps = 0;

boolean PWMTest = false;                //These Flags are used to identify test modes,
boolean PWMSweep = false;
boolean StreamData = false;


boolean SystemOn = false;               //Overall ON/OFF state for PV Heating, Power UP Default =OFF
boolean Green_LED_ON = false;           // Mode OFF = both leds off, Off with error RED, ON mppt increment = green,
boolean Red_LED_ON = false;
boolean Blue_LED_ON = false;


boolean BoilerOn = false;               //The Aux Relay is switched ON when BoilerOn state = true.

byte ActiveDisplay = 0;                 // which LCD display is shown 0=main, >=1 = data info screen. Active display value is decremented to 0 on each mppt loop pass
byte Hour = 0;                          // Hold the current RTC hour value, Used to determine data logging and time period when relay can be active (saves valve power at night)

//Serial Variables
//char BLERx = 0;                       // Holds the received BlueTooth data.


float PrevPVWatts = 0;                  // solar watts from previous time through selected MPPT routine
float PrevPVVolts = 0;
float PrevPVAmps = 0;
int delta = 1;                         // variable used to inentify the MPPT Step up or Down status

// An array of PWM values provides suitable step sizes relative to the actual PWM level

int DutyLevels[] = {11, 12, 13, 15, 18, 21, 24, 29, 33, 38, 44, 50, 57, 64, 72, 80, 88, 98, 107, 117, 128, 139, 151, 163, 176, 189, 202, 217, 231, 246, 262, 278, 295, 312, 330, 348, 366, 386, 405, 425, 446, 467, 489, 511, 534, 557, 580, 605, 629, 654, 680, 706, 733, 760, 788, 816, 844, 874, 903, 933, 964, 995, 1024};
int ArrayPosition = 0;

int PWMDuty = 0;         // pwm duty cycle 0-1023 for 0-100%
int PWMPercent = 0;

byte MaxTemp = 0;        //Initialise todays Max and Min values at startup
byte MinTemp = 255;
int MaxW = 0;            // Daily max watts - shown on remote Nextion Display

byte SetTemp = 0;        // The setpoint for water cylinder temperature control
byte RelayTemp = 0;      // Variable holding setpoint temperature to control relay (Pre-heat divert valve etc)

byte Hysteresis = 0;     // used to hold a 0 or 1 degC hysteresis offset for the temperature limit switching


static char LCDstr[16];                 //String array declaration, used to format Float values for display on LCD

// Global Constants

byte AVG_Samples = 6;
const int LCDUpdateInterval = 2000;

// for Pusbutton
const byte debounce = 20;                // ms debounce period to prevent flickering when pressing or releasing the button
const int holdTime = 1200;               // ms hold period: how long to wait for press+hold event

// Button variables
int buttonVal = 0;                       // value read from button
int buttonLast = 1;                      // buffered value of the button's previous state
long btnDnTime = 0;                      // time the button was pressed down
long btnUpTime;                          // time the button was released
boolean ignoreUp = false;                // whether to ignore the button release because the click+hold was triggered
boolean ButtonShortPress = false;
boolean ButtonLongPress = false;


//Variables for Serial CLI and Message Parsing
const byte numChars = 30;                 // Maximum Char length of received serial messages terminated in a Linefeed.
char receivedChars[numChars];
//char tempChars[numChars];               // temporary array for use by strtok() function
char RxdSerialMessage[numChars] = {0};    // variables to hold the parsed data

int RxdInt1 = 0;
int RxdInt2 = 0;
int RxdInt3 = 0;
int RxdInt4 = 0;
int RxdInt5 = 0;
char CommandChar = 0;
boolean NewData = false;



//Variable used in EEPROM storage
int PointerAddress;                       // Bytes 0 & 1 are Pointer address for next record, generally this value is loaded at run time from EEPROM
byte LowByte;                             // Used to read and write int values
byte HighByte;

byte LogDate = 33;                        //Used to find new day change for data log event - power on date will always differ to 33! (typically NewDay == true and 23:005am


Time  t;                                  // Init a Time-data structure - For DS1325 RTC


//******************************************************* MAIN PROGRAM START - SETUP ************************************************
void setup()
{

  Serial.begin(9600);       // Configue Hardware Serial
  Serial.flush();           // Clears the Serial Bluetooth RX buffer


  //LCD Initialisation and populate custom characters

  lcd.begin (20, 4);                // 20x 4 LCD module
  lcd.setBacklightPin(3, POSITIVE); // BL, BL_POL
  lcd.setBacklight(HIGH);          // This turns back light on


  lcd.createChar(1, solar);
  lcd.createChar(2, tankbot);
  lcd.createChar(3, energy);
  lcd.createChar(4, tanktop);
  //   lcd.createChar(0, tankbot);
  lcd.createChar(5, temp);        //thermometer - unused
  lcd.createChar(6, powerplug);
  lcd.createChar(7, DegC);
  //  lcd.clear();


  rtc.begin();


  LowByte = EEPROM.read(0);        // Read current Addresspointer value (a 2 byte int), Points to next EEPROM data log location for Storage
  HighByte = EEPROM.read(1);
  PointerAddress = ((LowByte << 0) & 0xFF) + ((HighByte << 8) & 0xFF00);                  // << is bitshift, create 16 bit address from low and high bytes


  SetTemp = EEPROM.read(2);        //Read the Water Temp threshold from EEPROM
  if (SetTemp > 65) {              //Trap erroneous High Temp values
    SetTemp = 60;
  }

  RelayTemp = EEPROM.read(3);        //Read the Relay activation temperature from EEPROM - for Combi Pre-heat / valve control
  if (RelayTemp > 43) {              //Trap erroneous High Temp values
    RelayTemp = 43;
  }


  t = rtc.getTime();

  pinMode(GreenLED, OUTPUT);
  pinMode(BlueLED, OUTPUT);
  pinMode(RedLED, OUTPUT);
  digitalWrite(RedLED, !Red_LED_ON);

  pinMode(Aux_Relay, OUTPUT);
  digitalWrite(Aux_Relay, BoilerOn);

  pinMode(PushButton, INPUT);
  digitalWrite(PushButton, HIGH); // pull-up


  // PWM Timer1 Setup
  Timer1.initialize(255);        // 60 us = 16.66 kHz,   500= 2kHz,  100 = 10khz , 6 k = 166, 3.921khz = 255
  Timer1.pwm(PWMPin, 0);         //timer1.pwm MUST be called once to initialise

  pinMode(PWM_EN_Pin, OUTPUT);   //Timer 2,      // Default T2 PWM Freq = 490.20 Hz
  TCCR2B = TCCR2B & B11111000 | B00000111;       // Setup registers for Timer 2 (D3 & D11) @ PWM frequency of 30.64 Hz = 32.6mS period


  //DS18B20 MULTIBUS APPROACH - each sensor is on its own data pin to simplify addressing and minimise reflections etc 
  // Start up the library on all defined bus-wires
  DeviceAddress deviceAddress;
  for (int i = 0; i < oneWireCount; i++) {
    sensor[i].setOneWire(&ds18x20[i]);
    sensor[i].begin();
    if (sensor[i].getAddress(deviceAddress, 0)) sensor[i].setResolution(deviceAddress, 11);
    //DS1820 measurement resolution vs conversion time. 9 bit=0.5degC 93.75 mS,10 bit=0.25degC 187.5 mS,11 bit=0.125degC 375 mS, 12 bit=0.0625degC 750 mS
  }
  Serial.println("OneWireCount");
  Serial.println(oneWireCount);

  sensor[0].requestTemperatures();
  sensor[1].requestTemperatures();
  sensor[2].requestTemperatures();
  //sensors.requestTemperatures()

  lcd.setCursor(5, 0);
  lcd.print(F("LOADMASTER XP"));
  lcd.setCursor(0, 1);
  lcd.print(F(" Photovoltaic MPPT"));
  lcd.setCursor(0, 2);
  lcd.print(F("Hot Water Controller"));
  lcd.setCursor(0, 3);
  lcd.print(F("Ver 80   PCB Rev4"));
  delay(5000);
  lcd.clear();

  mySerial.begin(9600);

}

//******************************************************* MAIN PROGRAM LOOP ************************************************
void loop()
{

  ErrorChecks();                     //Checks 12V supply OK, else turns the system off

  SerialRxWithEndMarker();           //Check for data message in serial buffer
  //messages seperated by NewLine char

  if (NewData == true) {

    if (PWMSweep || PWMTest) {       //If PWM tests are in progress, any new serial message containing new line (LF)will terminate the test and turn system state to off
      PWMSweep = false;
      PWMTest = false;
      SystemOn = false;
    }

    parseData();                     // Split message into command and values

    //  showParsedData();            // FOR TEST ONLY! see what Character command and values are received

    HandleCommand();                 //Routine to manage different received command characters
  }


  handle_button();                         //Call Routine to look for button press and hold

  if (ButtonShortPress == true) {          //Short press changes the active display,

    if (ActiveDisplay > 0) {
      ActiveDisplay = 0;
      lcd.clear();
      LCD_MainDisplay();                   // Show Main Display, Reset the display update interval

      MPPTcount = 14;
    }
    else {
      ActiveDisplay = 85;       //Short button shows the extended data value screen for x n mppt loops  = n x 0.1 secs,
      lcd.clear();
      LCD_DataScreen();          //Show Data + Status Display
      MPPTcount = 14;
    }

    ButtonShortPress = false;
  }

  if (ButtonLongPress == true) {        //Long button press updates
    SystemOn = !SystemOn ;
    ErrorCode = 0;
    DS1820ErrorCount=0;
    ErrorChecks ();                 //  If System is now on, Run Error Checking first to ensure 12V Gate drive is OK
    ButtonLongPress = false;
  }




  //  **********Update Interval for MPPT, PWM & Temperature Control *********************

  msec = millis();                                // We use the millis millisecond runtime counter to schedule specific tasks

  if ((msec - LastMPPTmsec) > MpptUpdate) {       //The Code below is executed evert MPPT update interval - 100ms)

    // Serial.println(msec - LastMPPTmsec);    // FOR TESTING ONLY  - Shows the msec time stamp between each mppt update, additional routines could slow the update rate. each time the mppt loop is updated

    LastMPPTmsec = msec;
    read_data();                             // read different sensors data from analog pin of arduino
    power();                                 // calculate the load power and KWHr accumulated energy since power up


    if (StreamData || PWMTest || PWMSweep) {        //Test modes triggered by serial commands stream real time data. A timeout ensures normal operation resumes after 45secs
      Serial.print(F("Pwm= "));
      Serial.print(PWMDuty);
      Serial.print(F(",   "));
      Serial.print((int)PVVolts);
      Serial.print(F("V,   "));
      Serial.print(PVAmps);
      Serial.print(F("A,   "));
      Serial.print((int)PVWatts);
      Serial.println(F("Watts" ));


      if (msec - TimeoutStart > 60000) {      //TimeoutStart is reset when a serial request to stream data is received , 60000ms = 1 min
        PWMTest = false;                      // 60000mS is a 1 min timeout
        PWMSweep = false;
        StreamData = false;
        SystemOn = false;                     //System is TURNED OFF following completion of any PWM tests
        Serial.println(F("Test Finished"));
      }

    }

    //Determine the next PWMuty value depending on active system mode;- mppt control?, PWM test values, temperature shutdowns etc :=-

    if (PWMTest) {
      PWMDuty = RxdInt2;      //System will run at the fixed PWM value sent in the serial command.
    }
    else if (PWMSweep) {          //Test sweeping through all values in the PWM array, Remembers the max power and associated PWM
      if (PVWatts > maxWatts) {
        maxWatts = PVWatts;
        maxPWM = PWMDuty;
        maxVolts = PVVolts;
        maxAmps = PVAmps;
      }
      if (PWMDuty == 1024) {
        Serial.println(F("PWM Sweep Complete!"));
        Serial.print(F("MaxPower was = "));
        Serial.print(maxWatts);
        Serial.print(F("W  @ Vmpp = "));
        Serial.print((int) maxVolts);
        Serial.print(F(" , "));
        Serial.print(F("Impp= "));
        Serial.print(maxAmps);
        Serial.print(F(" , "));
        Serial.print("PWM = ");
        Serial.println(maxPWM);
        Serial.println(F("PWM Test Finished"));

        SystemOn = false;
        PWMSweep = false;
      }
      else {
        ArrayPosition = ArrayPosition + 1;
        ArrayPosition = constrain(ArrayPosition, 0, 62);
        PWMDuty = DutyLevels[ArrayPosition];      //  look up the PMW percentage in the array of values
        set_pwm_duty();
      }
    }

    else if (SystemOn && (BottomTemp >= (SetTemp - Hysteresis))) { // Temperature control, threshold level reduced by 1degC hysteresis once the Specified Set Point temp is exceeded
      Hysteresis = 1;
      PWMDuty = 0;                            // Turn OFF PWM                                                                                                     ;
      analogWrite(PWM_EN_Pin, 0);             // Disable Gate Driver and Forces gate driver output low (Zero Duty)
      set_pwm_duty();                         //Call function to Update PWM Timer
    }


    else if (SystemOn && (BottomTemp < (SetTemp - Hysteresis))) { //Calls the MPPT routing to determine next PWM duty cycle
      Hysteresis = 0;
      MPPT_Petes();

      //0 = LOW, Gate Driver is continuously disabled, i.e No forced Zero current pulses to break Arcing.
      analogWrite(PWM_EN_Pin, 250);       //if set to 250, FET DRIVER IS DISABLED for 0.65mS at a  of 30Hz 
      //250 = @30Hz, i.e a 33.33ms period, 5/255 x 33.3 = 0.65mS width low pulse to disable the gate driver and force a Zero load current condition
      //at 30HZ to break DC arc current that could reduce life of Mechanical thermostat switches
    }

    else if (!SystemOn) {
      ArrayPosition = 0;                    // System state is OFF
      PWMDuty = 0;                          // turns OFF PWM                                                                                                     ;
      analogWrite(PWM_EN_Pin, 0);           //0 = Output is FULLY LOW thus gate driver is disabled and MOSFET will be Off
      set_pwm_duty();                       //Call function to Update PWM Timer
    }

    // The Next PWM Duty state will have been set by one of the 'IF' routines above !


    //   Time out extended data LCD screen and swap back to main screen based on count of passes through MPPT loop interval

    if (ActiveDisplay == 1) {                   // 1 causes LCD to clear and change to MainDisplay view
      lcd.clear();
      LCD_MainDisplay();                        // Main Display
      msec = millis();

      ActiveDisplay = 0;
    }

    if (ActiveDisplay > 1) ActiveDisplay --;  //Display updates shows Data values until this counts down to 0

    MPPTcount++;                               // Counts number of passes around Mppt routine to flash led etc

    LEDManager();                              // update RGB LEDS on every pass of the MPPT loop

    GetDS1820Temps();                          //Initiate & Read Temperature sensors and update max mins, Shedule based on count of MPPT loop passes

    // GetAnalogTemps();                          //For users with long cables, Analog temp sensors may be used!


    if (MPPTcount == 14)          //Display is updated when MPPT count reaches 14. Since MPPTcount increments to 20 then resets, display update rate = 2 sec
    {

      //  unsigned long start = millis();        //FOR TESTING TIMING
      //  Serial.println(start);
      //  unsigned long stop = millis();

      if (ActiveDisplay == 0) {
        LCD_MainDisplay();       // update values on Main Display
      }
      else {
        LCD_DataScreen();       // update extended Data + Status Display
      }

      // unsigned long stop = millis();     // FOR TESTING TIMING
      // Serial.println(stop - start);      // ""
    }


    // msec = millis();

    if (MPPTcount == 15) {        // One /minute interval to check for data log events or errors.
      DataLogCheck();
      // Test FET goes off??
    }

    if (MPPTcount == 16) {
      RelayControl();            //Update the Relay status - used to enable combi boiler or operate a diverter valve
      UpdateNextionIcons();
    }

    if (MPPTcount == 17) {
      UpdateNextionWatts();
      UpdateNextionWhrs();
    }
    if (MPPTcount == 18) {
      UpdateNextionTemps();
    }

    if (MPPTcount == 19) {
      UpdateNextionMaxMins();
    }
  }        //End of code executed within every 100mS MPPT event

}

//********************************PROGRAM MAIN LOOP END *********************************************************************************************
//**********************************SUBROUTINES FOLLOW **********************************************************************************************




//------------------------------------------------------------------------------------------------------------
//////////////////////////////// Read Volts, Current   ///////////////////////////////////////////////////////
//------------------------------------------------------------------------------------------------------------

void read_data(void)            // READS AND AVERAGES THE ANALOG INPUTS (SOLAR VOLTAGE,BATTERY VOLTAGE)

{
  //*******  Read each ADC Signal Input I times then average and Scale   **************

  //For Testing routine execution time
  // msec = millis();

  int sumV = 0;
  int sumA = 0;
  int sumBT = 0;
  int sumTT = 0;
  int sumHS = 0;
  int sample ;

  for (int i = 0; i < AVG_Samples; i++) {               // loop through reading raw adc values AVG_NUM number of times
    sample = analogRead(PVVolts_ADC);    // read the input pin
    delay(2);
    sample = analogRead(PVVolts_ADC);    // read the input pin
    sumV += sample;                      //  sum for PVVolt samples
    sample = analogRead(PVAmps_ADC);     // read the input pin
    delay(2);
    sample = analogRead(PVAmps_ADC);     // read the input pinsumA += sample;                      // sum for PVAmps samples
    sumA += sample;


    delayMicroseconds(30);               // pauses for 50 microseconds
  }

  // comment-out the PV Volts and Current scaling to suit R7-R9 and R10 and your PV Array's Vmp, Imp
  // PVVolts = (float)(sumV / AVG_Samples) * 0.114455;     //Scaling for 117vFSD, R10 =9k1
  // PVVolts = (float)(sumV / AVG_Samples) * 0.151515;     //Scaling for 155vFSD, R10 =6k8
  PVVolts = (float)(sumV / AVG_Samples) * 0.1863206;       //MY SYSTEM Scaling for 190VDC FSD, R7=R8=R9=68k & R10 =5k49
  // PVVolts = (float)(sumV / AVG_Samples) * 0.250307;     //Scaling for 256VDC FSD, R7=R8 =68k, R9=100K, R10 =4k7
  


  // PVAmps = (sumA / AVG_Samples) * 0.000444326;       //0.115R Shunt .454A FSD
  // PVAmps = (sumA / AVG_Samples) * 0.0195503;         //R1=0.0025 ohm Shunt 20A FSD
  // PVAmps = (sumA / AVG_Samples) * 0.0162920;         //R1=0.003 ohm Shunt 16.7A FSD
  // PVAmps = (sumA / AVG_Samples) * 0.0122190          //R1=0.004 ohm Shunt 12.5A FSD
  // PVAmps = (sumA / AVG_Samples) * 0.0097752          //R1=0.005 ohm Shunt 10A FSD
  PVAmps = (sumA / AVG_Samples) * 0.019350;             //0.0025R Shunt 20A FSD.. factor determined by DMM testing to accomodate Current amp and ADC errors

  //PVVolts = 160;   // Forced V and I readings for testing only!
  //PVAmps = 18.75;

  PVWatts = PVVolts * PVAmps;               //Watts now
  if (PVWatts > MaxW)MaxW = PVWatts;        //Update todays MaxW value - sent to remote display

  /*For Testing routine execution time
    msec_out = millis();
    Serial.println(msec_out-msec);     // For testing msec time stamp each time the mppt loop is updated
  */

}


//------------------------------------------------------------------------------------------------------------
//////////////////////////////// Read Temperatures and mangage errors and max mins ////////////////////////////
//------------------------------------------------------------------------------------------------------------

void GetDS1820Temps (void)     //Routine reads DS1820 temperature sensors. As each reading takes
{ //approx 22ms sensor are read seperately within differnt MPPT loop passes.

  // unsigned long start = millis();               //FOR TESTING PROCESSING TIMING
  float Reading;

  switch (MPPTcount) {

    case 6:
      DS1820ReadError = false;                    //Start of DS18B20 reading sequence,Reading error flag is cleared

      Reading = sensor[0].getTempCByIndex(0);     //Get Reading from Bottom Temp sensor
      if (Reading < 0 || Reading > 70) {          //FYI each getTempCByIndex reading takes approx 22ms
        DS1820ReadError = true;
        ErrorCode = 1;
      }
      else {
        BottomTemp = Reading;
      }
      break;


    case 7:
      Reading = sensor[1].getTempCByIndex(0);   //Get reading from Mid temperature sensor
      if (Reading < 1 || Reading > 70) {
        DS1820ReadError = true;
        ErrorCode = 2;
      }
      else {
        MidTemp = Reading;
        if (MidTemp > MaxTemp) {                  // Capture the daily max and min temperatures at bottom of tank
          MaxTemp = MidTemp;
        }
        if (MidTemp < MinTemp) {
          MinTemp = MidTemp;
        }
      }
      break;

    case 8:
      Reading = sensor[2].getTempCByIndex(0);  //Read the Top Water temp sensor
      if (Reading < 1 || Reading > 70) {
        DS1820ReadError = true;
        ErrorCode = 3;
      }
      else  {
        TopTemp = Reading;
      }
      break;


    case 9:
      //unsigned long start = millis();           //FOR TESTING PROCESSING TIMING

      FET_Temp = sensor[3].getTempCByIndex(0);    //Read FET Temp sensor
      if (FET_Temp < 0 || FET_Temp > 50) {
        ErrorCode = 4;
        SystemOn = false;                     //a single FET temp error instantly shuts the system down.
      }

      sensor[0].setWaitForConversion(false);  // makes sensor async
      sensor[0].requestTemperatures();        // Request Sensor bus 0 conversion, i.e the Bottom Temp sensor
      sensor[0].setWaitForConversion(true);

      sensor[1].setWaitForConversion(false);  // makes it async
      sensor[1].requestTemperatures();        // Request Sensor bus 1 conversion, i.e the Mid Temp sensor
      sensor[1].setWaitForConversion(true);

      sensor[2].setWaitForConversion(false);  // makes it async
      sensor[2].requestTemperatures();        // Request Sensor bus 2 conversion, i.e Top Temp sensor
      sensor[2].setWaitForConversion(true);

      sensor[3].setWaitForConversion(false);  // makes it async
      sensor[3].requestTemperatures();        // Request Sensor bus 3 conversion, i.e the Heatsing Temp sensor
      sensor[3].setWaitForConversion(true);


      if (DS1820ReadError == true) {
        DS1820ErrorCount++;
      }
      else {
        DS1820ErrorCount = 0;
      }

      if (DS1820ErrorCount                  == 3) {     //Looking for 3 consecutive DS1820 reading errors
        SystemOn = false;               //turns system off
      }

      break;
  }

  //unsigned long stop = millis();           //FOR TESTING PROCESSING TIMING
  //Serial.println(stop - start);            //FOR TESTING PROCESSING TIMING
}
//----------------------------------------------------------------------------------------------------------
/////////////////////////////////POWER AND ENERGY CALCULATION //////////////////////////////////////////////
//----------------------------------------------------------------------------------------------------------

void power(void)

{
  msec = millis();
  elapsed_msec = msec - last_msec;                                    //Calculate how long has past since last call of this function
  //elasped_time = elapsed_msec / 1000.0;                             // 1sec=1000 msec

  WattSecs = (PVWatts * elapsed_msec / 1000);                        //AmpSecs since last measurement
  //wattSecs = ampSecs * PVVolts;                                    //WattSecs since last measurement
  //ampHours = ampHours + ampSecs/3600;                              // 1 hour=3600sec //Total ampHours since program started
  Watthrs = Watthrs + (WattSecs / 3600);                             // 1 hour=3600sec / 1000 for kWHrs   Total kWattHours since program started

  last_msec = msec;                                                  //Store millis 'now' for next loop & sample time


  //For Testing routine execution time
  // msec_out = millis();
  // Serial.println(msec_out-msec);     // For testing msec time stamp each time the mppt loop is updated
}



//--------------------------------------------------------------------------------------------------------
//************************* Max Power Point Tracking *****************************************************
//--------------------------------------------------------------------------------------------------------

void MPPT_Petes (void)
{
  //MPPT P&O with guidance.  This employs the widely used P&O strategy to detect the reversal of the dV/dI gradient as
  //the algorith oscillates each side of the Peak power point.  Since the ideal PWM step size varies significantly at different
  // solar radiance level, an array of PWM values is used to optomise the step size across the range.

  if (PVWatts > PrevPVWatts) {
    if (PVVolts > PrevPVVolts) {   //Rising up LHS of Peak power curve,  reduce PWM to increase PV voltage and move towards peak Vmpp
      delta = -1;
    }
    else {                          //Rising up RHS of peak power curve, increase PWM to pull PV voltage down towards Vmpp
      delta = 1;
    }
  }
  else if (PVWatts < PrevPVWatts) {

    if (PVVolts > PrevPVVolts) {   //Dropping down RHS of Peak power curve, increment PWM to pull PV voltage down and move left towards peak
      delta = 1;
    }
    else {                          //Dropping down LHS of peak Power curve, reduce PWM to increment PV Voltage and move right towards peak
      delta = -1;
    }
  }

  if (delta == 1) {                  //Update the RED and Green LED status according to Step direction
    Red_LED_ON = true;
    Green_LED_ON = false;
  }
  else {
    Green_LED_ON = true;
    Red_LED_ON = false;
  }
  ArrayPosition = ArrayPosition + delta;
  ArrayPosition = constrain(ArrayPosition, 0, 62);

  PWMDuty = DutyLevels[ArrayPosition];      //Look up the PMW percentage in the array of values
  set_pwm_duty();                           //Loads PWM output to MOSFET Gate

  PrevPVWatts = PVWatts;
  PrevPVAmps = PVAmps;
  PrevPVVolts = PVVolts;

}


//------------------------------------------------------------------------------------------------------
//*************************************************************************************************
//------------------------------------------------------------------------------------------------------
// This routine uses the Timer1.pwm function to set the pwm duty cycle.
//------------------------------------------------------------------------------------------------------

void set_pwm_duty(void)                 //Routine to set TimerOne Lib PWM Duty Cycle and Enable / Disable MOSFET Gate Driver,
{


  if (PWMDuty > 1024) {                   // check limits of PWM duty cyle and set to PWM_MAX
    PWMDuty = 1024;
  }
  else if (PWMDuty < 0) {                 // if pwm is less than PWM_MIN then set it to PWM_MIN
    PWMDuty = 0;
  }
  Timer1.setPwmDuty(PWMPin, PWMDuty);

  PWMPercent = ((10 * PWMDuty) / 102);

}



//------------------------------------------------------------------------------------------------------
//////////////////////// LCD DISPLAY///////////////////////////////////////////////////////////
//------------------------------------------------------------------------------------------------------

void LCD_MainDisplay(void)
{
  // lcd.setCursor(0, 0);
  // lcd.print(F("PV Array"));


  dtostrf(PVVolts, 5, 1, LCDstr);
  lcd.setCursor(0, 0);
  lcd.write(1);
  lcd.setCursor(2, 0);
  lcd.print(LCDstr);
  lcd.setCursor(7, 0);
  lcd.print(F("V"));

  dtostrf(PVAmps, 5, 1, LCDstr);
  lcd.setCursor(0, 1);
  lcd.write(3 );
  lcd.setCursor(2, 1);
  lcd.print(LCDstr);
  lcd.setCursor(7, 1);
  lcd.print(F("A"));



  dtostrf(PVWatts, 4, 0, LCDstr);

  lcd.setCursor(0, 2);        //Display power icon
  lcd.write(6);

  lcd.setCursor(3, 2);
  lcd.print(LCDstr);
  lcd.setCursor(7, 2);
  lcd.print(F("W"));


  lcd.setCursor(0, 3);
  lcd.print(F("T"));
  dtostrf(Watthrs, 5, 0, LCDstr);
  lcd.setCursor(2, 3);
  lcd.print(LCDstr);
  lcd.setCursor(7, 3);
  lcd.print(F("Wh"));

  lcd.setCursor(9, 0);
  lcd.print(F("Water"));
  lcd.write(4);                                            // Top of cylinder icon
  dtostrf(TopTemp, 4, 1, LCDstr);                          //Display the Top of Tank Temperature
  lcd.setCursor(15, 0);
  lcd.print(LCDstr);
  lcd.write(7);                                            //custom chr code for combined degrees symbol +C



  lcd.setCursor(14, 1);
  lcd.write(2);                                             // Bottom of cylinder icon

  lcd.setCursor(15, 1);
  dtostrf(BottomTemp, 4, 1, LCDstr);                        //Display the Bottom of Tank Temperature
  lcd.print(LCDstr);
  lcd.write(7);                                             //custom chr code for combined degrees symbol +C


  lcd.setCursor(9, 2);

  if (SystemOn) {
    lcd.print(F("   PWM="));
    dtostrf(PWMPercent, 3, 0, LCDstr);                    //Display PWM%
    lcd.print(LCDstr);
    lcd.print("%");
  }
  else {
    switch (ErrorCode) {

      case 0:
        lcd.print(F(" System OFF"));
        break;

      case 1:
        lcd.print(F("BotTemp Err"));
        break;

      case 2:
        lcd.print(F("MidTemp Err"));
        break;

      case 3:
        lcd.print(F("TopTemp Err"));
        break;

      case 4:
        lcd.print(F("FETTemp Err"));
        break;

      case 5:
        lcd.print(F("12V Supply!"));
        break;
    }
  }

  lcd.setCursor(11, 3);
  if (BoilerOn == false) {
    lcd.print(F("Combi-OFF"));
  }
  else {
    lcd.print(F("Combi- ON"));
  }

}


//************************ Data and RTC Screen ************8

void LCD_DataScreen (void)
{

  lcd.setCursor(0, 0);
  lcd.print(rtc.getDOWStr());
  lcd.setCursor(10, 0);
  lcd.print(rtc.getDateStr());

  lcd.setCursor(0, 1);
  lcd.print(rtc.getTimeStr());

  lcd.setCursor(9, 1);
  lcd.print(F("HSink"));
  lcd.setCursor(15, 1);
  dtostrf(FET_Temp, 4, 1, LCDstr);                            //Display the top of Tank Temperature
  lcd.print(LCDstr);
  lcd.write(7);

  lcd.setCursor(0, 2);
  lcd.print(F("Mn/Mx/Set"));

  lcd.setCursor(10, 2);
  lcd.print(MinTemp);
  lcd.print("/");
  lcd.print(MaxTemp);
  lcd.print("/");
  lcd.print(SetTemp);

  lcd.write(0b11011111);
  lcd.print("C");

  lcd.setCursor(0, 3);
  lcd.print("Relay Trig<=");

  lcd.print(RelayTemp);
  lcd.write(0b11011111);
  lcd.print("C");

  lcd.setCursor(17, 3);
  if (BoilerOn == false) {
    lcd.print(F("OFF"));
  }
  else {
    lcd.print(F(" ON"));
  }

}




//*****************************************************************

int handle_button()
{
  // Routine Scans the state of the button and is used to identify brief button presses and longer duration presses.
  // A short key press  is used to toggle the active display. A Long press is used to toggle the systems operating ON/OFF status.

  buttonVal = digitalRead(PushButton);

  // Test for button pressed and store the down time
  if (buttonVal == LOW && buttonLast == HIGH && (millis() - btnUpTime) > long(debounce))
  {
    btnDnTime = millis();
  }

  // Test for button release and store the up time
  if (buttonVal == HIGH && buttonLast == LOW && (millis() - btnDnTime) > long(debounce))
  {
    if (ignoreUp == false) ButtonShortPress = true;
    else ignoreUp = false;
    btnUpTime = millis();
  }

  // Test for button held down for longer than the hold time
  if (buttonVal == LOW && (millis() - btnDnTime) > long(holdTime))
  {
    ButtonLongPress = true;
    ignoreUp = true;
    btnDnTime = millis();
  }

  buttonLast = buttonVal;


}

//********************************************************************************************************************************************
void HandleCommand (void)
// The first character within a received serial message (from SerialRxWithEndMarker CR/LF) is extracted by the ParseData Subroutine
// this routine identifies and handles the specific command request.
{

  switch (CommandChar) {

    case '?':                        // If received a ?:

      ClearTerminal ();

      Serial.println(F("LoadMaster XP PV DHW V80"));
      Serial.println(F("--- Command list: ---"));
      Serial.println("");
      Serial.println(F("? -  Show this Menu!"));
      Serial.println(F("I -   Status Info."));
      Serial.println(F("T -  Stream PV & PWM Data"));
      Serial.println(F("H -  Solar History Report"));
      Serial.println(F("P -  PWM Tests"));
      Serial.println(F("C -  Set Real Time Clock"));
      Serial.println(F("W - Set Water Temp Limit"));
      Serial.println(F("E -  Erase Memory"));
      Serial.println(F("A -  System ON/OFF"));



      break;

    case 'I':                          // I = Info request - Current Status

      ClearTerminal ();                  //Clears the Serial Terminal Screen and re-positions Cursor to Home position

      Serial.print(rtc.getDOWStr());
      Serial.print(F("\t\t\t\t"));
      Serial.print(rtc.getDateStr());
      Serial.print(F("   "));
      Serial.println(rtc.getTimeStr());

      Serial.print(F("Status "));
      Serial.print(F("\t\t\t\t\t\t\t"));         // prints a tab
      if (SystemOn == true) {
        Serial.print(F("ON"));
        Serial.print(F(" (PWM= "));
        //Serial.print(F("\t\t\t\t\t\t\t"));     // prints a tab
        Serial.print(PWMDuty);
        Serial.print(" / ");
        //Serial.print("\t");         // prints a tab
        Serial.print(PWMPercent);
        Serial.println(F("%)"));

      }
      else {
        switch (ErrorCode) {

          case 0:
            Serial.println(F("System OFF"));
            break;

          case 1:
            Serial.println(F("Bot °C Err"));
            break;

          case 2:
            Serial.println(F("Mid °C Err"));
            break;

          case 3:
            Serial.println(F("Top °C Err"));
            break;

          case 4:
            Serial.println(F("HeatSink °C Err"));
            break;

          case 5:
            Serial.println(F("12V Supply Err!"));
            break;
        }
      }
      Serial.print(F("PV Watts"));
      Serial.print(F("\t\t\t\t\t"));
      Serial.print(PVWatts);
      Serial.println(F(" W"));

      Serial.print(F("Total Whrs"));
      Serial.print(F("\t\t\t"));         // prints a tab
      Serial.print(Watthrs);
      Serial.println(F(" Whrs"));

      Serial.print(F("Top Temp   "));
      Serial.print(F("\t\t"));         // prints a tab
      Serial.print(TopTemp);
      Serial.println("°C");

      Serial.print(F("Mid Temp   "));
      Serial.print(F("\t\t"));         // prints a tab
      Serial.print(MidTemp);
      Serial.print("°C  (Min ");
      
      Serial.print(MinTemp);
      Serial.print(F(" / "));
      Serial.print(F("Max "));         // prints a tab
      Serial.print(MaxTemp);
      Serial.println(F(")"));

      Serial.print(F("Bott. Temp"));
      Serial.print(F("\t\t\t"));         // prints a tab
      Serial.print(BottomTemp);
      Serial.println(F("°C "));

      

      Serial.print(F("PV Array"));
      Serial.print(F("\t\t\t\t\t\t"));         // prints a tab
      Serial.print(PVVolts);
      Serial.print(F("Volts / "));

      Serial.print(PVAmps);
      Serial.println(F("Amps"));

      Serial.print(F("Heat Sink"));
      Serial.print("\t\t\t\t\t");         // prints a tab
      Serial.print(FET_Temp);
      Serial.println("°C");

      if (BoilerOn == true) {
        Serial.print(F("Combi Boost - ON"));
      }
      else {
        Serial.print(F("Combi Boost - OFF"));
      }

      break;



    case 'T':                          // If received 'T'  Real time trasmit Volts Watts PWM:

      ClearTerminal ();                  //Clears Serial Terminal Screen and positions Cursor at Home position
      StreamData = true;
      TimeoutStart = millis();
      break;


    case 'H':                          // If received 'H', Dump Solar history from EEPROM

      ClearTerminal ();             //Clears Serial Terminal Screen and positions Cursor at Home position
      ReadEEPROM();

      break;

    case 'P':                          // If received 'P', PWM test mode

      ClearTerminal ();              //Clears Serial Terminal Screen and positions Cursor at Home position
      PWMTests();

      break;




    case 'C':                         //Calls Set RTC Date and Time routine

      SetRTC();
      break;


    case 'W':                         //Calls routine to set Water temp limit 0 to 70C

      SetWaterTemp();
      break;

    case 'E':                         //Calls routine to Clear EPROM data

      ClearEEPROM();
      break;


    case 'A':                          // O = Toggle SystemON status

      OnOff();

      break;

    case 'D':                          // O = Toggle SystemON status


      //Serial.print("Heat sink res -");
      // Serial.println(sensors.getResolution(HSinkSensor), DEC);
      //  Serial.print("Bottom Sensor res -");Serial.println(sensors.getResolution(BottomSensor), DEC);
      break;
  }

}

//********************************************************************************************************************************************************
// Serial print commands used to Clear Terminal Screen and position Cursor to Home position

void ClearTerminal (void)
{
  Serial.write(27);             // ESC command

  Serial.print("[2J");          // Clear Screen...actually fills screen with carriage returns so earlier terminal data is not lost.
  Serial.write(27);
  Serial.print("[H");           // Cursor to Home position
}


//********************************************************************************************************************************************************
// Write Data Record to EEPROM

void WriteLog (void)
{
  unsigned int WatthrsInt = 0;
  t = rtc.getTime();             // Get data from the DS3231


  //MEMORY MAP *****************8
  //EEPROM.write(0, 10);             //Address Low byte
  //EEPROM.write(1, 0);              //Address High Byte = data logging Start Address (First 10 bytes reserved for setup variables etc)
  //EEPROM.write(2,60);              //Cylinder Temperature Limit
  //EEPROM.write(3,42);              //Relay temperature limit for Combi Pre-heat switching or valve control

  WatthrsInt = (unsigned int) Watthrs;

  //Write Data Record info; Byte 1 = Day, Byte 2 = Month, Byte 3 = Year-2000, Byte 4 = Whr Low Byte, Byte 5 = Whr High byte, Byte 6 = min temp, Byte 7 = Mx Temp
  EEPROM.write(PointerAddress, (t.date));
  EEPROM.write(PointerAddress + 1, (t.mon));
  EEPROM.write(PointerAddress + 2, (t.year - 2000));
  EEPROM.write(PointerAddress + 3, (WatthrsInt >> 0) & 0xFF);
  EEPROM.write(PointerAddress + 4, (WatthrsInt >> 8) & 0xFF);
  EEPROM.write(PointerAddress + 5, (MinTemp));     //Tmin
  EEPROM.write(PointerAddress + 6, (MaxTemp));     //Tmax



  if (PointerAddress < 1015) {
    PointerAddress = PointerAddress + 7;              // Advance Pointer Address to next record position. Stop advancing when full
  }

  LowByte = ((PointerAddress >> 0) & 0xFF);
  HighByte = ((PointerAddress >> 8) & 0xFF);
  EEPROM.write(0, LowByte);
  EEPROM.write(1, HighByte);

  if (RelayTemp > 43) {              //Reset a temporary high Relay trigger temp to <43C
    RelayTemp = 43;
  }

}


//**************************************************************************************************************************************

void ReadEEPROM(void)
{
  int Address;

  ClearTerminal ();                  //Clears Serial Terminal Screen and positions Cursor at Home position

  if (PointerAddress == 10) {
    Serial.print(F("Memory Empty!, No History Available"));
  }
  else {
    Serial.println(F("HISTORY:- PV Energy & Max/Min Water Temperature"));
    Serial.println("");
    Serial.println(F("D/M/YYYY,   WattHrs,  Tmin,  Tmax"));

    for (Address = 10; Address < PointerAddress; Address = Address + 7) {

      Serial.print(EEPROM.read(Address));
      Serial.print("/");
      Serial.print(EEPROM.read(Address + 1));
      Serial.print("/");
      Serial.print((EEPROM.read(Address + 2) + 2000));
      Serial.print(F(" ,    "));
      Serial.print((EEPROM.read(Address + 3) << 0 & 0xFF) + (EEPROM.read(Address + 4) << 8 & 0xFF00));
      Serial.print(F("  ,   "));
      Serial.print(EEPROM.read(Address + 5));
      Serial.print(F("  ,   "));
      Serial.println(EEPROM.read(Address + 6));
    }
    Serial.println(PointerAddress);
    Serial.print((PointerAddress * 10) / 102);
    Serial.println(F(" % Full"));
  }

}

//********************************************************************************************************************************************
void ClearEEPROM (void)
{
  ClearTerminal ();            //Clears Serial Terminal Screen and positions Cursor at Home position

  if (RxdInt1 != 101) {
    Serial.print(F("Clear Memory using Command  E 101"));
  }
  else {
    EEPROM.write(0, 10);          //Reset Address pointer Low byte
    EEPROM.write(1, 0);           //Reset Address pointer High Byte = data logging Start Address =10, First 10 bytes are reserved for pointers and setup variables)
    PointerAddress = 10;

    ClearTerminal ();            //Clears Serial Terminal Screen and positions Cursor at Home position
    Serial.print(F("Success! Data Logging memory Cleared"));
  }


}

//****************************************Set RTC via BLE***********************

void SetRTC (void)
{

  ClearTerminal ();                   //Clears Serial Terminal Screen and positions Cursor at Home position

  Serial.print(F("Current Time is:- "));
  Serial.print(F("\t\t\t\t\t"));
  Serial.print(rtc.getDateStr());
  Serial.print("   ");
  Serial.println(rtc.getTimeStr());

  if ((RxdInt1 == 0) && (RxdInt2 == 0)) {                              // check if initial command request contains Zero data values for HH:MM,(empty fields return 0!)
    Serial.print(F("Set RTC using the command 'C= hh mm dd mm yyyy'"));
  }
  else {


    if (RxdInt1 + RxdInt2 + RxdInt3 + RxdInt4 + RxdInt5) {

      rtc.setTime(RxdInt1, RxdInt2, 00);      // Set the h:m:s time using the Int values in received command
      rtc.setDate(RxdInt3, RxdInt4, RxdInt5); // Set Day, Month, Year
      rtc.setDOW();                           // Set Day-of-Week based upon the Date inside the DS3231s

      ClearTerminal ();            //Clears Serial Terminal Screen and positions Cursor at Home position

      Serial.print (F("\r\nClock Set to:- \r\n"));
      Serial.print(rtc.getDOWStr());
      Serial.print(F("\t\t\t"));
      Serial.print(rtc.getDateStr());
      Serial.print("  ");
      Serial.println(rtc.getTimeStr());

    }

  }

}

//*********************************************************************************************************************************************

void SetWaterTemp(void)                      // Serial command sets and stores the target water temperature
{
  ClearTerminal ();                   //Clears Serial Terminal Screen and positions Cursor at Home position

  if (RxdInt1 == 0) {
    Serial.print(F("Cylinder Temp limit = "));
    Serial.print(SetTemp);
    Serial.println(F("°C"));

    Serial.print(F("Pre-Heat Relay Activates at <="));
    Serial.print(RelayTemp);
    Serial.println(F("°C"));

    Serial.println(F("Set Hot Water and Pre-heat °C limits using 'W= HW PH' where HW= 1 to 65, PH = 1 to 43"));
  }
  else {
    if (RxdInt1 > 0 && RxdInt1 < 66) {
      EEPROM.write(2, RxdInt1);               //Save Cylinder water temperature threshold to EEPROM address 2.
      SetTemp = RxdInt1;
    }

    if (RxdInt2 > 0 && RxdInt2 < 46) {
      EEPROM.write(3, RxdInt2);               //Save Relay (Combi Pre-heat) activation temperature level to EEPROM address 2.
      RelayTemp = RxdInt2;
    }

    Serial.print("Cylinder Temperature Limit = ");
    Serial.print(SetTemp);
    Serial.println("°C");
    Serial.print("Pre-heat Relay activates at <= ");
    Serial.print(RelayTemp);
    Serial.println("°C");
  }
}





//*******************************************************************************************************************************************

void SerialRxWithEndMarker() {
  //("expects to receive strin of characters terminated in a LF. Max buffer size is currently 30 chars, text or Integer and an optional floating point value seperated by a comma");
  //("Enter data in this style "C/I 24.7 CRLF  "   C/I maybe a text command String or Integer value, after the space should be a float value );
  // You can ommit the comma and Float value if not required
  //Note the parsing methods are now implemented in the functions specific to commands

  static byte ndx = 0;
  char endMarker = '\n';
  char rc;

  // if (Serial.available() > 0) {
  while (Serial.available() > 0 && NewData == false) {       //reads in new characters from serial buffer
    rc = Serial.read();

    if (rc != endMarker) {                                   //Looks for a new line code to end the message
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string      // a new serial message has been received
      ndx = 0;
      NewData = true;

    }
  }
}


//*****************************************************************************************************************

void parseData() {

  // split the data into its parts
  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(receivedChars, " ");  // get the first part of string before the first space Char
  strcpy(RxdSerialMessage, strtokIndx);     // copy it to RxdSerial message - extracts text string at start of a message if required

  CommandChar = receivedChars[0];      //First Char in the initial command string is used to identify the Command

  strtokIndx = strtok(NULL, " ");      // this continues where the previous call left off
  RxdInt1 = atoi(strtokIndx);          // convert the next part of command upto next space to an integer

  strtokIndx = strtok(NULL, " ");
  RxdInt2 = atoi(strtokIndx);          // convert this part to a float - NOTE Currently Float values are not used in any commands
  //RxdInt2 = atof(strtokIndx);        // convert this part to a float - NOTE Currently Float values are not used in any commands

  strtokIndx = strtok(NULL, " ");
  RxdInt3 = atoi(strtokIndx);          // convert this part to a float - NOTE Currently Float values are not used in any commands

  strtokIndx = strtok(NULL, " ");
  RxdInt4 = atoi(strtokIndx);          // convert this part to a float - NOTE Currently Float values are not used in any commands

  strtokIndx = strtok(NULL, " ");
  RxdInt5 = atoi(strtokIndx);          // convert this part to a float - NOTE Currently Float values are not used in any commands

  NewData = false;                      // The last received message has been identified handled

  StreamData = false;                 // Stop BLE serial stream if a new message is received,

}







//*******************************************************************************************************************

void showParsedData() {
  Serial.print("Message ");            //Not Used by main program - just for testing only to see how command and interger values are extracted
  Serial.println(RxdSerialMessage);
  Serial.print("CommandChar ");
  Serial.println(CommandChar);
  Serial.print("RxdInt1 = ");
  Serial.println(RxdInt1);
  Serial.print("RxdInt2 = ");
  Serial.println(RxdInt2);
  Serial.print("RxdInt3 = ");
  Serial.println(RxdInt3);
  Serial.print("RxdInt4 = ");
  Serial.println(RxdInt4);
  Serial.print("RxdInt5 = ");
  Serial.println(RxdInt5);

}

//********************************************************************************************************************
void DataLogCheck (void)
{
  t = rtc.getTime();
  Hour = t.hour;

  if (LogDate != (t.date) && (Hour == 23)) { //Log data time? -Checks for a date change (i.e a new day) and 23:00 to log the days total data
    WriteLog();
    MaxTemp = MidTemp;                      // Max and Min temps & WHrs for the next day
    MinTemp = MidTemp;
    MaxW = 0;
    Watthrs = 0;
    LogDate = t.date;

  }

}

//*******************************************************************************************************************

void PWMTests(void)
{
  // This routine sets up values and flags to control Sweep and Fixed PWM tests within the main Loop

  ClearTerminal ();        //Clears Serial Terminal Screen and positions Cursor at Home position

  if (RxdInt1 == 0) {         //No Test mode info Supplied
    Serial.println("Set a Specific PWM using 'P 1 n' Where n = 0-1024");
    Serial.println("To start a PWM Sweep, use 'P 9999'");
  }
  else if (RxdInt1 == 1) {            //A fixed PWM command, eg  'P 1 500' has been rxd

    PWMTest = true;
    PWMDuty = RxdInt2;
    SystemOn = true;              //Will Turn system on for duration of test
    TimeoutStart = millis();       //Used to run test for only 60 secs

    analogWrite(PWM_EN_Pin, 250);     // force 0.65mS wide zero current pulse for arc prevention, 0 fully disables the gate driver output.
    ErrorCode = 0;
    ErrorChecks ();
    set_pwm_duty();
    delay(200);
  }


  else if (RxdInt1 == 9999) {       //the PWMSweep command 'P 9999' has been rxd
    PWMSweep = true;
    SystemOn = true;                //Will Turn system on for duration of test
    TimeoutStart = millis();        //Used to run test for only 60 secs
    analogWrite(PWM_EN_Pin, 250);   //forces 0.65mS wide zero current pulse for arc prevention, 0 fully disables the gate driver output
    ErrorCode = 0;
    ErrorChecks ();

    ArrayPosition = 0;
    PWMDuty = 0;
    set_pwm_duty();

    maxWatts = 0;                 //Resets all max values prior to staring a sweep in main loop
    maxPWM = 0;
    maxVolts = 0;
    maxAmps = 0;
    delay(200);                   //Lets V and I settle before the Read inputs routine is called
  }

}

//*****************************************************************************************************
void OnOff (void)  //Serial command Used to turn the System On or OFF
{
  ClearTerminal ();            //Clears Serial Terminal Screen and positions Cursor at Home position

  if (SystemOn == false) {
    SystemOn = true;
    ErrorCode = 0;
    ErrorChecks ();                       //Before turing PWM on, checks gate driver supply is ok
    Serial.println("System is currently ON");
  }
  else {
    SystemOn = false;
    Serial.println("System is OFF");
  }
}

//*****************************************************************************************************************

void ErrorChecks (void)           //Currently only checks gate driver supply is ok
{
  int Vin;

  Vin = analogRead(SupplyVin);    // read the input pin, 12V = ADC count of approx 896, 11V = 820 counts
  //Serial.println(Vin);
  if (Vin < 820) {
    SystemOn = false;
    PWMSweep = false;
    PWMTest = false;
    ErrorCode = 5;
  }
}

//********************************************************************************************************************

void LEDManager (void)
//An RGB LED is used to identify different operating conditions. This routine updates the LED colour state on each pass of the MPPT Loop.
{
  if (!SystemOn && ErrorCode == 0) {                 // Normal System Off, No Error codes,  Green LED blips on

    switch (MPPTcount) {

      case 1:
        Green_LED_ON = false;
        Red_LED_ON = false;
        break;

      case 2:
        Green_LED_ON = true;
        break;

      case 3:
        Green_LED_ON = false;
        break;

      case 4:
        Green_LED_ON = true;
        break;

      case 5:
        Green_LED_ON = false;
        break;

    }
  }

  else if (!SystemOn && ErrorCode != 0) {               //System is off with an Error Code - Show solid Red LED
    Green_LED_ON = false;

    switch (MPPTcount) {

      case 1:
        Red_LED_ON = false;
        break;

      case 5:
        Red_LED_ON = true;
        break;

      case 9:
        Red_LED_ON = false;
        break;

      case 15:
        Red_LED_ON = true;
        break;

      case 19:
        Red_LED_ON = false;
        break;

    }

  }

  else if (SystemOn && (BottomTemp >= (SetTemp - Hysteresis))) { // Temp control threshold  drops by 1degC once the Specified Set Point temp is exceeded
    Green_LED_ON = false;
    Red_LED_ON = false;
    Blue_LED_ON = true;
  }

  else if (SystemOn && (BottomTemp < (SetTemp - Hysteresis))) { // Turn the Blue LED off if below set temperature point
    Blue_LED_ON = false;


  }

  digitalWrite(GreenLED, !Green_LED_ON);      // During normal runtime MPPT operation, Updates LEDS with their On/off status
  digitalWrite(RedLED, !Red_LED_ON);          // Green = stepping down, Red = stepping up ...increasing PV power
  digitalWrite(BlueLED, !Blue_LED_ON);        // Blue = Temperature reached

  if (MPPTcount == 20) MPPTcount = 0;

}



//********************************************************************************************************************

void RelayControl(void)
{
  if ((TopTemp < RelayTemp) && (Hour >= 7) & (Hour < 23)) {    // The relay is off when top temp exceeds relay level and during daytime Hrs
    BoilerOn = true;                                          // Adjust active hours to suit your lifestyle!
  }
  else BoilerOn = false;

  digitalWrite(Aux_Relay, BoilerOn);
}


//********************************************************************************************************************

void UpdateNextionWatts (void) {

  int BarWidth = PVWatts;  // remaps PVWatts FSD range to match the bar width
  mySerial.print(F("Watts.val="));  // X,Y Position...Watts Linear bargraph size & pos = X, Y, W, H = 70,1,285,43)
  mySerial.print(BarWidth);  // This is the value you want to send to that object and atribute mentioned before.
  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);

  mySerial.print(F("p5.pic=16")); // Show Bluetooth Icon
  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);

}
//********************************************************************************************************************

void UpdateNextionWhrs (void) {

  // Update Whrs Variable to Nextion
  mySerial.print(F("Whrs.val="));
  mySerial.print((int)Watthrs);    // Whrs value as text we want to send
  mySerial.write(0xff);           // send these three lines after each command sent
  mySerial.write(0xff);
  mySerial.write(0xff);


}

//***********************************************************************************************************
void UpdateNextionTemps(void) {

  //Bottom Temp
  int BarWidth = BottomTemp * 10;
  mySerial.print(F("BotC.val="));  // Lower Temp progress Bar size & position values X, Y, W, H = 14,187,65,37)
  mySerial.print(BarWidth);      // This sets width of the image cropped from the active colured bar image.
  mySerial.write(0xff);     // This section crops out the appropriate Width from the Active coloured bar image
  mySerial.write(0xff);
  mySerial.write(0xff);


  //Middle Temp
  BarWidth = MidTemp * 10;
  mySerial.print(F("MidC.val="));  // Mid Temp progress Bar size & position values X, Y, W, H = 14,155,65,37
  mySerial.print(BarWidth);  // This is the value you want to send to that object and atribute mentioned before.
  mySerial.write(0xff);     // This section crops out the appropriate Width from the Active coloured bar image
  mySerial.write(0xff);
  mySerial.write(0xff);

  // Top Temp
  BarWidth = TopTemp * 10; // remaps PVWatts FSD range to match the bar width
  mySerial.print(F("TopC.val="));  // Mid Temp progress Bar size & position values X, Y, W, H = 14,121,65,37).
  mySerial.print(BarWidth);      // This is the value you want to send to that object and atribute mentioned before.
  mySerial.write(0xff);     // This section crops out the appropriate Width from the Active coloured bar image
  mySerial.write(0xff);
  mySerial.write(0xff);

}

//***********************************************************************************
void UpdateNextionMaxMins (void) {

  // Send todays MaxWatts varible to the remote Nextion Display
  mySerial.print(F("MaxW.val="));
  mySerial.print(MaxW);
  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);

  // Send the MinTemp Variable to the remote Nextion display
  int BarWidth = MinTemp * 10; // change float single decimal place to int by x10 multiply
  mySerial.print(F("MinC.val="));  // update Min temp variable
  mySerial.print(BarWidth);
  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);

  // Send the MaxTemp Variable to the remote Nextion Display

  BarWidth = MaxTemp * 10; // change float single decimal place to int by x10 multiply
  mySerial.print(F("MaxC.val="));  // update Min temp variable
  mySerial.print(BarWidth);
  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);


}


//*****************************************************************************************************

void UpdateNextionIcons(void) {

  if (BoilerOn) {
    mySerial.print(F("p1.pic=3")); // Changes the picture holder image, image 3 = boiler ON, 4 = bolier off
  }
  else {
    mySerial.print(F("p1.pic=4")); // Changes the picture holder image, image 3 = boiler ON, 4 = bolier off
  }

  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);


  if (SystemOn) {
    mySerial.print(F("b1.pic=13")); // Changes the picture holder image 13 =ON,
    //14 =OFF, 9 Top temp err, 10, Mid temp, 11 =Bot Temp, 12 = 12V bolier off
  }
  else {
    switch (ErrorCode) {

      case 0:
        mySerial.print(F("b1.pic=14")); // Changes the picture holder image 13 =ON,
        //14 =OFF, 9 Top temp err, 10, Mid temp, 11 =Bot Temp, 12 = 12V bolier off
        break;

      case 1:
        mySerial.print(F("b1.pic=11")); // Changes the picture holder image 13 =ON,
        //14 =OFF, 9 Top temp err, 10, Mid temp, 11 =Bot Temp, 12 = 12V bolier off
        break;

      case 2:
        mySerial.print(F("b1.pic=10")); // Changes the picture holder image 13 =ON,
        //14 =OFF, 9 Top temp err, 10= Mid temp, 11 =Bot Temp, 12 = 12V bolier off
        break;

      case 3:
        mySerial.print(F("b1.pic=9")); // Changes the picture holder image 13 =ON,
        //14 =OFF, 9 Top temp err, 10, Mid temp, 11 =Bot Temp, 12 = 12V bolier off
        break;

      case 4:
        mySerial.print(F("b1.pic=15")); // Changes the picture holder image 13 =ON, 15 = Fet Temp,
        //14 =OFF, 9 Top temp err, 10, Mid temp, 11 =Bot Temp, 12 = 12V bolier off
        break;

      case 5:
        mySerial.print(F("b1.pic=12")); // Changes the picture holder image 13 =ON,
        //14 =OFF, 9 Top temp err, 10, Mid temp, 11 =Bot Temp, 12 = 12V bolier off
        break;
    }
  }
  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);

  mySerial.print(F("p5.pic=17")); // replace Bluetooth icon with black background
  mySerial.write(0xff);
  mySerial.write(0xff);
  mySerial.write(0xff);
}


//***********************************************************************************************

Schematics

LoadMaster XP Schematic - MPPT PV Hot Water PCB Rev4
Please note:- this diagram incorporates a number of component positions which are provided to offer the potential for different configurations and functionality. PCB should be populated as required or according to build instructions (see downloads at the end)

Comments

Similar projects you might like

Sensor-Controlled Guard Lights

Project showcase by LIMPINGLIM

  • 2,444 views
  • 1 comment
  • 6 respects

Personal Weather Station (Arduino+ ESP8266 + Thingspeak)

Project tutorial by Jayraj Desai

  • 80,026 views
  • 35 comments
  • 158 respects

Arduino / ESP8266 RS485 MODBUS Anemometer

Project in progress by philippedc

  • 14,185 views
  • 9 comments
  • 17 respects

Humidity Sensor-Controlled Bathroom Exhaust Fan

Project showcase by LIMPINGLIM

  • 8,492 views
  • 11 comments
  • 28 respects

Basement/Crawlspace Ventilation System

Project tutorial by Christian

  • 7,433 views
  • 8 comments
  • 50 respects

Autonomous Indoor Greenhouse - Mature Real Working Project

Project showcase by vinikon

  • 32,535 views
  • 46 comments
  • 191 respects
Add projectSign up / Login