A Remote Wireless Connected Colour Nextion Display

A Remote Wireless Connected Colour Nextion Display © LGPL

Display LoadMaster Solar data on a HC-12 Wireless connected Nextion (3.2" Enhanced) Display. How to use a Nextion.

  • 1,246 views
  • 2 comments
  • 6 respects

Components and supplies

Necessary tools and machines

About this project

Introduction

In an earlier project I presented Loadmaster XP, a maximum power point (mppt) PV Solar Hot Water controller.

With such solar projects, you can easily become obsessed with understanding data:- How many KWhrs of solar energy have I received today?, Whats the PV power now? What was the highest power delivered today?, What is the current water temperature? How much hot water is there in the cylinder? What's todays Max&Min temperature range?, Is everything working ok?, etc, etc

I wanted a colour display providing more visual appeal than a 20x4 character LCD and with a viewing area able to display all parameters at a single glance. I really didn't fancy the idea of vast amounts of code that often goes with driving a colour LCD touchscreen. I also wanted this display to operate 100% reliably when located anywhere in our house using a wireless link. Enter a Nextion 3.2" Enhanced Display and a pair of HC-12 wireless modules:-

Nextion displays include an on board processor, memory and touch display and are supported by this free Editor software for creation of the graphical user interfaces.

The Nextion Editor software enables you to quickly (well, once you understand it!) develop a user interface from drag-and-drop components which include multi-state graphic images, text boxes, buttons, gauges, sliders, number boxes and progress bars etc.).

The Nextion display is connected to a micro controller device (i.e The Arduino Nano in aLoadmaster) via TTL Serial (5V, TX, RX, GND). Simple ASCII commands received from the Loadmaster are used control all the graphical elements and update data values shown on the display. In addition 'event notifications' (for say touch screen button presses) can be transmitted serially from the display to the peripheral MCU (Arduino) for it to act upon. The LoadMaster remote display application discussed here is aimed at updating the display only using just one-way traffic sent from the Arduino to the display. (In my application, the Nextion is located behind a glass kitchen cupboard door to provide 'at a glance' information only,-thetouch screen cantfunctionthroughglass!):-

The GUI includes one touch button element which is configured to send a command for ON/OFF switching, however this is not actually used.

Loadmaster is setup and configured (not a frequent operation) using a serial terminal interface, however, with more time it would be possible for the Nextion to become a full functionality, bi-directional touch screen control interface.

The Nextion smart display concept is fantastic for relatively quick results and the cost is highly attractive (almost comparable to producing a remote display solution based upon another Nano and a 20x4 graphics module etc). I found 3.2" Enhanced displays at a bargain price on Banggood.

However! (and I don't believe I am alone in saying this), the lack of documentation with code / graphics creation examples made the learning curve somewhat confusing and time consuming. I'm certainly no Nextion expert but hopefully this project may help to clarify a few aspects of using this remote display with Loadmaster, alternatively you can take the simple route and just load my default LoadMaster display code which should take <5 mins:-

Connecting the Nextion Display to LoadMaster.

The Nextion may be directly wired to connector CN6 of a LoadMaster PCB V4 as follows:-

Or, for a remote display via HC-12 (or other) wireless modules, wire as follows:-

Programming the display with my default display code is simple:-

From the software downloads section, download the 'LoadMaster Nextion Disp- 3kw FSD.tft' file.

Copy this file onto a micro SD card (<32GB). Insert the card into the Nextion 3.2" Enhanced display's socket and then apply 5V. The Display will show programming progress. When complete, remove the card an re-apply power. The display is now programmed. Simple, you should have a working remote display.

The Nextion's default baud rate is 9600. This is suited the HC-12 radio modules and so this was left at 9600.

I wanted the display to be wireless and operate reliably at 30m or so from the LoadMaster through brick walls etc. After trying a few different modules, I selected to use 433Mhz HC-12 modules which gave excellent 'error free' range well beyond 50m (using the basic wire spring antennas).

HC-12 modules can be programmed to operate on different RF Channels and baud rates etc, it is a very well documented module on the web and so, enough said.

Creating a Graphical User Interface..A basic overview

To understand the basics of how the Nextion is used I will focus on the graphical elements, especially the power of cropped images and how best to update variable values on the display with minimum of serial 'traffic'.

Nextion DisplayCodingand Libraries

There are several Nextion libraries for Arduino. I initially looked at using these but then, like others, concluded that using a library for a small to medium user interface application probably has little benefit and adds more complexity and confusion! If you have lots of pages and buttons then yes, consider using a library.

There are several approaches to using and controlling a Nextion, I will attempt to explain a few methods here.

My LoadMaster Display is shown below:-

Active display elements include:-

  • Linear Bargraphs - with max / Min bars. The Nextions toolbox contains a Progress Bar tool however the Cropped image function provides a good alternative method.
  • Gauge / Meter
  • Number Boxes for Watts and Temperatures.
  • Picture elements - providing a flashing wireless 'data received' icon.
  • TouchPush button for ON/OFF Status - The Button also displays fault error messages

Graphics work

Firstly you need to know your Display resolution (3.2" Enhanced = 400x240). In the editor open a new project selecting the appropriate display and Landscape / Portrait layout.

Generally the attribute box for Page 0 will specify either a solid background colour or a background image (for a more attractive look).

The background image file started life in MS PowerPoint. This handles a range of image file formats including transparent PNG images, it is easy to layout the graphics and add lines and text etc. A very important point is to create a rectangular background image having an aspect ratio to exactly match the display (i.e 400x240 pixels = say 16cm x 9.6cm). Use PowerPoint's size values to set the dimensions. (Example PowerPoint files are available in the downloads section)

PowerPoint allows you to select the background image and 'Save as a picture', however this has no option to set the actual pixel dimensions. Instead I select the image and paste it into Inkscape. Inkscape can also be used to add other graphic effects (I found it adds text elements better than Powerpoint without altering the aspect ratio). Finally, use Inkscape to 'Export as a PNG' using required pixel dimensions, 400x240.

I created a background image which included all bargraph elements in their blank state.

and another image with all segments on:-

Why not use the Nextion Progress Bar tool? Well you can. Progress bar elements can be configured to show either a solid colour progress bar with a width dependent upon the val attribute (0-100), or they can show part of an associated image. To produce colour graduated bars, you would need to create or 'cut-out' images of the different sized 'full' colour bars using the correct pixel width, height and with edges to match and blend into the background ('ppic' in the Attribute settings) plus the image which is shown for empty parts of the bar (bpic). Any shading effects, anti aliasing /blending into the background may become more awkward.

My chosen method to create the progress bars is discussed next and is based on using the cropped image instruction 'picq' (not to be confused with 'crop' in the toolbox). With this method, you only need to create and load two 'full size' screen images, one with all elements in their on condition, the other with them all off.

From the Nextion instructions:-

usage: picq <x>, <y>, <w>, <h>, <picid>

<x> is the x coordinate of upper left corner of defined crop area

<y> is the y coordinate of upper left corner of defined crop area

<w> is the width of the defined crop area

<h> is the height of the defined crop area

<picid> is the ID number of the associated full screen image.

The cropped image will be shown on top of the pages background image, eg

The only problem we have now is that if we now wish to display only 25% of the bar width, we need some method to clear the area of the bar above the target width value to 100%. picq can be used to do this by cropping and displaying a section from the 'off' image (image ID = 5 in this example).

The above would be achieved in Arduio using the following code:-

 void UpdateNextionWatts (void) {
 int BarWidth = map(PVWatts, 0, 3000, 0, 300);  // map PVWatts  FSD range to match the bar width
 mySerial.print(F("picq 84,3,"));  // X,Y Position
 mySerial.print(BarWidth);  // show the colour bar to the relevant width. 
 mySerial.print(F(",51,6"));  // This is the value Serial.write(0xff);  // We always have to send this three lines after each command sent to the nextion display.
 mySerial.write(0xff);
 mySerial.write(0xff);
 mySerial.write(0xff);
 mySerial.print(F("picq "));         
 mySerial.print(84 + BarWidth);   // This will clear the area of the bar
 mySerial.print(F(",3,"));        // above the value of BarWidth
 mySerial.print (300 - BarWidth);
 mySerial.print(F(",51,5")); 
 mySerial.write(0xff);
 mySerial.write(0xff);
 mySerial.write(0xff);

However! ...this is a sizeable serial data exchange plus next we will need to send commands to update the numeric value of PVwatts.

UsingVariables

It is possible to create an integer variable in the Nextion editor. We can then send just the value of a variable to the Nextion and then manage the updating of individual screen elements using a small amount of code in the Nextion.

If we add a timer element to the display, we can use this to periodically update all elements of the display based on received values of different variables.

Click on the timer element, tm0, to see its Event code window :-

Every time Timer 0 triggers it runs this code:-

tm0.en=1 // tm0 Start
tm0.tim=500 // tm0 Time base 500 mS
//********************
//use cropped images tocreate graduated Top Temp progress bar
width.val=TopC.val/8 //600 = 60degC and bar width =60 pixels
picq 18,90,width.val,37,6
picq17+width.val,90,75-width.val,37,5
x0.val=TopC.val // Update XFloat display for Top Tempvalue
//
//use cropped images tocreate graduated Mid Temp progress bar.
width.val=MidC.val/8 //600 = 60degC and bar width =60 pixels
picq 18,136,width.val,34,6
picq18+width.val,136,75-width.val,34,5
x1.val=MidC.val // Update XFloat display for Mid Tempvalue
//
//use cropped images tocreate graduated Bottom Temp progress bar.
width.val=BotC.val/8 //600 = 60degC and bar width =60pixels
picq 18,187,width.val,34,6
picq18+width.val,187,75-width.val,34,5
x2.val=BotC.val // Update XFloat box for Bottom Temp value
//use cropped images tocreate Min Mid Temp bar.
width.val=MinC.val/8 //600 = 60degC and bar width =60pixels
picq 18,170,width.val,5,6
picq18+width.val,170,75-width.val,5,5
//use cropped image to showmax mid temp bar.
width.val=MaxC.val/8 //600 = 60degC and bar width =60pixels
picq 18,131,width.val,5,6
picq18+width.val,131,75-width.val,5,5
//Show Whrs on gauge, widthvariable is used to hold the scaled angular needle position.
n0.val=Whrs.val
width.val=Whrs.val/35 //Gauge to go 6 to 235 for 0 to8000W = W/35 +6
z0.val=width.val+6
//
//create graduated Watts progress bar
width.val=Watts.val/10 //600 = 60degC and bar width =60 pixels
picq 85,2,width.val,36,6
picq85+width.val,2,300-width.val,36,5
n1.val=Watts.val // Update watts number box
//
// create graduated Max Watts progress bar
width.val=MaxW.val/10 //600 = 60degC and bar width =60 pixels
picq 85,38,width.val,12,6
picq85+width.val,38,300-width.val,12,5

Basically every timer interval this code will update the various display elements based upon the value of variables.

Downloads

In the Schematics and circuit diagrams section you will find:-

  • An example PowerPoint file used to create the graphics.
  • The Nextion development code - to aid understanding or for your modification!
  • The TFT file which is loaded into the display using a uSD card.

Code

Loadmaster Mppt Hot Water Controller FirmwareArduino
This is the firmware for the LoadMaster XP V4 PCB. This is supplied to illustrate the code which transmits data to the remote Nextion 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

Nextion LoadMaster Display Programming Code
Open this in the Nextion development application to help understand or modify the display functionality
loadmaster_timerupdate_05_k6igvduShs.HMI
LoadMaster - Nextion Remote Display TFT code file
LoadMaster Nextion Disp- 3kw FSD.tft - Copy this to a uSD card (<32GB) and fit into display to program it.
loadmaster_nextion_disp-_3kw_fsd_pOYnJezC8e.tft
Example Power Point file used to create screen graphics
Powerpoint was used to create screen layouts - images subsequently pasted into InkScape and saved as 400x240 PNG files
nextion_layout_ideas_v6_UzXB4U5d4N.pptx

Comments

Similar projects you might like

CeriTech (Coffee Beans Drying Process)

Project showcase by Ahmad Radhy

  • 1,992 views
  • 1 comment
  • 11 respects

Low Power RF Beacon for Remote Sensing & Communication

Project tutorial by Shahariar

  • 6,779 views
  • 1 comment
  • 20 respects

LCD Screens and the Arduino Uno

Project tutorial by Mahamudul Karim Khondaker

  • 1,710 views
  • 0 comments
  • 1 respect

SmartAgro

Project tutorial by Andrei Florian

  • 31,818 views
  • 20 comments
  • 102 respects

Arduino / ESP8266 RS485 MODBUS Anemometer

Project in progress by philippedc

  • 14,587 views
  • 9 comments
  • 17 respects

LED Matrix + Motion Sensor Door Display [Arduino Holiday]

Project tutorial by HeathenHacks

  • 7,380 views
  • 5 comments
  • 25 respects
Add projectSign up / Login