NodeMcu Home Automation

Projects - most projects save $100s/year each. Total for all projects saves $1,000+/year.   NodeMCU device is only $4 Cdn on ebay. See component costs below.

It's possible to reduce your municipal water consumption (variable part of water and sewer bills) by 80-90% with some of the projects and other conservation methods.

This is about 50-75 litres per person per day. Down from the Canadian average of 250 litres per person per day.

The demo covers a lot of subjects: input and output, digital and analog sensors, electrical, electronics, software automation control programming and web page definition html code. Read and display temperature, light level, distance and control a lamp and a fan from buttons on a web page.

Other Projects

ESP32 Cam sends email with pic attached  https://hackaday.io/project/183383-esp32-cam-emails-picture

Plant Watering System  https://hackaday.io/project/181370-nodemcu-plant-watering-with-web-page-hmi

ESP-NOW example. Send data directly between NodeMCUs  https://hackaday.io/project/181289-esp-now-inexpensive-microcontroller-scada-system

Get Time and Date from the Internet https://hackaday.io/project/174577-esp8266-time-and-date-from-internet

Detect Water Leaks with a $10 WiFi Cam https://hackaday.io/project/175152-detect-water-leaks-with-a-10-wifi-webcam

Robot Arm with Web Page HMI https://hackaday.io/project/177278-robot-arm-with-web-page-hmi

5,000 Volt Birdfeeder  https://hackaday.io/project/172250-5000-volt-birdfeeder 

34", $100, 20 lb, Triple Weed Whacker Lawnmower https://hackaday.io/project/166108-34-100-20-lb-triple-weed-whacker-lawnmower


NodeMCU I/O Add a Real Time Clock and a lot of digital and analog I/O with I2C bus devices using only 2 I/O. Many temperature sensors can be read using only 1 input configured as One Wire bus. 


Code is at bottom of page. Code is for item 1 but parts of it are used for all the others. It includes real time clock code. Upload the code wirelessly then put the local IP address of the device into a browser to see the web page without data. No app needed. Click on the Turn On and Turn Off buttons. Plug in a RTC and DS18B20 to see the time and temp. Change the address of the background image to one of your own.


The following programming was done with XP SP2 on an old desktop pc with wired ethernet. Wirelessly uploaded to multiple remote NodeMCUs via OTA with no issues. Web page HMIs are displayed on an Android phone or tablet where they are displayed very quickly. XP displays them painfully slowly. I am trying to replace the old XP machine with Windows 10 on a new laptop but am having nothing but problems. Not only was OTA uploading a problem but the web pages displays are painfully slow or don't work at all. Ports would work intially in Arduino IDE but would then disappear after a minute or so. Installing the latest Python 3.7.2 instead of 2.7.13 fixed that problem but many others still exist. The delay(300); after html definition and before client.stop() solves a lot of the above problems and makes the web pages display very rapidly in desktop Chrome in XP or Windows 10. Other browsers give a "connection was reset" error after briefly displaying web page.

Instructions for those familiar with Arduino but new to NodeMCU are at the top of the code. Follow the 4 YouTube videos.

For those with an electronics, software and automation background or are willing to learn.

It is helpful if you are already familiar with programming Arduino boards.

Be aware it is not as easy to work with initially since it is not a supported Arduino device within Arduino IDE so you have to add it to the list of boards. See "getting started" video link in the code.

Monitor and control web page is defined within controller code and hosted on the device. Need to learn html for this. Wirelessly upload your code to the remote device.

Learn to program and save $750+ per year in utility costs by modifying your existing gas furnace and electric water heater control for about $25 in components. You likely won't even notice the changes. 

See schematic and details below the pinout images. 

This mod only describes changes to heating not air conditioning although the technique may also apply. The outside temperature in Bracebridge varies from cool to very cold (be!ow -30C) for 75% of the year so a very long heating season.

YouTube demo video:  https://youtu.be/AcMQKKVfWcM

Hardware Details

NodeMCU ESP8266 12E

NodeMCU

I/O hooked up to NodeMCU 12E

A0 analog input photoresistor from voltage divider

D0 not used

D1 SCL  I2C bus connections used for Real Time Clock DS3231 and MCP23017 I/O expander chip

D2 SDA I2C bus connections used for Real Time Clock  DS3231 and MCP23017 I/O expander chip

D3 One Wire bus for two (or more) DS18B20 stainless steel temperature sensors

D4 not used

D5 digital communication to DHT22 temperature and humidity sensor

D6 Yellow LED

D7 Red LED

D8 not used

I/O hooked up to MCP23017 16 I/O expander chip

4   Input button

5   Green LED

6   Yellow LED

7   Red LED

8   Relay 1

9   Relay 2

10 Relay 3

11 Relay 4

NodeMCU 12E

Home Automation

Total cost of all hardware from Ebay Asian suppliers shown in the picture is around $25 free shipping and no tax. 6 week to 2.5 month!!! delivery though. Tracking is meaningless. Sometimes the parts don't show up at all and you get your money back minus exchange fee. North American suppliers can be 3-10X more expensive plus high shipping cost but delivery in a few days. Canadian dollars. The software can be tough/frustrating to figure out as well but there are tons of YouTube Videos and hobbyist web pages to help with each piece of the puzzle. The pinout images and most of the code functions were cut and pasted from other websites and applied to this project.

Nodemcu 12E                             $4.10

17X10 Protoboard                      $1.29 

4 channel relay board                $3.49

Dupont 120 wires                       $3.34

MCP23017 I/O expander           $1.50

DS18B20 SS temp sensor          $1.87

DHT22 temp/humidity               $3.53

DS3231 Real time clock             $1.60  

NodeMCU

NodeMCU 12E_wiring

NodeMCU

Telephone cord and jacks used for wiring to remote temperature sensors. Any multi-conductor cable is OK as voltage and current is very low.

NodeMCU 12E_wiring_1

NodeMCU12E complete. High current relay not shown. It is remote mounted on water heater so no new high voltage and current additional wiring needed. Only 5mA 5VDC control wiring from 4 channel relay needed.

NodeMCU 12E complete

Connections to remote sensor cables. Round telephone jack from dual wall plate.

Includes strain relief. Check continuity on jacks at both ends. Some switch colours.

Furnace ON sensor

Photoresistor inside plastic tubing optically detects gas furnace flame ON yellow LED. Used to modify furnace control for 33% saving in gas WITHOUT lowering thermostat. Two relays on relay board interrupt thermostat circuit and furnace Hi circuit. All relays are wired on NC contacts so unplugging controller reverts system back to original control logic.

$3.29 relay used to turn water heater on and off. 5VDC 5mA from controller switches 30A 240VAC.

http://www.ebay.ca/itm/New-5V-30A-1-Channel-Relay-Module-Optocoupler-H-L-Level-Triger-for-Arduino-WR-/282496539290?hash=item41c61b1a9a

Relay is 5VDC so 5V from VU on NodeMCU 12E goes to a relay on the 4 channel relay board for switching.  

In Ontario electricity is half cost before 7 a.m. compared to after 7 a.m. in the winter.

Water heater normally is only on 1 a.m. to 7 a.m on weekdays plus weekends. Includes manual override. Override is done with a web page now with NodeMCU 12E. The dishwasher has a delay start timer so it only runs after 2 a.m. Clothes washer only uses cold water.

Screenshot below of browser monitor/control page from Android tablet. No App install required. Works on Apple, PC or Android or any device that can display a web page.

 Just put local IP address of Nodemcu device in browser. 

Center column of numbers is seconds furnace is running total, per hour and %. Resets at 9 am. I can predict exactly what the gas meter says every day based on the total number of seconds the furnace was running in the previous 24 hrs and my furnace input Btu rating. Replaced Arduino Uno with Nodemcu device Sept.10, 2017.

NodeMCU 12E Webserver Monitor and Control Page HMI

Home Automation

NodeMCU 12E Webserver Monitor and Control Page

Original Arduino Uno serial HMI. Same data. Only available on one wired device. Not that cool.

Click for larger image.

Previous Arduino Uno water heater manual override and heat ON indicator located in a closet upstairs above the water heater. Also not that cool. Replaced with web page Auto select on NodeMcu device. Only used on weekday holidays if there are a lot of people around and a lot of hot water is needed. Plastic box is a soap travel container.

Marine Deicer Control

Marine Deicer Project. In order to efficiently stop water freezing around boats and docks I was asked to create a control board for a deicer with a web interface. Below is the monitor and control page. It uses a fraction of the furnace and water heater capability.

There is a submerged motor and propeller that brings warmer water up from below to stop the surface water from freezing.

The problem is the motor uses 1/2 hp which consumes a lot of power even when it's not needed on warmer days.

This project uses a battery backed real time clock so all the power failures don't affect the controller time, a temperature sensor so it doesn't run on warmer days, a 30A relay for the motor, a transistor to switch the 5V for the relay, an ACS712 current sensor board to monitor if the motor is actually running when it should and an email alert if it isn't.


IFTTT

Automatic text or email or tweet alerts with If This Then That (IFTTT). If you give IFTTT access to your Google Drive it can create and record the data to a Google Sheets spreadsheet automatically. 100 SMS alerts per month max. No limit emails or IFTTT notifications if you install the app. Maker channel is now called Webhooks. Can include up to 3 variables in notification. Put code above web page code. Limit alerts to one per state change in code. If you have set up OTA programming you don't need the wifi connect ssid and password code in the example. This example worked for me. Other examples didn't work but they relied on a lot of libraries which was probably the problem.

There are two alerts. One sends an email if the motor current is too high or too low (Deicer_Fail), repeating every hour and the other is a daily total Kwh consumption.

You can also send data to a Google cloud spreadsheet viewable from anywhere. See working code at the bottom of the page.

Very quick overview. See second example in the video here:

http://www.instructables.com/id/Send-Notifications-to-Your-Phone-From-an-ESP8266/

Very syntax sensitive. Copy and paste code from here:

http://www.instructables.com/id/Sparkfun-Thing-How-to-Trigger-IFTTT-Event-and-Send/

https://bigjungle.net/blog/2015/6/26/connect-maker-to-anything-ifttt

Set up account here and get key to use in code:

Ifttt.com

Search code below for alert and iftt application:

https://sites.google.com/site/nodemcu12e/#code

Example Alert

The deicer would normally only run at night when power is half the cost unless it's really cold. Run time per hour depends on temperature and date. Longer in Feb compared to Dec for the same air temp because the water is colder. The blue propeller spins if the motor is ON as it's an animated GIF file.

There are likely a lot of similar applications where operation of high powered devices can be tailored for more appropriate use and take advantage of lower cost power Time Of Use. Much more sophisticated control strategies than just a simple timer could result in large cost savings.

Deicer Project HMI under construction

Nodemcu Deicer Control

Deicer Solid State Relay Version

Deicer Solid State Relay

  MQTT demo

Monitor, Record and Control from anywhere using MQTT

The web page HMIs shown elsewhere on this site work within the same Wifi as the device, not from anywhere.

Control dashboards shown above are much easier to create and modify than a web page but amount of data and rate is limited.

If you want bidirectional control and monitoring from anywhere in the world you can use MQTT. 

With the free Adafruit MQTT broker the rate, amount of data and display is limited. See Adafruit.io.

Demo video: 

https://drive.google.com/file/d/1agqAzsVEomJLMy864SY1XzXDtvMiHkHU/view?usp=sharing

Tutorials:

https://m.youtube.com/watch?v=9G-nMGcELG8o

https://m.youtube.com/watch?v=VjpONmC2tac&t=121s

https://learn.adafruit.com/adafruit-io/mqtt-api

Load Basic OTA first to enable wireless uploads. 

See video here: https://m.youtube.com/watch?v=ZVA1pV6WUQg

 

Code example in demo video from Adafruit modified for wireless upload.

Code: https://sites.google.com/site/mqttdemocode/


Hot Water Line Preheater for instant hot water - save $180+/year

Same components as above plus deicing cable minus current sensor. No plumbing changes required at all. Saving depends on how far the hot water tank is from the shower or how much time, energy and water is wasted waiting for hot water. This uses less energy to heat the water in the line because it starts at basement ceiling temperature not the really cold incoming water to the hot water tank. I can even turn the hot water tank off if there is only one person present. The hot water in the line is enough for one short shower (barely).

Preheat the hot water line between the tank and the shower for instant hot water.

Save time, energy and water going down the drain waiting while it heats up. Only 140 watts for 1.5 hours at mostly off peak rates. Heating cable is less than 5 watts per foot so max temp is self-limiting to less than 50C. Check this for safety. My hot water tank is 4500 watts. Auto function turns preheater on at 6 a.m. and off at 8 a.m. weekdays, off at 9 a.m. on weekends. Hot water tank is set to 50C so showering will turn the system off as it is set to 40C. Auto Off means On continuously. Deicing cable is energized until setpoint temp is met then off until 1 degree C less than setpoint during morning heating time.

If your hot water line is not accessible you can use a Watts instant hot water pump instead. It pumps water from the hot line into the cold line until it gets hot so the water is not wasted. Home Depot has them but they are far more expensive and require small DIY plumbing additions. Check out water and power cost savings under reviews. Part of it mounts under the sink nearest the shower. They come with a simple timer but could also benefit from a day of the week timer, an actual temperature sensor and an override from a web page as above.

https://www.homedepot.ca/en/home/p.instant-hot-water-recirculating-system.1000728120.html

Preheater HMI

WiFi Waterbed Temperature Control HMI

WiFi Waterbed Temperature Control  The ancient mechanical waterbed thermostat failed ON so the temperature kept climbing uncontrolled. Luckily we were home at the time.

I tossed the faulty thermostat and replaced it with another NodeMCU controller system. All the new components fit inside the original plastic thermostat housing. All of the original 120VAC wiring is reused.

Housing must be plastic for the NodeMCU antenna. Total cost of components is less than $15 Cdn.

Heater is 325 Watts so significant power is involved although once it's up to temp it's only on about 20% of the time. 

It only heats now at low cost power Time of Use 7 pm to 7 am using the DS3231 battery backed clock module. 

Power failures don't affect the correct time now like they threw off the original mechanical timer.

Temperature is controlled within +/-0.15C of setpoint. Resolution of digital temp sensor is set to 0.125C.

The temperature only falls 1.25C during the 12 hrs 7 am to 7 pm that the waterbed heater is totally off at high cost Time of Use weekdays. It gets back up to setpoint temp in 2 hrs after 7pm.

Daily kWh usage is calculated at midnight from the recorded previous day total daily seconds ON/3600 for hours X 0.325 kW.

In this case the cost savings are very small if the heater is only powered 7pm to 7am vs 24 hrs compared to other higher power devices like the electric hot water tank and hot tub.

AWAY mode (not implemented yet) will enable the bedside light to go on for a few hours in the evening for security. Random on and off times. It will use a second of four available 10A relays.

I could have AWAY mode also turn off the waterbed but it would have to be turned on again before returning because it could take 12 hrs to go from room temp to the setpoint again.

It could be programmed to turn on again based on the date of the day before returning or turned on remotely via MQTT. Temp could also be monitored remotely via MQTT. See MQTT demo above.

You can also have it automatically put data into a cloud spreadsheet, viewable from anywhere, or send you an email or text via IFTT.

This is the sixth home automation project with NodeMCU. Total saving is $1,000+/year. Another example where a small change you won't notice can save you money.

Same components plus deicing cable used to preheat the long copper hot water line between the hot water tank and shower. This saves time, money, water and power.

NodeMCU, DS3231 clock and 30A 240VAC relay used to enable electric water heater only between 1 am and 7 am weekdays and weekends. Override from web page if needed

Components used

Original thermostat. Reused the plastic housing and 120VAC wiring.

Wireless Programming with Arduino IDE. Program any NodeMCU device from anywhere in your house. Just double check you are connected to the right device before uploading.

Zero Municipal Water Toilet Save $250+/ year in water and sewer costs

Fully automatic. No changes to existing plumbing. Can always switch back if needed.

Very low cost way to save on your water and sewer bills if the variable parts of your bill are very expensive. In my area costs are very high,  $4.36/cubic metre in 2018.

Non-potable source water can be a shallow well, sump pump well, air conditioning condensate, rain water or tub and shower drain water or any combination.

A shallow well can easily be drilled with a Shop-Vac. https://youtu.be/2UiHwK33Y9A

Or just cut some teeth in the end of a piece of PVC like this 3 part video: https://youtu.be/2poYu1zKtoo

HMI

This pump will lift water 5 metres or more than 1 storey from basement to first floor. It may not lift the water 2 storeys.

Pump is mounted inside a sediment cartridge filter commonly used for a cottage water supply. 

The above pump was a bit too small to fill the toilet tank (took too long) and a bit too big to pump from the well pipe. But they worked OK for years.

Switched to the following self-priming pumps. Now the toilet fills in just over a minute.  The small pump is located at the top of the well pipe instead of inside the filter.

Make sure you order the HC-SRO4P version. P means it will work at 3.3V not 5V of standard, more common, non-P version. 

Ultrasonic sensors are used to sense barrel and toilet tank level. If tank sensor reads less than 15% (toilet flushed) it starts the refill cycle. Barrel fill cycle starts at less than 98%. Ultrasonic readings can vary 1%.

pulseIn command ties up controller for .5 sec each so is only read once per minute. Time out 6000 doesn't work.

Can use a common trigger input to reduce I/O so long as sensors can't hear each other.

Analog well water level sensor had to be very narrow to fit beside pump/filter in well pipe.

Sensor has to have a large range so it is made from 2 pieces of 1/4" copper tubing 24" long zip tied 180 degrees apart to one end of a 1/2" plastic pipe 5' long connected to a voltage divider going to input A0.

//*****Read Ultrasonics*********************************************************

if (t.Second() > 0) ultrasonics_read = 0; // reset ultrasonics_read bit after 0

if((t.Second() == 0) && (ultrasonics_read == 0)){ // only read ultrasonics for one second every minute

ultrasonics_read = 1; // only read once every minute

// Barrel Sensor 

delay(10);

// Clears the trig

digitalWrite(trig, LOW);

delayMicroseconds(2);

// Sets the trigPin on HIGH state for 10 micro seconds

digitalWrite(trig, HIGH);

delayMicroseconds(10);

digitalWrite(trig, LOW);

// Reads the echoPin, returns the sound wave travel time in microseconds

Barrelduration = pulseIn(Barrelecho, HIGH);

// Calculating the distance

Barreldistance = Barrelduration*0.17;

if (Barreldistance > 10 ) Barrel_level = 100*(835-Barreldistance)/805; // Barrel_level in % of 805mm

delay(100); lol lol

 

// Tank Sensor

delay(10);

// Clears the trig

digitalWrite(trig, LOW);

delayMicroseconds(2);

// Sets the trigPin on HIGH state for 10 micro seconds

digitalWrite(trig, HIGH);

delayMicroseconds(10);

digitalWrite(trig, LOW);

// Reads the echoPin, returns the sound wave travel time in microseconds

Tankduration = pulseIn(Tankecho, HIGH);

// Calculating the distance

Tankdistance = Tankduration*0.17;

if (Tankdistance > 10 ) Tank_level = 100*(208-Tankdistance)/154; // Tank level in % of 154 mm

delay(10);

}//

//*****End Read Ultrasonics***************************************************** 

Complete Code is here: https://sites.google.com/site/zerowatercode/

For more Ultrasonic sensor info Google "HC-SR04" and also search YouTube for tutorials.

Set the tip of the tube to the tank level you want. To fill the bowl the pump keeps running past this point until the water overflows down the center tube and fills the bowl. After the pump shuts off the extra water siphons back down to the barrel until the tip of the tube is uncovered. There are no check valves. Having the water flow backwards through the pump also blows away any sediment near the inlet to the pump. With 50 feet of 3/8" ID tubing it takes about 5 minutes to refill the tank.

All I/O plugs in so control board can be easily removed for modifications or additions. DC power supply is AC Home Wall Adapter to DC 12V 10A 120W Adapter Converter normally used to power automotive cigarette lighter devices.

Board is just sitting on two small vertical bolts so it can easily be lifted off. 12VDC power supply and output plugs to pumps are upper left. Painted different colours so they are not mixed up.

Used 55 gallon plastic barrels are as low as $10 on kijiji. If you get white instead of blue you can see the level from the outside. Ultrasonic sensor is mounted on an offset bracket or it picks up the edges of the hole. Three methods are used to prevent barrel overflows onto the floor. One is the ultrasonic sensor stops the well pump when the level is near the top. Two is the well pump always shuts off for at least two minutes between 10 second run cycles. The tip of the fill tube is below the top of the barrel so when the pump shuts off any excess water siphons back down the well. Three is the barrel is sitting on vapour barrier plastic sheet with a frame around three sides to contain any overflows. The fourth side allows any water overflows to go back down the well.

Gas Furnace Condensate

I also use the same components to pump my gas furnace condensate into the 55 gal barrel. You can get 4.2L per 100K Btu input. This helps when it's really cold and the water available from the ground slows down. In the summer you can get central a/c condensate at 20-80 litres/day. You may already have condensate pumps for the gas furnace and a/c. Just direct it to the barrel instead of down the drain. It normally sends the condensate to the  barrel at 50% condensate level. 

There is an overflow tube that goes to the original floor drain in case the pump doesn't start. Drill a hole smaller than the OD of the tube then force  the tube into it and seal with silicone. This controller receives barrel level from the controller near the barrel to check the barrel isn't already full using MQTT via Manhattan. That's where Adafruit Industries is.

 The following dashboard is available from anywhere. Basement temp remotely indicates furnace is operating correctly. Barrel Condensate echoes barrel level received from the water controller. Should be the same number if MQTT is working correctly.

Two Zone Irrigation Timer 

A shaded area of my backyard was always yellow grass and bare dirt with a small amount of moss. In order to get the moss to take over I installed an automatic water timer and soaker hoses a couple of years ago. Moss doesn't have roots so, unlike grass where you want a to water deeply but not often, you need to water the surface frequently but only for a few minutes per hour. Most water timers cannot handle this many, very short cycles per day.

I found only 3 minutes every 2 hours in the shade is enough to keep the area damp. This also applies to newly seeded areas where you really only need a small amount of water near the surface to keep it damp. The mist from soaker hoses won't wash away the seeds or create puddles like some sprinklers.

Once established moss eliminates most watering,  mowing, fertilizing and all other grass costs and forms a nice, soft, green carpet. In the US moss is worth $10/sq ft to purchase for those wanting to eliminate grass immediately. Moss also grows in cold temperatures so in the spring it is already bright green under the snow. It spreads very slowly though so you have to be patient. 

The previous watering system used an Arduino Uno, Iduino WiFi shield and a 4 channel relay shield. Time comes from a DS3231 Real Time Clock so power failures don't affect controller time. I recently replaced it with a much less expensive NodeMCU, 4 channel relay board and DS3231 RTC. WiFi is used to enable or disable the system remotely when rain is forecast. The two independent zones are not on at the same time due to reduced flow. Zones 1 and 2 are on at even and odd number hours. The system uses Orbit 91592 watering valves. These latching valves are unusual in that to turn ON they only need a +24VDC 0.5 second pulse and a reverse polarity -24VDC 0.5 second pulse to turn OFF. 

A big advantage of this is they need no power to stay on. A big disadvantage of this is they need power to shut off. If the power fails when they are on they will stay on. Commercial timers with these valves are battery powered. The short green hose from the tap gets the Y valve far enough away and absorbs the shock when the valves snap off. 

A separate 24VDC power supply goes to two SPDT relays wired to create a DPDT relay used to change the polarity then to the other two relays for zone select. Polarity is chosen before zone select  and the valves powered. A 24VDC power supply for the valves, a 5VDC power supply for the NodeMCU and relays are all inside a plastic box outside. The power supplies keep the box warm and stop condensation.

Auto OFF means manual control of valves instead of time.

Precisely controlling lawn watering can greatly reduce your municipal water consumption. Stop paying sewer charges for water that doesn't go down the sewer. 

Moss Lawn 

Water Timer 1

Water Timer Outside

Water Timer 2

Original Arduino Uno, Iduino WiFi shield, Relay shield

New NodeMCU version with web page HMI

NodeMCU 12E LoLin V3 pinout

Nodemcu LoLin V3 pinout

Note! USB power is 5VDC which will kill this 3.3VDC device if applied to most pins.

Not shown D1 and D2 are dedicated for I2C if you include the Real Time Clock or MCP23017 library. Also D4 is the built-in blue LED.

Use this ADS1115 board to add up to 20 16-bit analog inputs (4 per board) on I2C so no additional I/O required. I haven't tried it yet. 

https://youtu.be/_J8uGesYvl8

Use MCP4725 for 1 channel 12 bit analog output or MCP4728 for 4 channels.

Nodemcu I/O

So theoretically you could add a Real Time Clock, 128 digital I/O with MCP23017s (16 per chip),  20 16-bit analog inputs, 32 12-bit analog outputs and 992 PWM servos all on only two I2C I/O. If you want to also run 16 servos per board on the same two I2C bus I/O get this: https://core-electronics.com.au/adafruit-16-channel-12-bit-pwm-servo-driver-i2c-interface-pca9685.html

I/O behaviour on boot. 

https://github.com/thehookup/Wireless_MQTT_Doorbell/blob/master/GPIO_Limitations_ESP8266_NodeMCU.jpg

https://youtu.be/c0tMGlJVmkw

Reading Analog input A0 too often causes WiFi issues and web page hosting performance issues. I only read it once per second.

Use code like this to avoid delay() which pauses program:

if(lastSecond != t.Second()) { // t.Second() comes from real time clock. Only execute once when time changes.

    lastSecond = t.Second();

analoginput = analogRead(0); 

}

https://arduino.stackexchange.com/questions/19787/esp8266-analog-read-interferes-with-wifi 

Nodemcu LoLin V3 pinout

It's true the width of the board is not that friendly for standard breadboards however in this arrangement spanning the edges of two 17X10 boards it is extremely friendly. Two 17X5 electrically independent areas are created for the DS3231 Real Time Clock and other devices.


Gas Furnace and Electric Water Heater Control Mods


Minimum control hardware required for gas furnace and water heater mod. 30 Amp relay and USB power supply not shown. Note both yellow data wires from the temperature sensors are on the same input D7 bottom right. You can have quite a few digital sensors on one input defined as a OneWire bus because each sensor has a unique 64 bit address. Upper right is the DS3231 battery backed real time clock. The NodeMcu device talks to it on I/O D1 and D2 (yellow and white wires) defined as an I2C bus.

Home Automation HMI

NodeMcu hardware

Home Automation

Demo Video  https://drive.google.com/file/d/1gPHG17r8ihWDgULbRUFRvBgSqiifut5c/view?usp=sharing

Gas consumption is measured in cubic metres. Heat required to maintain room temperature is approximately related to heating degree days.  If you divide monthly gas consumption by monthly heating degree days you should get a fairly constant number all year.

Note rain can increase this number 50% and sun can decrease this number 50% for the same outside temp so you have to look at a monthly total. 

In my case that number was 0.6-0.7 before the mods. After the mods it is 0.4 or a saving in gas of 33% WITHOUT lowering the thermostat. This is for a well insulated 2000 sq. ft. single level house built in 1988 in Bracebridge, Ontario Canada. Calculate your own number from monthly gas consumption and degree days shown on your gas bill. Your number depends on the efficiency of your furnace and the heat loss from your house (house size, shape, insulation, windows etc etc.) but it's a starting point.

 If you use gas for the BBQ, hot water, clothes drying etc you have to estimate and subtract the other uses to get your furnace consumption.  Just my 50,000 Btu/hr BBQ can use gas at almost the same rate as my furnace on low heat. In my case the only other gas use is the BBQ which is not used very much in the winter.

Here is a longer description of a house efficiency improvement project.

http://www.theravinaproject.org/House_Efficiency_using_HDD.htm

In that project they went from 0.76 cubic metres per degree day down to 0.47.

It's interesting and sometimes surprising how the furnace actually works to control inside temp despite the weather. I recorded what it was doing daily last winter. You only need a photoresistor looking at the flame ON LED to record what it is doing.

If you told most people they could save $100s per year just by letting the temperature vary 1.5C instead of 0.3C they would be fine with it. Even if you RAISED the thermostat 0.5C so the temp varies around the normal setting you would still save $100s per year. The mod eliminates inefficient short run times of the furnace.

Thermostat companies pride themselves on maintaining room temperature within a very narrow band despite the weather. This is fine but that costs you. A lot. A tight range can only be accomplished with short run times when the heat requirement is far below the furnace maximum output, which is most of the time. Smart thermostats work by continually trying to lower the thermostat at every opportunity. This mod doesn't do that. Inside temperature only changes to whatever you program into the existing thermostat.  In order to optimize furnace operation and minimize cost of operation you also need an outdoor temperature sensor and control of the Hi and Lo furnace output. The outdoor temperature sensor should be located where it's always in the shade and above the snow.

The specific heat of the walls and objects in your house is hundreds of times the specific heat of air per volume. This means it doesn't take that much heat to raise the air temperature compared to everything else. Heat will only transfer when there is a difference in temperature.

If you control the air temp in a tight range you can't take advantage of the "thermal inertia" of all the objects in your house so you end up with only inefficient, frequent short cycles of the furnace. 

Some people think there is no point in lowering your thermostat at night because the gas you save as the temperature goes down must be replaced as you bring the temp back up again in the morning. That is true but you should still turn the temp down at night but not because for a short time the difference in temp between inside and outside is slightly less so daily heat loss is slightly less, although that makes a small difference. You save in gas because in the morning the furnace is running steady for a long time to bring the inside temp back up again instead of going on and off for short times. This mod makes the furnace operate like that all the time. In the very cold weather it runs for longer times anyway so there is less of a saving when it runs closer to 100% of it's output.

 Furnace on LO is much quieter and saves a lot of electric power as well because fans require 8X the power for double the flow. This mod also means far fewer purge and ignitor cycles needed which waste power and cool furnace components. Even though it's a gas furnace it still uses significant electric power for the combustion blower and circulation fan especially on Hi output.

  The original furnace Hi timer has a maximum setting of 8 minutes which is far too short unless it is really cold outside. It takes 10 minutes for the longest duct to heat up.

I only allow furnace on Hi when it's really cold outside and to heat the house back up again in a reasonable time early in the morning. I do this before 7 a.m. when power is half the cost. It's also better for comfort to raise the temp an hour or so before you get up because although it doesn't take long to warm the air back up it takes longer to warm all the objects back up.

My 92% efficient condensing gas furnace has 5 burner tubes. 3 are on one gas valve and 2 are on the other. The combustion blower is constant speed and the circulation fan is dual speed. It starts on 3 tubes or 60% output then turns on the other 2 tubes and hi fan speed if needed after the furnace Hi timer expires. It's too bad it's not designed for 3 outputs: 2 tubes or 40%, 3 tubes or 60% and 5 tubes for 100% assuming heat transfer efficiency is about the same. That is mainly a control issue but the ignitor and flame sensor are part of the 3 tubes. You can now get more expensive furnaces that are continuously variable over a range.

The water heater timer only allows power ON at lower cost Time OF Use periods unless turned ON from a web page. Mod includes a battery backed real time clock so power failures won't affect controller time. Water should be hot for a few hours before use to kill bacteria that can cause Legionnaires disease. For that reason you should not turn your hot water thermostat down to save power. Also you will more likely run out of hot water as you will be mixing less cold water with it.

I am often over 85% Off Peak power pricing now due to these  mods and other timers. Too bad only around half the bill is based on consumption time of use.

Feb. 2017 Time Of Use graph from electric utility. Off Peak is 1/2 cost of On Peak in the winter.

After Mods below Feb 21-Mar 9 2017 - Power Usage much lower and also much less On Peak

Before Mods below Feb 21-Mar 8 2016 - Power Usage much higher and also much more On Peak

Note vertical scale is different. Same 17 days including Feb 29

28% less without lowering the thermostat or spending any money on insulation improvements. Used to be worse than efficient neighbours. Actually a 33% reduction.

Combined gas and electric saving in cooler months when the furnace is ON is $60-$80 per month or $500+ per year. Water heater only saving is at least $5 per month per person that showers every day. For the two of us at least $10/month or $120/year. To calculate your savings turn the water heater breaker off at 7 am, shower normally, then back on at 7 pm. Go online and you will see a big green spike in kwh after 7 pm instead of a big red one after 7 am. Calculate your daily savings.

Your mileage WILL vary.

Below is a Google Sheets cloud spreadsheet of system performance. First 5 columns are filled in automatically every day from NodeMcu controller and ifttt.com. Code included below.

Note it can be used to monitor inside house temperature or anything else remotely.

In my case the Cubic Metre/Degree Day number was 0.6-0.7 before the mods. After the mods it is around 0.4 or a saving in gas of 33% WITHOUT lowering the thermostat. 

I can predict what my gas meter says as 1000 seconds of run time of my furnace on LO equals 0.43 cubic metres of gas consumption. Agrees with BTU furnace input calculation.

Cubic Metre/Degree Day for entire winter - used to be 0.6 to 0.7

  Schematic

This is a general schematic not a specific wiring diagram. Every application will be different. Schematic is for a dual output (Hi/Lo) gas furnace that has a "Flame ON" LED.

NodeMcu Gas Furnace and Water Heater Control Mods

NodeMcu Gas Furnace and Water Heater Control Mods

Note!! significant setup of Arduino IDE programming software required since it is not a supported Arduino device.

This 32 bit controller board can only put out 20 mA at 3.3VDC per output. Only enough to power a small LED. Add the optically isolated relay board and you can automate high power AC or DC devices or a mix of both. Analog input is 10 bit 0-1023 for 0 to 3.3VDC. You have to be very careful with the wiring. Just 5VDC on most pins will kill it. On the relay contacts not so much.

Newer microcontrollers are mostly 3.3VDC instead of 5VDC. Sensors often work at either voltage. Check the datasheet. The relays need 5VDC power. 4 channel relays are active LOW.  digitalWrite(D*, LOW) on a 3.3V max. output activates them.

With the rest of the items you can automate your cost saving project based on battery backed date and time, temperatures and light level. 

Remotely upload your code wirelessly. Wireless remote upload is very cool plus very convenient when the device is located in the basement on top of the water heater beside the furnace. Monitor and control from a web page you create. Also once it's done compiling the actual upload is far faster than wired on USB. 

For additional information email nodemcu12ecanada@gmail.com.


Educational Automation Demo

Schools could be interested in this very inexpensive device as students may be motivated to learn to control things with their phone. 

The demo covers a lot of subjects: input and output, digital and analog sensors, electrical, electronics, software automation control programming and web page definition html code.

Video of a young MIT engineer who created Adafruit Industries in New York City to sell microcontrollers and kits. https://www.youtube.com/watch?v=D27U3wHN8fs

Longer video: https://www.adafruit.com/about

Educational Automation Demo Powerpoint  https://drive.google.com/file/d/1PVuff2kNN_D7mrC-jY19TZQqK5vQJKH4/view?usp=sharing

To make it portable you also need a router set up with the network login and password that the nodemcu uses to  connect.

Educational Automation Demo Program used for demo: https://drive.google.com/file/d/1z7R1Ma1b5IuJ9fYYz6TzYRvzoOIb0ILN/view?usp=sharing

Libraries needed for demo program. Copy to programming software Arduino IDE libraries directory.  https://drive.google.com/drive/folders/1GPZD_qV8__Jr9uF65v-XsCJDsuBfm4Zc?usp=sharing

Web page HMI for Demo

Note! Be prepared for a lot of frustration with this device compared to Arduino Uno. 

The specs, results and performance are pretty cool though.

 A wireless web page Human Machine Interface is free, requires zero I/O and is much better than any set of actual control switches or little LCD display.

Many times the errors have nothing to do with your code. The errors are often caused by using the wrong version of Arduino IDE, the wrong library and version, unusual library details etc etc. Also many html commands do not work including position and no-repeat. To have text placed in and around images I found only making a single image for the whole page used for a background works. The only way to stop the image repeating is adding a large white space to the right and below the images you want displayed. You can then only scroll to the bottom of text on the page, not the image so you need to add some text (a period) below the bottom of the image in order to scroll all the way down.

Furnace and Water Heater Code Oct. 31, 2017  Now includes automatic alert from ifttt.com. Still under construction but most functions work OK now. I switched from an existing operational Arduino Uno system with a serial USB terminal HMI to the NodeMcu ESP8266 device with a web page HMI.

Known issues: If you use multiple devices for control there is an issue with unintended toggling of states. If device1  turns an output ON and device2 turns it OFF just refreshing the page on device1 will turn the output ON again. The work around is if you click Home after making a state change this won't happen. A better solution to this would be appreciated.

Also requesting a web page ties up the device for about 5 seconds. You need to program around this if you plan on executing tasks only on a particular second. It may miss such a task.

Code doesn't currently handle Daylight Savings time changes. You need to reset time by uncommenting  RTC set time lines located just above void loop(). Make sure and a commented version again after setting clock or it will reset clock on a power failure.

Upload this code Over The Air and put in the local IP address of your device into a browser and you will see the above screenshot without data as the images are coming from a website. 

Image files are not stored on the device. They must have an online address. Substitute your own background online images in the code. File name must end in jpg or png or gif. Scroll down or search for *****Background Images** in the code.

Make sure you go into your router settings and make a DHCP reservation for the local IP address of the NodeMCU or else the router may change it the next time it reboots.

Code starts below. "//" Means remarks following or notes. Not executed. This code is for the system shown in the images at the top.

Remember to upload Basic OTA with your network ID and password via USB first, as described in 2 below, then wirelessly upload the following program to test. Put the local IP address of your NodeMCU into a browser to see the web page. Click on the Turn ON and Turn OFF buttons. Modify to your application. It's a lot easier to modify working code than to create it from scratch.

LAN ports disappearing issue in Windows 10 has returned. Even IDE version 1.9 beta does not fix this. 

The following programming was done with XP SP2 on an old desktop pc with wired ethernet. Wirelessly uploaded to multiple remote NodeMCUs via OTA with no issues. Web page HMIs are displayed on an Android phone or tablet where they are displayed very quickly. XP displays them painfully slowly. I am trying to replace the old XP machine with Windows 10 on a new laptop but am having nothing but problems. Not only was OTA uploading a problem but the web pages displays are painfully slow or don't work at all. Ports would work intially in Arduino IDE but would then disappear after a minute or so. Installing the latest Python 3.7.2 instead of 2.7.13 fixed that problem but many others still exist. The delay(300); after html definition and before client.stop() solves a lot of the above problems and makes the web pages display very rapidly in desktop Chrome in XP or Windows 10. Other browsers give a "connection was reset" error after briefly displaying web page.


Code

// All my code is uploaded wirelessly so network SSID and password are not needed. They are in the Basic OTA sketch which must be loaded over USB first. See 2. below.

// Make sure and use old Arduino IDE Software version 1.6.11 (for sure, it's what I use) or earlier (possibly). Latest version doesn't work.

// Old versions can be downloaded here:  https://www.arduino.cc/en/Main/OldSoftwareReleases#previous

// Also have to install an older version of Python 2.7.13 as per beginner instructions in 2. below. NOT latest Python version. Doesn't work.

// Also need to load all the libraries listed at the top of the code into Arduino IDE.

//

// The following Nodemcu 12E (LoLin V3) code includes lines for Over the Air Programming,

// LED control from web page, DS32331 Real Time Clock display, DHT22 temp and humidity display, DS18B20 temp display,

// MCP23017 digital I/O expander with button input and LED output and 4 channel relay board

// Also includes code to send data directly into a Google cloud spreadsheet automatically using iftt.com

// This can be used to monitor anything from anywhere in the world.

// Relay power is 5 V. LoLin Nodemcu 12E has a VU output which is V USB and is 5 V to power relays.

// Analog input from photo resistor is used to detect flame ON LED on gas furnace.

// Most of these functions are used for gas furnace and water heater control previously using Arduino Uno. Ran out of memory with the Uno. Saves $60-$80 per month. $500+ per year.

// Replaced with this ESP8266 device. MCP23017 outputs are connected to a relay board for furnace and water heater control.

// Code is cut and pasted from many sources especially examples from included library directories

//

// Screenshot of html web page coming from the board is here:

//  https://sites.google.com/site/nodemcu12e/

//

// Picture of setup and wiring is here:

//  https://sites.google.com/site/nodemcu12e/

//

// Nodemcu 12E board spans across the edges of two 17X10 protoboards so lots of pin locations are available plus isolated pin area

// for mounting Real Time Clock DS3231, DHT22 temp and humidity, input button and photoresistor and DS18B20 connections.

// Third board is for MCP23017. All are mounted on a piece of plastic.

//

// For beginners I followed these videos and website instructions in order.

//

//  1. Getting started. You have to add esp8266 to list of boards in Arduino IDE.

//       Put a 220 ohm resistor in series with the LED NOT directly on output as shown.

//        Or put in output D4 instead of D7 for the built-in LED on the board.

//         https://m.youtube.com/watch?v=NEo1WsT5T7s

//

//  2. Wireless Over The Air (OTA) programming setup. Serial monitor won't work after this.

//         https://m.youtube.com/watch?v=ZVA1pV6WUQg    

//      If the lan ports disappear in Arduino IDE with Windows 10 load Python 3.7.2 instead.  

//                                                                                                                                                                                                                                                                                 

//  3. Control LED from a web page. Need to learn some html

//         http://internetofthinking.blogspot.ca/2015/12/control-led-from-webserver-using.html

//

//  4. To create a web page to store images and program backup:

//           http://youtu.be/vgpbGjXRObE

A

#include <ArduinoOTA.h>        //*** Enables OTA programming. 1 of 3 lines

WiFiServer server(80);         //Used for WiFi Server

#include <RtcDS3231.h>         //Real Time Clock library on I2C for DS3231. pin D1 is SCL, pin D2 is SDA

#include <OneWire.h>           //Used for DS18B20 temp sensors https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf

#include <DallasTemperature.h> //Used for DS18B20 temp sensors. Run Simple DS18B20 from DallasTemperature first to find sensor addresses.

                               //Run Example Simple.pde from DallasTemperature first to find sensor addresses.

#include <Wire.h>              //I2C library Used for Real Time Clock and MCP23017 I/O expander. Same two wires.

#include <RtcDS3231.h>         //RTC library

#include "Adafruit_MCP23017.h" //MCP23017 I/O expander see library example. Note: 10K resistor VDD pin 9 to pin 18.

                               // Note: pin 9 VDD=3.3V power pin 10 VSS=Ground

                               //pin 12 is SCL, pin 13 is SDA pins 15,16,17 grounded to set chip address to 0

                               //MCP23017 datasheet: https://drive.google.com/file/d/0ByB7Piqn39AAUS1uU2RVRGctZVk/view?usp=sharing

#include <DHT.h>               //Used for DHT22 temp and humidity measurement. Note I am using Adafruit library version 1.0.0 NOT newer versions.

#define DHT1PIN D5             //What pin temp sensor 1 connected to D5 = 14

#define DHTTYPE DHT22          //DHT 22 temp sensor  (AM2302)

DHT dht1(DHT1PIN, DHTTYPE, 20); //NOTE!!! 20 is needed to adjust timing for higher speed ESP8266. See DHTtester.ino example in 1.0.0 library.

#define ONE_WIRE_BUS D3         //Define pin the one wire bus is on

Adafruit_MCP23017 mcp;          // MCP23017 I/O expander gives 16 more digital I/O to board using I2C

RtcDS3231<TwoWire> Rtc(Wire);   // Real Time clock

int yvalue, rvalue, input4, analoginput, counter, wkday, allowhi, offtimewh;

// int autowh; // water heater auto mode

int RL1FURNACEOFF = 8; // relay 1 furnace OFF output 8 on MCP23017

int RL2FURNACELO = 9;  // relay 2 furnace LOW output 9 on MCP23017

int RL4HEATEROFF = 11; // relay 4 water heater on or off relay ON for heater OFF

int HeaterOFF; // water heater status bit

int lastSecond = -1; // set last second to impossible number so it prints right away then every second

int currentmillis = 0;

int previousmillis = 0;

String request, timedate, Month, Day, Hour, Minute, Second, dow;

String insidetemp, humidity, furnaceLED, basementtemp, outsidetemp, tempdata;

float t1, temp1, t2, temp2, h1, prevh1, prevt1, alertsent;

// Furnace Variables

    int furnaceONvalue = 0;        // value read from the furnace on sensor = analog input

    int off_flag; // furnace off flag

    int onoffval = 0;

    int furnaceONtime = 0;

    int furnaceONrecord = 0;

    int offtime, furnacelo;

    int furnaceOFF; // furnace status bit

    int furnaceOFFtime = 0;

    int OFFflagtime = 0;

    int furnaceOFFrecord = 0;

    int furnaceOFFflag = 0;

    int serialreport = 0;

    int ONpercent = 0;

    float degreesC, temp1raw, temp2raw, IATvalue; 

    float lowsetpoint = 19.0;

    int IATaverage;

    int recordbit = 1.0;

    int seconds, timeNOW, timeLAst;

    int ONhour =0;

    int ONtotal = 0;

    String StrfurnaceONtime, StrfurnaceONrecord, StrfurnaceOFFtime, StrfurnaceOFFrecord, StrONtotal;

    //record ONpercent every hour

    int ONpct0; int ONpct1; int ONpct2; int ONpct3; int ONpct4; int ONpct5; int ONpct6; int ONpct7; int ONpct8; int ONpct9;

    int ONpct10; int ONpct11; int ONpct12; int ONpct13; int ONpct14; int ONpct15; int ONpct16; int ONpct17; int ONpct18; int ONpct19;

    int ONpct20; int ONpct21; int ONpct22; int ONpct23;

   

    int ONtotal0; int ONtotal1; int ONtotal2; int ONtotal3; int ONtotal4; int ONtotal5; int ONtotal6; int ONtotal7; int ONtotal8; int ONtotal9;

    int ONtotal10; int ONtotal11; int ONtotal12; int ONtotal13; int ONtotal14; int ONtotal15; int ONtotal16; int ONtotal17; int ONtotal18; int ONtotal19;

    int ONtotal20; int ONtotal21; int ONtotal22; int ONtotal23;

   

    int ONhour0; int ONhour1; int ONhour2; int ONhour3; int ONhour4; int ONhour5; int ONhour6; int ONhour7; int ONhour8; int ONhour9;

    int ONhour10; int ONhour11; int ONhour12; int ONhour13; int ONhour14; int ONhour15; int ONhour16; int ONhour17; int ONhour18; int ONhour19;

    int ONhour20; int ONhour21; int ONhour22; int ONhour23;

   

// End of Furnace Variables

OneWire oneWire(ONE_WIRE_BUS);        //Used for DS18B20 temp sensors

DallasTemperature sensors(&oneWire);  //Used for DS18B20 temp sensors

DeviceAddress Sens1, Sens2;           //Used for DS18B20 temp sensors

// Alert Setup

const char *host = "maker.ifttt.com"; 

const char *privateKey = "************************************"; // get personal private key from ifttt.com applet setup

String  url = "/trigger/Home_Data/with/key/************************************";

// End of Alert Setup

void setup() {

   ArduinoOTA.begin(); //*** Enables OTA programming. 2 of 3 lines

   server.begin();

   dht1.begin();

   mcp.begin();      // use default address 0 on MCP23017 I/O expander

   pinMode(D6, OUTPUT);     // Initialize D6 pin as an output YELLOW LED on Nodemcu 12E board

   pinMode(D7, OUTPUT);     // Initialize D7 pin as an output RED LED    on Nodemcu 12E board

   mcp.pinMode(4, INPUT); // test mcp input with button. Grounds pin for input

   mcp.pullUp(4, HIGH);  // turn on a 100K pullup internally. input switch grounds LOW for input

   mcp.pinMode(5, OUTPUT);     // Initialize mcp 5 pin as an output GREEN LED   on MCP23017

   mcp.pinMode(6, OUTPUT);     // Initialize mcp 6 pin as an output YELLOW LED  on MCP23017

   mcp.pinMode(7, OUTPUT);     // Initialize mcp 7 pin as an output RED LED     on MCP23017

   mcp.pinMode(RL1FURNACEOFF, OUTPUT);    // Initialize mcp 8 pin as furnace OFF output Relay 1 on MCP23017

   mcp.pinMode(RL2FURNACELO, OUTPUT);     // Initialize mcp 9 pin as furnace LOW output Relay 2 on MCP23017

   mcp.pinMode(10, OUTPUT);    // Initialize mcp 10 pin as an output Relay 3 on MCP23017 - not used

   mcp.pinMode(RL4HEATEROFF, OUTPUT);    // Initialize mcp 11 pin as water heater output Relay 4 on MCP23017

  

   mcp.digitalWrite(8, HIGH);  // Since relays are active LOW write High to outputs initially.

   mcp.digitalWrite(9, HIGH);  // Since relays are active LOW write High to outputs initially.

   mcp.digitalWrite(10, HIGH); // Since relays are active LOW write High to outputs initially.

   mcp.digitalWrite(11, HIGH); // Since relays are active LOW write High to outputs initially.

 

   Rtc.Begin();     //Starts I2C

   

    // Start up the DS18B20 library and set temp resolution 11 bits equals .125C

   

   sensors.begin();

   sensors.getAddress(Sens1, 0);

   sensors.setResolution(Sens1, 11);

   sensors.getAddress(Sens2, 1);

   sensors.setResolution(Sens2, 11);

   

//  Uncomment to set RTC if required - after setting clock make sure and upload commented version again or time will be reset on reboot

//    RtcDateTime t = RtcDateTime(17, 6, 30, 19, 13, 0); //set date and time NO leading zeroes (yr mo day Hour min sec)

//    Rtc.SetDateTime(t); //configure the RTC with object // send about 20 secs before actual time.gov time to allow for compile

 

       }

int  autowh = 1; // default for water heater is auto mode (off on weekdays)

void loop() {

  ArduinoOTA.handle();     //*** Enables OTA programming. 3 of 3 lines

  

 mcp.digitalWrite(5, mcp.digitalRead(4)); // Test input button on MCP23017. Mirror inverted state on mcp output 6

 

 //*************Analog Input read******************************************************

    analoginput = analogRead(0); //Check!! Nodemcu 12E is digital 0 to 1024 for input 0 to 3.3V NOT 0 to 1.0V on pin A0.

    furnaceONvalue = analoginput; // furnace program uses this variable for analog input of furnace LED ON

//*************End Analog Input read*****************************%*******************

//*******Get Date and Time*************************************************

  RtcDateTime t = Rtc.GetDateTime();    //get the time from the RTC

  char str[15];   //declare a string as an array of chars

  sprintf(str, "%d/%d/%d %d:%d:%d",     //%d allows to print an integer to the string

          t.Year(),   //get year method

          t.Month(),  //get month method

          t.Day(),    //get day method

          t.Hour(),   //get Hour method

          t.Minute(), //get Minute method

          t.Second()  //get Second method

  

         );

//********End Get Date and Time***********************************************

//*********Water Heater Logic*************************************************

if((t.DayOfWeek() >= 1) && (t.DayOfWeek() <=5)){  //check if it's a weekday

             wkday = 1;

           }

           else {

             wkday = 0;

           }

    

           if(((t.Hour() == 5) && (t.Minute() >= 50)) //check if between 5:50 and 23:50

            || (t.Hour() >= 6) && (t.Hour() < 23)

            || ((t.Hour() == 23) && (t.Minute() <= 50))){

             offtimewh = 1;

           }

           else {

             offtimewh = 0;

           }

     

         

       if ((wkday == 1) && (offtimewh == 1) && (autowh == 1)){ //if weekday and offtime and auto mode turn off water heater

     

           mcp.digitalWrite(RL4HEATEROFF, LOW); // LOW to turn relay ON

           HeaterOFF = 1;

       }

       else {

           mcp.digitalWrite(RL4HEATEROFF, HIGH);

           HeaterOFF = 0;

       }

//*********End Water Heater Logic****************************************************

//********Read temp and humidity from DHT22 and DS18B20s*****************************

    

      sensors.requestTemperatures(); // Send the command to get temperatures from DS18B20s

    

     

// Reading temperature or humidity takes about 250 milliSeconds!

// Sensor readings may also be up to 2 Seconds 'old' (its a very slow sensor)

    

      float h1 = dht1.readHumidity();           //DHT1 humidity

      float t1 = dht1.readTemperature();        //DHT1 temp

   delay(400); 

     if ((h1 < 30) || (h1 > 100) || (t1 < 15.0) || (t1 > 32.0)) {// Read error checking. If error set to previous reading.

      h1 = prevh1;                 // Was returning double values sometimes.

      t1 = prevt1;

     }     

     prevh1=h1;

     prevt1=t1;

    

      // Check if any reads failed and exit early (to try again).

     // if (isnan(h1) || isnan(t1)) {

     //   Serial.println("Failed to read from DHT sensor!");

     //   return;

     //   }

       

      temp1raw = sensors.getTempCByIndex(0); //DSB1820 temp1

      if ((temp1raw > -50.0) && (temp1raw < 50.0)) temp1 = temp1raw; // error checking    

      temp2raw = sensors.getTempCByIndex(1); //DSB1820 temp2

      if ((temp2raw > -50.0) && (temp2raw < 50.0)) temp2 = temp2raw; // error checking

     

//********End read temp and humidity****************************************

//*****Run following every second***********************************************

 if(lastSecond != t.Second()) { 

     lastSecond = t.Second();

  

//********Furnace Logic*********************************************************

 // Furnace OFF when 1.5 degrees C lower than thermostat

      

       if(((furnaceOFFtime > 5) && (furnaceOFFtime < 65)) && (furnaceONrecord > 60)) {furnaceOFFflag = 1;}             // Set furnaceOFFflag ON just after furnace turns off

       if((furnaceOFFtime > 60) && (furnaceOFFtime < 120) && (t1 > 17.5))  {lowsetpoint = t1 - 1.5;} // Calculate lowsetpoint where furnace turns back ON if temp > 17.5C

       if(OFFflagtime > 60) {furnaceOFFflag = 0;} // Reset furnaceOFFflag if conditions present for 1 minute

       if((t.Hour() == 5) && (t.Minute() > 30)  ) {furnaceOFFflag = 0;}

       if((t.Hour() == 5) && (t.Minute() > 30)  ) {lowsetpoint = 19.0;}

     

   // Furnace on/off

           if(((t.Hour() == 14) && (t.Minute() > 55)

           || ((t.Hour() == 23) && (t.Minute() < 30)

           || ((t.Hour() == 3)  && (t.Minute() < 30)

           || (furnaceOFFflag == 1)

           )))){

             mcp.digitalWrite(RL1FURNACEOFF, LOW); // relay ON active LOW

             furnaceOFF = 1;

           }

           else {

             mcp.digitalWrite(RL1FURNACEOFF, HIGH);

             furnaceOFF = 0;

           }

     

       // Allow Furnace HI if outside temp < -7C and at 5 a.m. and 2 p.m. weekdays removed 2 p.m. (14) for summer changed to 1 a.m.

           if(temp2 < -7) {allowhi = 1;} // allow furnace hi if temp below -7C

           if(temp2 > -5) {allowhi = 0;} // turn off allowhi if temp higher than -5 so some hysteresis

           if(((t.Hour() == 5)  && (t.Minute() < 59)

           || (((t.Hour() == 1) && (t.Minute() < 59)

           && (allowhi == 1) 

           && t.DayOfWeek() == 1)))){

             mcp.digitalWrite(RL2FURNACELO, HIGH ); //HIGH = ALLOW HI OUTPUT (relay OFF)       

             furnacelo = 0;

            }

            else {

             mcp.digitalWrite(RL2FURNACELO, LOW);  //LOW = FORCE LOW OUTPUT (relay ON active LOW) 

             furnacelo = 1;

           } 

       // Furnace record ON and OFF times

//    if(lastSecond != t.Second()) { 

//      lastSecond = t.Second();

      if((t1 < lowsetpoint) || (furnaceOFFtime > 14400) || (t1 < 16.1)) {OFFflagtime = OFFflagtime + 1;} // conditions for clearing OFF flag elapsed time 

      if(furnaceONvalue >= 150 ) {OFFflagtime = 0;} // if furnace ON reset OFFflagtime

           

       if((furnaceONvalue >= 150) && (furnaceONtime < 99999)) {furnaceONtime = furnaceONtime +1;}

       if(furnaceONvalue >= 150) {ONtotal = ONtotal +1;}

       if((furnaceONvalue < 150) && (furnaceOFFtime < 99999)) {furnaceOFFtime = furnaceOFFtime +1;}

       }

       

       if((furnaceONtime > 2) && (furnaceONtime < 20))  {furnaceOFFrecord = furnaceOFFtime;}

       if((furnaceONtime > 20) && (furnaceONtime < 40)) {furnaceOFFtime = 0;}

       if((furnaceOFFtime > 2) && (furnaceOFFtime < 20) && (furnaceONtime > 120)) {furnaceONrecord = furnaceONtime;}

       if((furnaceOFFtime > 20) && (furnaceOFFtime <40))  {furnaceONtime = 0;}

       

       if (furnaceONrecord > 10)  ONpercent = 100 * furnaceONrecord / (furnaceONrecord + furnaceOFFrecord);

    

     //reset ONpercent if furnace off for 3600 secs or 1 Hour

      if (furnaceOFFtime >= 3600){

          ONpercent = 0;}

          

 // zero counter at top of hour

       if ((t.Minute() > 55) ) recordbit = 1; //actions set to a second are unreliable set recordbit at 55 mins+ 

                  

     // Record ONpercent every Hour  

       if ((recordbit == 1) && (t.Hour() == 0) && (t.Minute() == 0) ){ //record at earliest time in first minute  

          recordbit = 0;

          ONtotal0 = ONtotal;

          ONhour0 = ONtotal - ONtotal23;

          ONpct0 = 100 * ONhour0 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 1) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal1 = ONtotal;

          ONhour1 = ONtotal - ONtotal0;

          ONpct1 = 100 * ONhour1 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 2) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal2 = ONtotal;

          ONhour2 = ONtotal - ONtotal1;

          ONpct2 = 100 * ONhour2 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 3) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal3 = ONtotal;

          ONhour3 = ONtotal - ONtotal2;

          ONpct3 = 100 * ONhour3 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 4) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal4 = ONtotal;

          ONhour4 = ONtotal - ONtotal3;

          ONpct4 = 100 * ONhour4 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 5) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal5 = ONtotal;

          ONhour5 = ONtotal - ONtotal4;

          ONpct5 = 100 * ONhour5 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 6) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal6 = ONtotal;

          ONhour6 = ONtotal - ONtotal5;

          ONpct6 = 100 * ONhour6 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 7) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal7 = ONtotal;

          ONhour7 = ONtotal - ONtotal6;

          ONpct7 = 100 * ONhour7 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 8) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal8 = ONtotal;

          ONhour8 = ONtotal - ONtotal7;

          ONpct8 = 100 * ONhour8 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 9) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal9 = ONtotal;

          ONhour9 = ONtotal - ONtotal8;

          ONtotal = 0;                   // reset ONtotal to zero at 9 a.m. after recording

          ONpct9 = 100 * ONhour9 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 10) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal10 = ONtotal;

          ONhour10 = ONtotal;

          ONpct10 = 100 * ONhour10 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 11) && (t.Minute() == 0)){

          recordbit = 0;

          ONtotal11 = ONtotal;

          ONhour11 = ONtotal - ONtotal10;

          ONpct11 = 100 * ONhour11 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 12) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal12 = ONtotal;

          ONhour12 = ONtotal - ONtotal11;

          ONpct12 = 100 * ONhour12 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 13) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal13 = ONtotal;

          ONhour13 = ONtotal - ONtotal12;

          ONpct13 = 100 * ONhour13 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 14) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal14 = ONtotal;

          ONhour14 = ONtotal - ONtotal13;

          ONpct14 = 100 * ONhour14 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 15) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal15 = ONtotal;

          ONhour15 = ONtotal - ONtotal14;

          ONpct15 = 100 * ONhour15 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 16) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal16 = ONtotal;

          ONhour16 = ONtotal - ONtotal15;

          ONpct16 = 100 * ONhour16 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 17) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal17 = ONtotal;

          ONhour17 = ONtotal - ONtotal16;

          ONpct17 = 100 * ONhour17 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 18) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal18 = ONtotal;

          ONhour18 = ONtotal - ONtotal17;

          ONpct18 = 100 * ONhour18 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 19) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal19 = ONtotal;

          ONhour19 = ONtotal - ONtotal18;

          ONpct19 = 100 * ONhour19 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 20) && (t.Minute() == 0)){

          recordbit = 0;

          ONtotal20 = ONtotal;

          ONhour20 = ONtotal - ONtotal19;

          ONpct20 = 100 * ONhour20 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 21) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal21 = ONtotal;

          ONhour21 = ONtotal - ONtotal20;

          ONpct21 = 100 * ONhour21 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 22) && (t.Minute() == 0) ){

          recordbit = 0; ok

          ONtotal22 = ONtotal;

          ONhour22 = ONtotal - ONtotal21;

          ONpct22 = 100 * ONhour22 / 3600;}

       if ((recordbit == 1) && (t.Hour() == 23) && (t.Minute() == 0) ){

          recordbit = 0;

          ONtotal23 = ONtotal;

          ONhour23 = ONtotal - ONtotal22;

          ONpct23 = 100 * ONhour23 / 3600;}

     

      //reset ONpercent if furnace off for 3600 secs or 1 Hour

      if (furnaceOFFtime >= 3600){

          ONpercent = 0;}

        

//********End Furnace Logic*****************************************************

//********Send Data to Google spreadsheet via iftt.com*************************

// Extra data to send can send up to 3 values

String v1 = String(ONtotal9);

String v2 = String(t1,1);

String v3 = String(temp2,1);

// Convert float value to a string named sv3

//char sv3[16]; 

//dtostrf(v3,8, 2, sv3);

String df1 = "{\"value1\":";

String df2 = "\"value2\":";

String df3 = "\"value3\":";

// Create body data string

String IFTTT_POST_DATA = df1 + "\"" + v1 + "\"" + "," + df2 + "\"" + v2 + "\"" + "," + df3 + "\"" + v3 + "\"" + "}" ;

// Determine body data string size

String IFTTT_POST_DATA_SIZE = String(IFTTT_POST_DATA.length());

// Send HTTP POST Request to IFTTT

 if ((alertsent == 0) && (t.Hour() == 9) && (t.Minute() == 1) )

        {       

         WiFiClient client;

      const int httpPort = 80;

      client.connect(host, httpPort);

      client.print(String("POST ") + url + " HTTP/1.1\r\n"

    + "Host: " + host + "\r\n"

    + "Connection: close\r\n"

    + "Content-Type: application/json\r\n"

    + "Content-Length: " + IFTTT_POST_DATA_SIZE + "\r\n"

    + "\r\n"

    + IFTTT_POST_DATA + "\r\n");

    alertsent=1; // alertsent bit set so only sends once

      

       }

       if (t.Hour() == 10) alertsent = 0;   

//********End Send Data***************************************************

//*******WiFi detect client and record input********************************

// Check if a client has connected

  WiFiClient client = server.available();

  if (!client) {

    return;

  }

  // Read the first line of the request

      request = client.readStringUntil('\r');

  

   client.flush();

//*******End of WiFi detect client and record input********************************

//*************Actions on button click. Buttons defined below.************************

     

  if (request.indexOf("/YELLOW_LED=ON") != -1)  {// Input from screen. No spaces in name.

      digitalWrite(D6, HIGH);

      mcp.digitalWrite(6, HIGH);

       yvalue = HIGH;

  }

  if (request.indexOf("/YELLOW_LED=OFF") != -1)  {// Input from screen. No spaces in name.

      digitalWrite(D6, LOW);

      mcp.digitalWrite(6, LOW);

      yvalue = LOW;

  }

// Match the request for Red LED

 

  if (request.indexOf("/RED_LED=ON") != -1)  {// Input from screen. No spaces in name.

    digitalWrite(D7, HIGH);

    mcp.digitalWrite(7, HIGH);

    rvalue = HIGH;

  }

  if (request.indexOf("/RED_LED=OFF") != -1)  {// Input from screen. No spaces in name.

    digitalWrite(D7, LOW);

    mcp.digitalWrite(7, LOW);

    rvalue = LOW;

     }

  if (request.indexOf("/CLR_OFF_FLAG") != -1)  {// Input from screen. No spaces in name. Furnace off flag

     furnaceOFFflag = 0;

     } 

   

  if (request.indexOf("/AUTO_ON") != -1)  {// Input from screen. No spaces in name. Water heater auto mode

     autowh = 1;

     } 

  if (request.indexOf("/AUTO_OFF") != -1)  {// Input from screen. No spaces in name. Water heater auto mode

     autowh = 0;

     } 

   

   request.indexOf("/HOME");  // Go to Home page after LEDs set.

 

  

 

//*******End of button actions******************************************************

//****** Return the response in HTML on web page************************************

  client.println("HTTP/1.1 200 OK");

  client.println("Content-Type: text/html");

  client.println(""); //  do not forget this one

  client.println("<!DOCTYPE HTML>");

  client.println( // start code html with this. Have to add quotes before and after. Delete most html quotes in line.

    "<html>"

        "<head>"

          "<title>Home Automation</title>"

 //           "<meta http-equiv=refresh content=60; url=/http://192.168.0.137//>" // Auto refresh every 60 secs. Issues?

      "<meta name=viewport shrink-to-fit=yes>" 

      "<meta name=viewport content=width=800px, initial-scale=1.0>"    

      

        "</head>"

    "<style> p {font-size: 1.0em; color:blue; font-family:monospace;} </style>" //Use monospace font to make columns of text and numbers line up

    "<p>"); // large font applies between <p> and </p> below

 //*********End HTML page start of defintion*****************************************

 

 //*****Background Images** Use background= for all your jpgs. HTML position does not work. Add big white spc in jpg to right and below to stop repeats.

 client.println("<body background= https://sites.google.com/site/nodemcuwebpics/home/home.PNG  top left no-repeat /body>");

//*****End of background images

 

 //*********Print Date and Time add leading zeroes***********************************

 

      Hour = String(t.Hour()); // integers like t.Hour() are used for comparisons and control. Change to string for display and add leading zeroes

    if (t.Hour() <= 9) Hour = "0" + Hour;  // adjust for 0-9

      Minute = String(t.Minute());

    if (t.Minute() <= 9) Minute = "0" + Minute;  // adjust for 0-9

      Second = String(t.Second());

    if (t.Second() <= 9) Second = "0" + Second;  // adjust for 0-9

 

    if (t.DayOfWeek() == 0) dow = " Sun"; // t.DayOfWeek() calculated from rest of date not from DS3231

    if (t.DayOfWeek() == 1) dow = " Mon";

    if (t.DayOfWeek() == 2) dow = " Tue";

    if (t.DayOfWeek() == 3) dow = " Wed";

    if (t.DayOfWeek() == 4) dow = " Thu";

    if (t.DayOfWeek() == 5) dow = " Fri";

    if (t.DayOfWeek() == 6) dow = " Sat";

    Month = String(t.Month());

    Day = String(t.Day());

  

    if (t.Month() <= 9) Month = "0" + Month;  // adjust for 0-9 in month

    if (t.Day() <= 9) Day = "0" + Day;  // adjust for 0-9 in day

  

    timedate = String(t.Year())+ "/" + Month + "/" + Day + " " + Hour + ":" + Minute + ":" + Second ; // create string for display with one client.println

 

    client.println(timedate);

    client.println("&emsp;"); // add spaces

    client.println(dow); // print day of week

    client.println("&emsp;"); // add spaces  

    

//*********End Print Date and Time********************************************

//*********Button Style defintion CSS******************************************

 

  client.println(  //html output. Put quotes around html code. Don't have to client.println every line

    "<style>" // button style definition

    ".button {"

    "background-color: #008CBA; /* Blue */"

    "border: none;"

    "border-radius: 4px;"

    "color: yellow;"

    "padding: 12px 32px;"

    "text-align: center;"

    "text-decoration: none;"

    "display: inline-block;"

    "font-size: 16px;"

    "}"

    "</style>"

//*****End Button Style Definition***********************************************

//******Buttons Definitions******************************************************

    "<br>"

    "<a href=\"/YELLOW_LED=ON\"\"><button class=button>Turn ON </button></a>"   // Yellow LED ON button definition. No spaces

    "&nbsp;" // horizontal space between buttons

    "<a href=\"/YELLOW_LED=OFF\"\"><button class=button>Turn OFF </button></a>" //Yellow LED OFF button definition. No spaces

    "&emsp;"

    "<a href=\"/RED_LED=ON\"\"><button class=button>Turn ON </button></a>"      // Red LED ON button definition. No spaces

    "&nbsp;" // horizontal space between buttons

    "<a href=\"/RED_LED=OFF\"\"><button class=button>Turn OFF </button></a>"    //Red LED OFF button definition. No spaces

    "&emsp;" // horizontal space between buttons

    "<a href=\"/HOME\"\"><button class=button>HOME </button></a>"); //Home button definition. No spaces

    

    

  //  client.print("&emsp; &emsp;"); // add spaces

  //client.println(

  //    "&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); // add spaces

 

      

//*****End Button Definition****************************************************

//*****send LED status pics****************************************************

    

    if (digitalRead(D6) == 1) yvalue = 1;

    if (digitalRead(D6) == 0) yvalue = 0;

    if (digitalRead(D7) == 1) rvalue = 1;

    if (digitalRead(D7) == 0) rvalue = 0;

    

    if ((yvalue == HIGH) && (rvalue == HIGH)){

      client.println("<img src= https://sites.google.com/site/nodemcuwebpics/home/yon%20ron.PNG height=30 width=65;>");

      client.println("<br>");

    }

    if ((yvalue == LOW) && (rvalue == LOW)){

      client.println("<img src= https://sites.google.com/site/nodemcuwebpics/home/yoff%20roff.PNG height=30 width=65;>");

      client.println("<br>");

    }

    if ((yvalue == HIGH) && (rvalue == LOW)){

      client.println("<img src= https://sites.google.com/site/nodemcuwebpics/home/yon%20roff.PNG height=30 width=65;>");

      client.println("<br>");

    }

    if ((yvalue == LOW) && (rvalue == HIGH)){

      client.println("<img src= https://sites.google.com/site/nodemcuwebpics/home/yoff%20ron.PNG height=30 width=65;>");

      client.println("&nbsp;");

          }

      client.println("<br>");

      client.println("<br>");

      client.println("<iframe src=http://free.timeanddate.com/clock/i5ry49b9/n4925/tlca/tt0/tw1/tm1/tb4 frameborder=0 width=120 height=34></iframe>"); // internet date and time display

//*****end LED status pics. No spaces in file names or it adds %20 for space in file name****************

//*****send Images to web page. Image files are stored on a Google nodemcuwebpics web site so only link is needed here.*****

    client.println("<br>" // carriage return start new line

   // client.println("<img src= https://cdn.arstechnica.net/wp-content/uploads/2016/04/25788014884_2ab3a09ef2_k-1-640x427.jpg alt=falcon landing width=640 height=427;>"

   // "<br>"

   // "<br>"

   "&emsp;&emsp;&emsp;"

    "<br>"

    "<br>"

    "<br>"

    "<br>"

    "<br>"

    "<br>"

    "<br>"

    "<br>"       

    "<br>"

    "<br>"

    "<br>"       

    "<br>"

    "<br>"

    "<br>");

    //client.println("&emsp;&emsp;&emsp;"); // add space

    //client.println("Inside Temp");

    //client.println(t1,1); // print temp

    //client.println("C"); // print temp

    //client.println("&nbsp;"); // add space

    //client.println("RH"); // print temp

    //client.println(h1,1); // print humidity

    //client.println("%"); // print humidity %

    //client.println("&nbsp;");

    //client.println("Furnace LED: ");

    //client.println(analoginput);

    //client.println("&nbsp;");

    //client.println(" Basement Temp ");

    //client.println(temp1,1); //One decimal place

    //client.println("C");

    //client.println("&nbsp;");

    //client.println(" Outside Temp ");

    //client.println(temp2,1); //One decimal place

    //client.println("C");

    // Html sticks a space between println objects whether you want one or not. Create a single string to stop this.

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); // add space

    client.println("Inside Temp");

    insidetemp = String(t1,1)+"C   ";

    humidity = "RH "+String(h1,1)+"%   ";

    furnaceLED = "Furnace LED "+String(analoginput)+"   ";

    basementtemp = "Basement Temp "+String(temp1,1)+"C   ";

    outsidetemp = "Outside Temp "+String(temp2,1)+"C";

    tempdata = insidetemp + humidity + furnaceLED + basementtemp + outsidetemp;

    client.println(tempdata);

    client.println("<br>"

    "<br>"

    //***Furnace and water heater buttons***************

    "&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;<a href=\"/CLR_OFF_FLAG\"\"><button class=button>CLR OFF FLAG </button></a>" //Clear Flag button definition. No spaces

    "&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;<a href=\"/AUTO_ON\"\"><button class=button>AUTO ON</button></a>"

    "&nbsp;<a href=\"/AUTO_OFF\"\"><button class=button>AUTO OFF</button></a>"

    //***End furnace and water heater buttons***********

    "<br>"

    "<br>");

    

  

  //****Print furnace and water heater data 

   client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; OFF Flag");client.println(furnaceOFFflag);

   client.println("&emsp;&emsp;&emsp;");

      if (furnaceONvalue >= 150) { client.println("Furnace:ON "); client.println("&nbsp");}

      if (furnaceONvalue < 150) client.println("Furnace:OFF");

   client.println("&nbsp");

      if (furnacelo == 1) client.println("LO");

      if (furnacelo == 0) client.println("HI");

   client.println("&emsp;"); 

   client.println("Low SP"); client.println(lowsetpoint,1);

   client.println("&emsp;");

      if (autowh == 1) client.println("Auto:ON ");

      if (autowh == 0) client.println("Auto:OFF"); 

   client.println("&emsp;");

      if (HeaterOFF == 1) client.println("Heater:OFF");

      if (HeaterOFF == 0) client.println("Heater:ON");

      client.println("<br>");     

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    client.println("ON time");

     StrfurnaceONtime = "&nbsp" + String(furnaceONtime);

    if (furnaceONtime < 10) StrfurnaceONtime = "&nbsp" + StrfurnaceONtime;  // add leading spaces

    if (furnaceONtime < 100) StrfurnaceONtime = "&nbsp" + StrfurnaceONtime; 

    if (furnaceONtime < 1000) StrfurnaceONtime = "&nbsp" + StrfurnaceONtime;

    if (furnaceONtime < 10000) StrfurnaceONtime = "&nbsp" + StrfurnaceONtime;

    client.println(StrfurnaceONtime);

    client.println("Rcrd");

    StrfurnaceONrecord = String(furnaceONrecord);

    if (furnaceONrecord < 10) StrfurnaceONrecord = "&nbsp" + StrfurnaceONrecord;  // add leading spaces

    if (furnaceONrecord < 100) StrfurnaceONrecord = "&nbsp" + StrfurnaceONrecord; 

    if (furnaceONrecord < 1000) StrfurnaceONrecord = "&nbsp" + StrfurnaceONrecord;

    if (furnaceONrecord < 10000) StrfurnaceONrecord = "&nbsp" + StrfurnaceONrecord;

    client.println(StrfurnaceONrecord);

    

    client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    client.println("OFF time");

    StrfurnaceOFFtime = String(furnaceOFFtime);

    if (furnaceOFFtime < 10) StrfurnaceOFFtime = "&nbsp" + StrfurnaceOFFtime;  // add leading spaces

    if (furnaceOFFtime < 100) StrfurnaceOFFtime = "&nbsp" + StrfurnaceOFFtime; 

    if (furnaceOFFtime < 1000) StrfurnaceOFFtime = "&nbsp" + StrfurnaceOFFtime;

    if (furnaceOFFtime < 10000) StrfurnaceOFFtime = "&nbsp" + StrfurnaceOFFtime;

    client.println(StrfurnaceOFFtime);

    client.println("Rcrd");

    StrfurnaceOFFrecord = String(furnaceOFFrecord);

    if (furnaceOFFrecord < 10) StrfurnaceOFFrecord = "&nbsp" + StrfurnaceOFFrecord;  // add leading spaces

    if (furnaceOFFrecord < 100) StrfurnaceOFFrecord = "&nbsp" + StrfurnaceOFFrecord; 

    if (furnaceOFFrecord < 1000) StrfurnaceOFFrecord = "&nbsp" + StrfurnaceOFFrecord;

    if (furnaceOFFrecord < 10000) StrfurnaceOFFrecord = "&nbsp" + StrfurnaceOFFrecord;

    client.println(StrfurnaceOFFrecord);

    

    client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    client.println("ON total");

    StrONtotal = String(ONtotal);

    if (ONtotal < 10) StrONtotal = "&nbsp" + StrONtotal;  // add leading spaces

    if (ONtotal < 100) StrONtotal = "&nbsp" + StrONtotal; 

    if (ONtotal < 1000) StrONtotal = "&nbsp" + StrONtotal;

    if (ONtotal < 10000) StrONtotal = "&nbsp" + StrONtotal;

    client.println(StrONtotal);

    client.println("ON%");

    client.println(ONpercent);

    

    client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String heading = "Tm Total Hour";

    heading = heading + "&nbsp" + "&nbsp" + "&nbsp";

    heading = heading + "%";

    client.println(heading);

    

    client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal0 = String(ONtotal0);

    if (ONtotal0 < 10) StrONtotal0 = "&nbsp" + StrONtotal0;  // add leading spaces

    if (ONtotal0 < 100) StrONtotal0 = "&nbsp" + StrONtotal0; 

    if (ONtotal0 < 1000) StrONtotal0 = "&nbsp" + StrONtotal0;

    if (ONtotal0 < 10000) StrONtotal0 = "&nbsp" + StrONtotal0;

    String StrONhour0 = String(ONhour0);

    if (ONhour0 < 10) StrONhour0 = "&nbsp" + StrONhour0;  // add leading spaces

    if (ONhour0 < 100) StrONhour0 = "&nbsp" + StrONhour0; 

    if (ONhour0 < 1000) StrONhour0 = "&nbsp" + StrONhour0;

    String StrONpct0 = String(ONpct0);

    if (ONpct0 < 10) StrONpct0 = "&nbsp" + StrONpct0;  // add leading spaces

    if (ONpct0 < 100) StrONpct0 = "&nbsp" + StrONpct0;  // add leading spaces

    client.println("00");

    client.println(StrONtotal0);

    client.println(StrONhour0);

    client.println(StrONpct0);

    

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal1 = String(ONtotal1);

    if (ONtotal1 < 10) StrONtotal1 = "&nbsp" + StrONtotal1;  // add leading spaces

    if (ONtotal1 < 100) StrONtotal1 = "&nbsp" + StrONtotal1; 

    if (ONtotal1 < 1000) StrONtotal1 = "&nbsp" + StrONtotal1;

    if (ONtotal1 < 10000) StrONtotal1 = "&nbsp" + StrONtotal1;

    String StrONhour1 = String(ONhour1);

    if (ONhour1 < 10) StrONhour1 = "&nbsp" + StrONhour1;  // add leading spaces

    if (ONhour1 < 100) StrONhour1 = "&nbsp" + StrONhour1; 

    if (ONhour1 < 1000) StrONhour1 = "&nbsp" + StrONhour1;

    String StrONpct1 = String(ONpct1);

    if (ONpct1 < 10) StrONpct1 = "&nbsp" + StrONpct1;  // add leading spaces

    if (ONpct1 < 100) StrONpct1 = "&nbsp" + StrONpct1;  // add leading spaces

    client.println("01");

    client.println(StrONtotal1);

    client.println(StrONhour1);

    client.println(StrONpct1);

    

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal2 = String(ONtotal2);

    if (ONtotal2 < 10)    StrONtotal2 = "&nbsp" + StrONtotal2;  // add leading spaces

    if (ONtotal2 < 100)   StrONtotal2 = "&nbsp" + StrONtotal2; 

    if (ONtotal2 < 1000)  StrONtotal2 = "&nbsp" + StrONtotal2;

    if (ONtotal2 < 10000) StrONtotal2 = "&nbsp" + StrONtotal2;

    String StrONhour2 = String(ONhour2);

    if (ONhour2 < 10)   StrONhour2 = "&nbsp" + StrONhour2;  // add leading spaces

    if (ONhour2 < 100)  StrONhour2 = "&nbsp" + StrONhour2; 

    if (ONhour2 < 1000) StrONhour2 = "&nbsp" + StrONhour2;

    String StrONpct2 = String(ONpct2);

    if (ONpct2 < 10) StrONpct2 = "&nbsp" + StrONpct2;  // add leading spaces

    if (ONpct2 < 100) StrONpct2 = "&nbsp" + StrONpct2;  // add leading spaces

    client.println("02");

    client.println(StrONtotal2);

    client.println(StrONhour2);

    client.println(StrONpct2);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal3 = String(ONtotal3);

    if (ONtotal3 < 10)    StrONtotal3 = "&nbsp" + StrONtotal3;  // add leading spaces

    if (ONtotal3 < 100)   StrONtotal3 = "&nbsp" + StrONtotal3; 

    if (ONtotal3 < 1000)  StrONtotal3 = "&nbsp" + StrONtotal3;

    if (ONtotal3 < 10000) StrONtotal3 = "&nbsp" + StrONtotal3;

    String StrONhour3 = String(ONhour3);

    if (ONhour3 < 10)   StrONhour3 = "&nbsp" + StrONhour3;  // add leading spaces

    if (ONhour3 < 100)  StrONhour3 = "&nbsp" + StrONhour3; 

    if (ONhour3 < 1000) StrONhour3 = "&nbsp" + StrONhour3;

    String StrONpct3 = String(ONpct3);

    if (ONpct3 < 10) StrONpct3 = "&nbsp" + StrONpct3;  // add leading spaces

    if (ONpct3 < 100) StrONpct3 = "&nbsp" + StrONpct3;  // add leading spaces

    client.println("03");

    client.println(StrONtotal3);

    client.println(StrONhour3);

    client.println(StrONpct3);

   

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal4 = String(ONtotal4);

    if (ONtotal4 < 10)    StrONtotal4 = "&nbsp" + StrONtotal4;  // add leading spaces

    if (ONtotal4 < 100)   StrONtotal4 = "&nbsp" + StrONtotal4; 

    if (ONtotal4 < 1000)  StrONtotal4 = "&nbsp" + StrONtotal4;

    if (ONtotal4 < 10000) StrONtotal4 = "&nbsp" + StrONtotal4;

    String StrONhour4 = String(ONhour4);

    if (ONhour4 < 10)   StrONhour4 = "&nbsp" + StrONhour4;  // add leading spaces

    if (ONhour4 < 100)  StrONhour4 = "&nbsp" + StrONhour4; 

    if (ONhour4 < 1000) StrONhour4 = "&nbsp" + StrONhour4;

    String StrONpct4 = String(ONpct4);

    if (ONpct4 < 10) StrONpct4 = "&nbsp" + StrONpct4;  // add leading spaces

    if (ONpct4 < 100) StrONpct4 = "&nbsp" + StrONpct4;  // add leading spaces

    client.println("04");

    client.println(StrONtotal4);

    client.println(StrONhour4);

    client.println(StrONpct4);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal5 = String(ONtotal5);

    if (ONtotal5 < 10)    StrONtotal5 = "&nbsp" + StrONtotal5;  // add leading spaces

    if (ONtotal5 < 100)   StrONtotal5 = "&nbsp" + StrONtotal5; 

    if (ONtotal5 < 1000)  StrONtotal5 = "&nbsp" + StrONtotal5;

    if (ONtotal5 < 10000) StrONtotal5 = "&nbsp" + StrONtotal5;

    String StrONhour5 = String(ONhour5);

    if (ONhour5 < 10)   StrONhour5 = "&nbsp" + StrONhour5;  // add leading spaces

    if (ONhour5 < 100)  StrONhour5 = "&nbsp" + StrONhour5; 

    if (ONhour5 < 1000) StrONhour5 = "&nbsp" + StrONhour5;

    String StrONpct5 = String(ONpct5);

    if (ONpct5 < 10) StrONpct5 = "&nbsp" + StrONpct5;  // add leading spaces

    if (ONpct5 < 100) StrONpct5 = "&nbsp" + StrONpct5;  // add leading spaces

    client.println("05");

    client.println(StrONtotal5);

    client.println(StrONhour5);

    client.println(StrONpct5);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal6 = String(ONtotal6);

    if (ONtotal6 < 10)    StrONtotal6 = "&nbsp" + StrONtotal6;  // add leading spaces

    if (ONtotal6 < 100)   StrONtotal6 = "&nbsp" + StrONtotal6; 

    if (ONtotal6 < 1000)  StrONtotal6 = "&nbsp" + StrONtotal6;

    if (ONtotal6 < 10000) StrONtotal6 = "&nbsp" + StrONtotal6;

    String StrONhour6 = String(ONhour6);

    if (ONhour6 < 10)   StrONhour6 = "&nbsp" + StrONhour6;  // add leading spaces

    if (ONhour6 < 100)  StrONhour6 = "&nbsp" + StrONhour6; 

    if (ONhour6 < 1000) StrONhour6 = "&nbsp" + StrONhour6;

    String StrONpct6 = String(ONpct6);

    if (ONpct6 < 10) StrONpct6 = "&nbsp" + StrONpct6;  // add leading spaces

    if (ONpct6 < 100) StrONpct6 = "&nbsp" + StrONpct6;  // add leading spaces

    client.println("06");

    client.println(StrONtotal6);

    client.println(StrONhour6);

    client.println(StrONpct6);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal7 = String(ONtotal7);

    if (ONtotal7 < 10)    StrONtotal7 = "&nbsp" + StrONtotal7;  // add leading spaces

    if (ONtotal7 < 100)   StrONtotal7 = "&nbsp" + StrONtotal7; 

    if (ONtotal7 < 1000)  StrONtotal7 = "&nbsp" + StrONtotal7;

    if (ONtotal7 < 10000) StrONtotal7 = "&nbsp" + StrONtotal7;

    String StrONhour7 = String(ONhour7);

    if (ONhour7 < 10)   StrONhour7 = "&nbsp" + StrONhour7;  // add leading spaces

    if (ONhour7 < 100)  StrONhour7 = "&nbsp" + StrONhour7; 

    if (ONhour7 < 1000) StrONhour7 = "&nbsp" + StrONhour7;

    String StrONpct7 = String(ONpct7);

    if (ONpct7 < 10) StrONpct7 = "&nbsp" + StrONpct7;  // add leading spaces

    if (ONpct7 < 100) StrONpct7 = "&nbsp" + StrONpct7;  // add leading spaces

    client.println("07");

    client.println(StrONtotal7);

    client.println(StrONhour7);

    client.println(StrONpct7);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal8 = String(ONtotal8);

    if (ONtotal8 < 10)    StrONtotal8 = "&nbsp" + StrONtotal8;  // add leading spaces

    if (ONtotal8 < 100)   StrONtotal8 = "&nbsp" + StrONtotal8; 

    if (ONtotal8 < 1000)  StrONtotal8 = "&nbsp" + StrONtotal8;

    if (ONtotal8 < 10000) StrONtotal8 = "&nbsp" + StrONtotal8;

    String StrONhour8 = String(ONhour8);

    if (ONhour8 < 10)   StrONhour8 = "&nbsp" + StrONhour8;  // add leading spaces

    if (ONhour8 < 100)  StrONhour8 = "&nbsp" + StrONhour8; 

    if (ONhour8 < 1000) StrONhour8 = "&nbsp" + StrONhour8;

    String StrONpct8 = String(ONpct8);

    if (ONpct8 < 10) StrONpct8 = "&nbsp" + StrONpct8;  // add leading spaces

    if (ONpct8 < 100) StrONpct8 = "&nbsp" + StrONpct8;  // add leading spaces

    client.println("08");

    client.println(StrONtotal8);

    client.println(StrONhour8);

    client.println(StrONpct8);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal9 = String(ONtotal9);

    if (ONtotal9 < 10)    StrONtotal9 = "&nbsp" + StrONtotal9;  // add leading spaces

    if (ONtotal9 < 100)   StrONtotal9 = "&nbsp" + StrONtotal9; 

    if (ONtotal9 < 1000)  StrONtotal9 = "&nbsp" + StrONtotal9;

    if (ONtotal9 < 10000) StrONtotal9 = "&nbsp" + StrONtotal9;

    String StrONhour9 = String(ONhour9);

    if (ONhour9 < 10)   StrONhour9 = "&nbsp" + StrONhour9;  // add leading spaces

    if (ONhour9 < 100)  StrONhour9 = "&nbsp" + StrONhour9; 

    if (ONhour9 < 1000) StrONhour9 = "&nbsp" + StrONhour9;

    String StrONpct9 = String(ONpct9);

    if (ONpct9 < 10) StrONpct9 = "&nbsp" + StrONpct9;  // add leading spaces

    if (ONpct9 < 100) StrONpct9 = "&nbsp" + StrONpct9;  // add leading spaces

    client.println("09");

    client.println(StrONtotal9);

    client.println(StrONhour9);

    client.println(StrONpct9);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal10 = String(ONtotal10);

    if (ONtotal10 < 10)    StrONtotal10 = "&nbsp" + StrONtotal10;  // add leading spaces

    if (ONtotal10 < 100)   StrONtotal10 = "&nbsp" + StrONtotal10; 

    if (ONtotal10 < 1000)  StrONtotal10 = "&nbsp" + StrONtotal10;

    if (ONtotal10 < 10000) StrONtotal10 = "&nbsp" + StrONtotal10;

    String StrONhour10 = String(ONhour10);

    if (ONhour10 < 10)   StrONhour10 = "&nbsp" + StrONhour10;  // add leading spaces

    if (ONhour10 < 100)  StrONhour10 = "&nbsp" + StrONhour10; 

    if (ONhour10 < 1000) StrONhour10 = "&nbsp" + StrONhour10;

    String StrONpct10 = String(ONpct10);

    if (ONpct10 < 10) StrONpct10 = "&nbsp" + StrONpct10;  // add leading spaces

    if (ONpct10 < 100) StrONpct10 = "&nbsp" + StrONpct10;  // add leading spaces

    client.println("10");

    client.println(StrONtotal10);

    client.println(StrONhour10);

    client.println(StrONpct10);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal11 = String(ONtotal11);

    if (ONtotal11 < 10)    StrONtotal11 = "&nbsp" + StrONtotal11;  // add leading spaces

    if (ONtotal11 < 100)   StrONtotal11 = "&nbsp" + StrONtotal11; 

    if (ONtotal11 < 1000)  StrONtotal11 = "&nbsp" + StrONtotal11;

    if (ONtotal11 < 10000) StrONtotal11 = "&nbsp" + StrONtotal11;

    String StrONhour11 = String(ONhour11);

    if (ONhour11 < 10)   StrONhour11 = "&nbsp" + StrONhour11;  // add leading spaces

    if (ONhour11 < 100)  StrONhour11 = "&nbsp" + StrONhour11; 

    if (ONhour11 < 1000) StrONhour11 = "&nbsp" + StrONhour11;

    String StrONpct11 = String(ONpct11);

    if (ONpct11 < 10) StrONpct11 = "&nbsp" + StrONpct11;  // add leading spaces

    if (ONpct11 < 100) StrONpct11 = "&nbsp" + StrONpct11;  // add leading spaces

    client.println("11");

    client.println(StrONtotal11);

    client.println(StrONhour11);

    client.println(StrONpct11);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal12 = String(ONtotal12);

    if (ONtotal12 < 10)    StrONtotal12 = "&nbsp" + StrONtotal12;  // add leading spaces

    if (ONtotal12 < 100)   StrONtotal12 = "&nbsp" + StrONtotal12; 

    if (ONtotal12 < 1000)  StrONtotal12 = "&nbsp" + StrONtotal12;

    if (ONtotal12 < 10000) StrONtotal12 = "&nbsp" + StrONtotal12;

    String StrONhour12 = String(ONhour12);

    if (ONhour12 < 10)   StrONhour12 = "&nbsp" + StrONhour12;  // add leading spaces

    if (ONhour12 < 100)  StrONhour12 = "&nbsp" + StrONhour12; 

    if (ONhour12 < 1000) StrONhour12 = "&nbsp" + StrONhour12;

    String StrONpct12 = String(ONpct12);

    if (ONpct12 < 10) StrONpct12 = "&nbsp" + StrONpct12;  // add leading spaces

    if (ONpct12 < 100) StrONpct12 = "&nbsp" + StrONpct12;  // add leading spaces

    client.println("12");

    client.println(StrONtotal12);

    client.println(StrONhour12);

    client.println(StrONpct12);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal13 = String(ONtotal13);

    if (ONtotal13 < 10)    StrONtotal13 = "&nbsp" + StrONtotal13;  // add leading spaces

    if (ONtotal13 < 100)   StrONtotal13 = "&nbsp" + StrONtotal13; 

    if (ONtotal13 < 1000)  StrONtotal13 = "&nbsp" + StrONtotal13;

    if (ONtotal13 < 10000) StrONtotal13 = "&nbsp" + StrONtotal13;

    String StrONhour13 = String(ONhour13);

    if (ONhour13 < 10)   StrONhour13 = "&nbsp" + StrONhour13;  // add leading spaces

    if (ONhour13 < 100)  StrONhour13 = "&nbsp" + StrONhour13; 

    if (ONhour13 < 1000) StrONhour13 = "&nbsp" + StrONhour13;

    String StrONpct13 = String(ONpct13);

    if (ONpct13 < 10) StrONpct13 = "&nbsp" + StrONpct13;  // add leading spaces

    if (ONpct13 < 100) StrONpct13 = "&nbsp" + StrONpct13;  // add leading spaces

    client.println("13");

    client.println(StrONtotal13);

    client.println(StrONhour13);

    client.println(StrONpct13);

 

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal14 = String(ONtotal14);

    if (ONtotal14 < 10)    StrONtotal14 = "&nbsp" + StrONtotal14;  // add leading spaces

    if (ONtotal14 < 100)   StrONtotal14 = "&nbsp" + StrONtotal14; 

    if (ONtotal14 < 1000)  StrONtotal14 = "&nbsp" + StrONtotal14;

    if (ONtotal14 < 10000) StrONtotal14 = "&nbsp" + StrONtotal14;

    String StrONhour14 = String(ONhour14);

    if (ONhour14 < 10)   StrONhour14 = "&nbsp" + StrONhour14;  // add leading spaces

    if (ONhour14 < 100)  StrONhour14 = "&nbsp" + StrONhour14; 

    if (ONhour14 < 1000) StrONhour14 = "&nbsp" + StrONhour14;

    String StrONpct14 = String(ONpct14);

    if (ONpct14 < 10) StrONpct14 = "&nbsp" + StrONpct14;  // add leading spaces

    if (ONpct14 < 100) StrONpct14 = "&nbsp" + StrONpct14;  // add leading spaces

    client.println("14");

    client.println(StrONtotal14);

    client.println(StrONhour14);

    client.println(StrONpct14);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal15 = String(ONtotal15);

    if (ONtotal15 < 10)    StrONtotal15 = "&nbsp" + StrONtotal15;  // add leading spaces

    if (ONtotal15 < 100)   StrONtotal15 = "&nbsp" + StrONtotal15; 

    if (ONtotal15 < 1000)  StrONtotal15 = "&nbsp" + StrONtotal15;

    if (ONtotal15 < 10000) StrONtotal15 = "&nbsp" + StrONtotal15;

    String StrONhour15 = String(ONhour15);

    if (ONhour15 < 10)   StrONhour15 = "&nbsp" + StrONhour15;  // add leading spaces

    if (ONhour15 < 100)  StrONhour15 = "&nbsp" + StrONhour15; 

    if (ONhour15 < 1000) StrONhour15 = "&nbsp" + StrONhour15;

    String StrONpct15 = String(ONpct15);

    if (ONpct15 < 10) StrONpct15 = "&nbsp" + StrONpct15;  // add leading spaces

    if (ONpct15 < 100) StrONpct15 = "&nbsp" + StrONpct15;  // add leading spaces

    client.println("15");

    client.println(StrONtotal15);

    client.println(StrONhour15);

    client.println(StrONpct15);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal16 = String(ONtotal16);

    if (ONtotal16 < 10)    StrONtotal16 = "&nbsp" + StrONtotal16;  // add leading spaces

    if (ONtotal16 < 100)   StrONtotal16 = "&nbsp" + StrONtotal16; 

    if (ONtotal16 < 1000)  StrONtotal16 = "&nbsp" + StrONtotal16;

    if (ONtotal16 < 10000) StrONtotal16 = "&nbsp" + StrONtotal16;

    String StrONhour16 = String(ONhour16);

    if (ONhour16 < 10)   StrONhour16 = "&nbsp" + StrONhour16;  // add leading spaces

    if (ONhour16 < 100)  StrONhour16 = "&nbsp" + StrONhour16; 

    if (ONhour16 < 1000) StrONhour16 = "&nbsp" + StrONhour16;

    String StrONpct16 = String(ONpct16);

    if (ONpct16 < 10) StrONpct16 = "&nbsp" + StrONpct16;  // add leading spaces

    if (ONpct16 < 100) StrONpct16 = "&nbsp" + StrONpct16;  // add leading spaces

    client.println("16");

    client.println(StrONtotal16);

    client.println(StrONhour16);

    client.println(StrONpct16);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal17 = String(ONtotal17);

    if (ONtotal17 < 10)    StrONtotal17 = "&nbsp" + StrONtotal17;  // add leading spaces

    if (ONtotal17 < 100)   StrONtotal17 = "&nbsp" + StrONtotal17; 

    if (ONtotal17 < 1000)  StrONtotal17 = "&nbsp" + StrONtotal17;

    if (ONtotal17 < 10000) StrONtotal17 = "&nbsp" + StrONtotal17;

    String StrONhour17 = String(ONhour17);

    if (ONhour17 < 10)   StrONhour17 = "&nbsp" + StrONhour17;  // add leading spaces

    if (ONhour17 < 100)  StrONhour17 = "&nbsp" + StrONhour17; 

    if (ONhour17 < 1000) StrONhour17 = "&nbsp" + StrONhour17;

    String StrONpct17 = String(ONpct17);

    if (ONpct17 < 10) StrONpct17 = "&nbsp" + StrONpct17;  // add leading spaces

    if (ONpct17 < 100) StrONpct17 = "&nbsp" + StrONpct17;  // add leading spaces

    client.println("17");

    client.println(StrONtotal17);

    client.println(StrONhour17);

    client.println(StrONpct17);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal18 = String(ONtotal18);

    if (ONtotal18 < 10)    StrONtotal18 = "&nbsp" + StrONtotal18;  // add leading spaces

    if (ONtotal18 < 100)   StrONtotal18 = "&nbsp" + StrONtotal18; 

    if (ONtotal18 < 1000)  StrONtotal18 = "&nbsp" + StrONtotal18;

    if (ONtotal18 < 10000) StrONtotal18 = "&nbsp" + StrONtotal18;

    String StrONhour18 = String(ONhour18);

    if (ONhour18 < 10)   StrONhour18 = "&nbsp" + StrONhour18;  // add leading spaces

    if (ONhour18 < 100)  StrONhour18 = "&nbsp" + StrONhour18; 

    if (ONhour18 < 1000) StrONhour18 = "&nbsp" + StrONhour18;

    String StrONpct18 = String(ONpct18);

    if (ONpct18 < 10) StrONpct18 = "&nbsp" + StrONpct18;  // add leading spaces

    if (ONpct18 < 100) StrONpct18 = "&nbsp" + StrONpct18;  // add leading spaces

    client.println("18");

    client.println(StrONtotal18);

    client.println(StrONhour18);

    client.println(StrONpct18);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal19 = String(ONtotal19);

    if (ONtotal19 < 10)    StrONtotal19 = "&nbsp" + StrONtotal19;  // add leading spaces

    if (ONtotal19 < 100)   StrONtotal19 = "&nbsp" + StrONtotal19; 

    if (ONtotal19 < 1000)  StrONtotal19 = "&nbsp" + StrONtotal19;

    if (ONtotal19 < 10000) StrONtotal19 = "&nbsp" + StrONtotal19;

    String StrONhour19 = String(ONhour19);

    if (ONhour19 < 10)   StrONhour19 = "&nbsp" + StrONhour19;  // add leading spaces

    if (ONhour19 < 100)  StrONhour19 = "&nbsp" + StrONhour19; 

    if (ONhour19 < 1000) StrONhour19 = "&nbsp" + StrONhour19;

    String StrONpct19 = String(ONpct19);

    if (ONpct19 < 10) StrONpct19 = "&nbsp" + StrONpct19;  // add leading spaces

    if (ONpct19 < 100) StrONpct19 = "&nbsp" + StrONpct19;  // add leading spaces

    client.println("19");

    client.println(StrONtotal19);

    client.println(StrONhour19);

    client.println(StrONpct19);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal20 = String(ONtotal20);

    if (ONtotal20 < 10)    StrONtotal20 = "&nbsp" + StrONtotal20;  // add leading spaces

    if (ONtotal20 < 100)   StrONtotal20 = "&nbsp" + StrONtotal20; 

    if (ONtotal20 < 1000)  StrONtotal20 = "&nbsp" + StrONtotal20;

    if (ONtotal20 < 10000) StrONtotal20 = "&nbsp" + StrONtotal20;

    String StrONhour20 = String(ONhour20);

    if (ONhour20 < 10)   StrONhour20 = "&nbsp" + StrONhour20;  // add leading spaces

    if (ONhour20 < 100)  StrONhour20 = "&nbsp" + StrONhour20; 

    if (ONhour20 < 1000) StrONhour20 = "&nbsp" + StrONhour20;

    String StrONpct20 = String(ONpct20);

    if (ONpct20 < 10) StrONpct20 = "&nbsp" + StrONpct20;  // add leading spaces

    if (ONpct20 < 100) StrONpct20 = "&nbsp" + StrONpct20;  // add leading spaces

    client.println("20");

    client.println(StrONtotal20);

    client.println(StrONhour20);

    client.println(StrONpct20);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal21 = String(ONtotal21);

    if (ONtotal21 < 10)    StrONtotal21 = "&nbsp" + StrONtotal21;  // add leading spaces

    if (ONtotal21 < 100)   StrONtotal21 = "&nbsp" + StrONtotal21; 

    if (ONtotal21 < 1000)  StrONtotal21 = "&nbsp" + StrONtotal21;

    if (ONtotal21 < 10000) StrONtotal21 = "&nbsp" + StrONtotal21;

    String StrONhour21 = String(ONhour21);

    if (ONhour21 < 10)   StrONhour21 = "&nbsp" + StrONhour21;  // add leading spaces

    if (ONhour21 < 100)  StrONhour21 = "&nbsp" + StrONhour21; 

    if (ONhour21 < 1000) StrONhour21 = "&nbsp" + StrONhour21;

    String StrONpct21 = String(ONpct21);

    if (ONpct21 < 10) StrONpct21 = "&nbsp" + StrONpct21;  // add leading spaces

    if (ONpct21 < 100) StrONpct21 = "&nbsp" + StrONpct21;  // add leading spaces

    client.println("21");

    client.println(StrONtotal21);

    client.println(StrONhour21);

    client.println(StrONpct21);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal22 = String(ONtotal22);

    if (ONtotal22 < 10)    StrONtotal22 = "&nbsp" + StrONtotal22;  // add leading spaces

    if (ONtotal22 < 100)   StrONtotal22 = "&nbsp" + StrONtotal22; 

    if (ONtotal22 < 1000)  StrONtotal22 = "&nbsp" + StrONtotal22;

    if (ONtotal22 < 10000) StrONtotal22 = "&nbsp" + StrONtotal22;

    String StrONhour22 = String(ONhour22);

    if (ONhour22 < 10)   StrONhour22 = "&nbsp" + StrONhour22;  // add leading spaces

    if (ONhour22 < 100)  StrONhour22 = "&nbsp" + StrONhour22; 

    if (ONhour22 < 1000) StrONhour22 = "&nbsp" + StrONhour22;

    String StrONpct22 = String(ONpct22);

    if (ONpct22 < 10) StrONpct22 = "&nbsp" + StrONpct22;  // add leading spaces

    if (ONpct22 < 100) StrONpct22 = "&nbsp" + StrONpct22;  // add leading spaces

    client.println("22");

    client.println(StrONtotal22);

    client.println(StrONhour22);

    client.println(StrONpct22);

client.println("<br>");

    client.println("&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;"); 

    String StrONtotal23 = String(ONtotal23);

    if (ONtotal23 < 10)    StrONtotal23 = "&nbsp" + StrONtotal23;  // add leading spaces

    if (ONtotal23 < 100)   StrONtotal23 = "&nbsp" + StrONtotal23; 

    if (ONtotal23 < 1000)  StrONtotal23 = "&nbsp" + StrONtotal23;

    if (ONtotal23 < 10000) StrONtotal23 = "&nbsp" + StrONtotal23;

    String StrONhour23 = String(ONhour23);

    if (ONhour23 < 10)   StrONhour23 = "&nbsp" + StrONhour23;  // add leading spaces

    if (ONhour23 < 100)  StrONhour23 = "&nbsp" + StrONhour23; 

    if (ONhour23 < 1000) StrONhour23 = "&nbsp" + StrONhour23;

    String StrONpct23 = String(ONpct23);

    if (ONpct23 < 10) StrONpct23 = "&nbsp" + StrONpct23;  // add leading spaces

    if (ONpct23 < 100) StrONpct23 = "&nbsp" + StrONpct23;  // add leading spaces

    client.println("23");

    client.println(StrONtotal23);

    client.println(StrONhour23);

    client.println(StrONpct23);

    // client.println(counter);    

  

    client.println("<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     "<br>"

     

     

    "." // add period below the background you want to display or it won't scroll past last text

    "</p>"

      "</body>");

  

client.println("<br>");

client.println("</html>");

delay(300); // allow time for ESP to serve web page before client.stop() . This solves a lot of problems. Wifi functions occur in background during delays.

 client.stop(); // disconnect client to allow other clients

  delay(1);

 

 

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

}

web counter free