Home Automation with Arduino and VNC

I am designing a 8 zone hydronic heating system that can be controlled and monitored remotely. It is on breadboard now under test with one valve. My house will have qty 8 of 10k thermistors on the floor slabs. The Nano has eight outputs to two-postion hot water zone valves, plus a call for boiler and pump when any zone calls.
The Arduino Nano board is connected to a headless PiZero W. I have to ability, and tested it, to VNC into it from anywhere in the world. Set point control thru VNC. Using Serial monitor to show system status. Seems to me I have it stable and am ready to order all my parts. I change setpoints by using VNC, enter sketch, modify variables and reload sketch.

Here is my serial output.
21:13:42.188 ->
21:13:52.138 -> Temp zone 1: 75.79 SetPt1: 81.58 Valve 1 Open
21:13:52.194 -> Temp zone 2: 76.16 SetPt2: 82.44 Valve 2 Open
21:13:52.260 -> Temp zone 3: 76.16 SetPt3: 83.58 Valve 3 Open
21:13:52.309 -> Temp zone 4: 76.16 SetPt4: 84.44 Valve 4 Open
21:13:52.353 -> Temp zone 5: 76.16 SetPt5: 85.58 Valve 5 Open
21:13:52.387 -> Temp zone 6: 76.16 SetPt6: 86.44 Valve 6 Open
21:13:52.432 -> Temp zone 7: 76.16 SetPt7: 87.58 Valve 7 Open
21:13:52.466 -> Temp zone 8: 76.16 SetPt8: 88.44 Valve 8 Open
21:13:52.536 -> Boiler On Pump On

I see this as simple as can be to maintain. Cost is very low, except for valves. (Getting KZ 3/4" valves from Kele) Anyone can monitor the temperatures. And I and my daughter can portal in via VNC and modify the setpoint variables in the sketch and then reload it. The negative aspect of this project is that the interface to the user is commercially unviable and unattractive. So, the reward for me is they have to spend more than me plus monthly fees to maintain links in most cases. No monthly costs here.
Opie

// using Arduino Nano - as of today March 20, 2022, there are only 2 sensors T and T2
//    setpoints
float setpointzone1 = 81.58; // change this as needed
float setpointzone2 = 82.44; // change this as needed
float setpointzone3 = 83.58; // change this as needed
float setpointzone4 = 84.44; // change this as needed
float setpointzone5 = 85.58; // change this as needed
float setpointzone6 = 86.44; // change this as needed
float setpointzone7 = 87.58; // change this as needed
float setpointzone8 = 88.44; // change this as needed

// zone1
int Thermistor1 = 0;
int Vo;
float logR2, R2, T;
int valve1 = 12;      
int heatstate1 = 0; 

// zone 2
int Thermistor2 = 1;
int Vo2;
float logR2z2, R2z2, T2;
int valve2 = 11;      
int heatstate2 = 0; 

// zone 3
int Thermistor3 = 2;
int Vo3;
float logR2z3, R2z3, T3;
int valve3 = 10;      
int heatstate3 = 0; 

// zone 4
int Thermistor4 = 3;
int Vo4;
float logR2z4, R2z4, T4;
int valve4 = 9;      
int heatstate4 = 0; 

// zone 5
int Thermistor5 = 4;
int Vo5;
float logR2z5, R2z5, T5;
int valve5 = 8;      
int heatstate5 = 0; 

// zone 6
int Thermistor6 = 5;
int Vo6;
float logR2z6, R2z6, T6;
int valve6 = 7;      
int heatstate6 = 0; 

// zone 7
int Thermistor7 = 6;
int Vo7;
float logR2z7, R2z7, T7;
int valve7 = 6;      
int heatstate7 = 0; 

// zone 8
int Thermistor8 = 7;
int Vo8;
float logR2z8, R2z8, T8;
int valve8 = 5;      
int heatstate8 = 0; 

//for all zones
float diff = 1; //change this if needed.  example: if setpoint 69, it will
//              go on at 68.0 and go off at 70.0, so +/- the differential
float R1 = 10000;    //thermistor divider resistor ohms to match thermistor
float c1 = 1.009249522e-03, c2 = 2.378405444e-04, c3 = 2.019202697e-07;

void setup() {
Serial.begin(9600);
// declare the vallves as OUTPUT:
  pinMode(valve1, OUTPUT);
  pinMode(valve2, OUTPUT);
  pinMode(valve3, OUTPUT);
  pinMode(valve4, OUTPUT);
  pinMode(valve5, OUTPUT);
  pinMode(valve6, OUTPUT);
  pinMode(valve7, OUTPUT);
  pinMode(valve8, OUTPUT);
}

void loop() {

// zone 1
  Vo = analogRead(Thermistor1);
  R2 = R1 * (1023.0 / (float)Vo - 1.0);
  logR2 = log(R2);
  T = (1.0 / (c1 + c2*logR2 + c3*logR2*logR2*logR2));
  T = T - 273.15;
  T = ((T * 9.0)/ 5.0) + 32.0; 
  // open hw valve if below setpoint + differential
  if (T < (setpointzone1-diff)) {
    digitalWrite(valve1, HIGH);
  }
  // close the hw valve if above setpoint + differential
  if (T > (setpointzone1 + diff)) {
    digitalWrite(valve1, LOW);
    // note: short cycle preventor on Boiler will ignore quick changes of state
  }
  
 // zone 2
  Vo2 = analogRead(Thermistor2);
  R2z2 = R1 * (1023.0 / (float)Vo2 - 1.0);
  logR2z2 = log(R2z2);
  T2 = (1.0 / (c1 + c2*logR2z2 + c3*logR2z2*logR2z2*logR2z2));
  T2 = T2 - 273.15;
  T2 = ((T2 * 9.0)/ 5.0) + 32.0; 
  if (T2 < (setpointzone2-diff)) {
    digitalWrite(valve2, HIGH);
  }
  if (T2 > (setpointzone2 + diff)) {
    digitalWrite(valve2, LOW);
  }

// zone 3
  Vo3 = analogRead(Thermistor3);
  R2z3 = R1 * (1023.0 / (float)Vo3 - 1.0);
  logR2z3 = log(R2z3);
  T3 = (1.0 / (c1 + c2*logR2z3 + c3*logR2z3*logR2z3*logR2z3));
  T3 = T3 - 273.15;
  T3 = ((T3 * 9.0)/ 5.0) + 32.0; 
  if (T3 < (setpointzone3-diff)) {
    digitalWrite(valve3, HIGH);
  }
  if (T3 > (setpointzone3 + diff)) {
    digitalWrite(valve3, LOW);
  }

// zone 4
  Vo4 = analogRead(Thermistor4 );
  R2z4 = R1 * (1023.0 / (float)Vo4 - 1.0);
  logR2z4 = log(R2z4);
  T4 = (1.0 / (c1 + c2*logR2z4 + c3*logR2z4*logR2z4*logR2z4));
  T4 = T4 - 273.15;
  T4 = ((T4 * 9.0)/ 5.0) + 32.0; 
  if (T4 < (setpointzone4-diff)) {
    digitalWrite(valve4, HIGH);
  }
  if (T4 > (setpointzone4 + diff)) {
    digitalWrite(valve4, LOW);
  }

// zone 5
  Vo5 = analogRead(Thermistor5);
  R2z5 = R1 * (1023.0 / (float)Vo5 - 1.0);
  logR2z5 = log(R2z5);
  T5 = (1.0 / (c1 + c2*logR2z5 + c3*logR2z5*logR2z5*logR2z5));
  T5 = T5 - 273.15;
  T5 = ((T5 * 9.0)/ 5.0) + 32.0; 
  if (T5 < (setpointzone5-diff)) {
    digitalWrite(valve5, HIGH);
  }
  if (T5 > (setpointzone5 + diff)) {
    digitalWrite(valve5, LOW);
  }
  
// zone 6
  Vo6 = analogRead(Thermistor6);
  R2z6 = R1 * (1023.0 / (float)Vo6 - 1.0);
  logR2z6 = log(R2z6);
  T6 = (1.0 / (c1 + c2*logR2z6 + c3*logR2z6*logR2z6*logR2z6));
  T6 = T6 - 273.15;
  T6 = ((T6 * 9.0)/ 5.0) + 32.0; 
  if (T6 < (setpointzone6-diff)) {
    digitalWrite(valve6, HIGH);
  }
  if (T6 > (setpointzone6 + diff)) {
    digitalWrite(valve6, LOW);
  }

// zone 7
  Vo7 = analogRead(Thermistor7 );
  R2z4 = R1 * (1023.0 / (float)Vo7 - 1.0);
  logR2z7 = log(R2z7);
  T7 = (1.0 / (c1 + c2*logR2z7 + c3*logR2z7*logR2z7*logR2z7));
  T7 = T7 - 273.15;
  T7 = ((T7 * 9.0)/ 5.0) + 32.0; 
  if (T7 < (setpointzone7-diff)) {
    digitalWrite(valve7, HIGH);
  }
  if (T7 > (setpointzone7 + diff)) {
    digitalWrite(valve7, LOW);
  }

// zone 8
  Vo8 = analogRead(Thermistor8);
  R2z8 = R1 * (1023.0 / (float)Vo8 - 1.0);
  logR2z8 = log(R2z8);
  T8 = (1.0 / (c1 + c2*logR2z8 + c3*logR2z8*logR2z8*logR2z8));
  T8 = T8 - 273.15;
  T8 = ((T8 * 9.0)/ 5.0) + 32.0; 
  if (T8 < (setpointzone8-diff)) {
    digitalWrite(valve8, HIGH);
  }
  if (T8 > (setpointzone8 + diff)) {
    digitalWrite(valve8, LOW);
  }

  // below is for serial monitor
  
  heatstate1 = digitalRead (valve1);
  Serial.print("Temp zone 1: "); 
  Serial.print(T);
  Serial.print("  SetPt1: "); 
  Serial.print(setpointzone1);
  if (heatstate1 == LOW ){
    Serial.println(" Valve 1 Closed");
  }
  if (heatstate1 == HIGH ){
    Serial.println(" Valve 1 Open ");
  }
  
  heatstate2 = digitalRead (valve2);
  Serial.print("Temp zone 2: "); 
  Serial.print(T2);
  Serial.print("  SetPt2: "); 
  Serial.print(setpointzone2);
  if (heatstate2 == LOW ){
    Serial.println(" Valve 2 Closed ");
  }
  if (heatstate2 == HIGH ){
    Serial.println(" Valve 2 Open ");
  }
  
  heatstate3 = digitalRead (valve3);
  Serial.print("Temp zone 3: "); 
  Serial.print(T2);
  Serial.print("  SetPt3: "); 
  Serial.print(setpointzone3);
  if (heatstate3 == LOW ){
    Serial.println(" Valve 3 Closed ");
  }
  if (heatstate3 == HIGH ){
    Serial.println(" Valve 3 Open");
  }
  
  heatstate4 = digitalRead (valve4);
  Serial.print("Temp zone 4: "); 
  Serial.print(T2);
  Serial.print("  SetPt4: "); 
  Serial.print(setpointzone4);
  if (heatstate4 == LOW ){
    Serial.println(" Valve 4 Closed ");
  }
  if (heatstate4 == HIGH ){
    Serial.println(" Valve 4 Open ");
  }
  
  heatstate5 = digitalRead (valve5);
  Serial.print("Temp zone 5: "); 
  Serial.print(T2);
  Serial.print("  SetPt5: "); 
  Serial.print(setpointzone5);
  if (heatstate5 == LOW ){
    Serial.println(" Valve 5 Closed ");
  }
  if (heatstate5 == HIGH ){
    Serial.println(" Valve 5 Open ");
  }
  
  heatstate6 = digitalRead (valve6);
  Serial.print("Temp zone 6: "); 
  Serial.print(T2);
  Serial.print("  SetPt6: "); 
  Serial.print(setpointzone6);
  if (heatstate6 == LOW ){
    Serial.println(" Valve 6 Closed ");
  }
  if (heatstate6 == HIGH ){
    Serial.println(" Valve 6 Open ");
  }
  
  heatstate7 = digitalRead (valve7);
  Serial.print("Temp zone 7: "); 
  Serial.print(T2);
  Serial.print("  SetPt7: "); 
  Serial.print(setpointzone7);
  if (heatstate7 == LOW ){
    Serial.println(" Valve 7 Closed ");
  }
  if (heatstate7 == HIGH ){
    Serial.println(" Valve 7 Open ");
  }
  
  heatstate8 = digitalRead (valve8);
  Serial.print("Temp zone 8: "); 
  Serial.print(T2);
  Serial.print("  SetPt8: "); 
  Serial.print(setpointzone8);
  if (heatstate8 == LOW ){
    Serial.println(" Valve 8 Closed ");
  }
  if (heatstate8 == HIGH ){
    Serial.println(" Valve 8 Open ");
  }
  Serial.print("         Boiler On ");
  Serial.print(" Pump On ");
  Serial.println();
  Serial.println(); 
  
  delay(10000);
}

It looks like your sketch could benefit from the use of arrays, possibly an array of structs, in order to reduce the amount of
duplicate code

Bob,
I agree. Thanks. I'm rusty on arrays but I will brush up on them.

Come back if you get stuck

I'll post my array problems (or success) on the programming category forum. Thanks again.

Here is my 99% finished code. I used Bob’s array suggestion where I could.

The Arduino Nano does almost all the work. It gets a single 3v signal (with resistors for safety) from a Raspberry Pi 4 GPIO output for occupied/unoccupied schedule. I have 8 zone valves with 6 on-slab thermistor floor sensors and the garage zone under tagalong control with another zone. The laundry valve is not used.

Valves and automation including panel and relays etc are up and running on my bench for two weeks now. Installation in the 3500 sq ft house will be in August. Project is far away and I am in NC. I have total control from any place in the world via VNC for $0 cost per month since VNC allows up to 5 remote sites for free.

To change set points I VNC into the RPi4 and use the headless desktop to modify my Arduino sketch in the IDE and then upload to change set points and save the sketch. I also can modify the time clock lines of code to be auto (via RPi4) or ON or OFF. I can monitor all the temperatures using the serial monitor feature of the Arduino IDE. I used all 8 analog inputs by adding HWSupply temp and HWReturn temp using thermistors. I needed one temperarature more for outside air, so I used a DS18B20 into one of the Arduino Nano digital inputs.

I used a 5 volt relay strip of 8 relays to interface the outputs to the valves. A separate independent panel (Honeywell R8888, qty 2) gets signals from my relays and controls the boiler on and off.

The Arduino just keeps on plugging along even after a power interruption. The RPi4 needed a crontab -e program to initiate the time lock python (Thonny) program after a reboot, and my python program picks up the time from the internet on reboot and sets time clock occupancy correctly.

I am proud of what I have done so far. I am also open to suggestions from anyone. I seem to have no bugs. All works well with the actual finished and wired panel and actual physical valves. If any pictures are requested or further info needed, let me know. And thanks, Bob for array suggestion.

ps - I was not happy with the “noise” of the thermistors that made the temperature “jump” as much as 1 degree up and down in milliseconds. Not sure why, I don’t suspect electronic noise. I suspect calculation noise of the complicated log math used for the non-linear thermistors. So, I sampled each reading from each thermistor 20 times over two seconds and averaged the 20 readings.

Btw, I can copy any amount of serial monitor temperature readings and by using spreadsheet data sort commands and the spreadsheet graph function, I can produce temperature graphs for hours or days.

Arduini Nano Code ------------------------------------------ (Thonny Python code follows after Nano Code)

[code]
NOTE: intermediate code removed and replaced with new code below on another day.  The only changes were zone names.
}
[/code]

And here is RPi4 python (Thonny) code for timeclock function.

import datetime
import time
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
clockout = 14
GPIO.setup (clockout, GPIO.OUT)
x=0
while True:
    comment =  ("You can change the AWAKE time window on next two lines.")
    comment2 = ("Note, use military, or 24 hour time format, with comma between hour and minute.")
    comment3 = ("Note also that there can be no leading zero in time.")
    comment4 = ("Example: 4am is (4,0), 4:03am is (4,3) etc. 11pm is (23,0).  10:33pm is (22,33).")
    on_time = datetime.time(6,0)   # this means 6:00am is start of AWAKE time
    off_time = datetime.time(23,0)  # this means 11:00 pm is start of ASLEEP time
    current_time = datetime.datetime.now().time()
    
    if (current_time < on_time) or (current_time > off_time):
        print("Night Time detected. (change if needed, line 24,25)")
        print("ASLEEP setpoints are in effect now...")
        GPIO.output(clockout,GPIO.LOW)
        print("Time is:", current_time)
        print("AWAKE on_time:" , on_time)
        print ("AWAKE off_time:", off_time)
        
    else:
         print("Day Time detected. (change if needed, line 24, 25)")
         print("AWAKE setpoints are in effect now...")
         GPIO.output(clockout,GPIO.HIGH)
         print("Time is:", current_time)
         print("AWAKE on_time:" , on_time)
         print ("AWAKE off_time:", off_time)
        
    time.sleep(30)
    x=x+1
    print("-------------------------------",x)
    
  1. List item

serial monitor via headless with VNC

image

all sensors are not in place in my test bench setup and setpoints are in test values

here is the Thonny output text

image

That's odd because I don't see any arrays in the sketch

Bob,
Apologies. I used "for" loops to sample my thermistor readings, but, you are right, those are a far cry from arrays.
I am not a good programmer, I am a plugger who hits code with an endless hammer of try and re-write endless times.
My code works, however sloppy it is.
However, Bob, if I were to get the time and smarts to tighten the code with arrays, I will try. For example, I think, my 6 paragraphs of zone sensor/valve operation are identical except for the analog input and digital output, I think. So, to do the array I think I would just have to array those two values in two arrays of 6? I'm not sure.
Bob, I put my code here for critical comment so I very much welcome your comments. I could have published (or bragged) on a hydronic diy forum on a plumbing website, but that's not what I want to do.
thanks.
I'll try to learn arrays.

I don't want to hijack my own thread here with sensor questions so I will open a new topic in the sensor forum here about why I am getting "noise" and unstable sensor readings on both my thermistor reading calculations as well as my digital DS18B20 sensor

98% finished code , subject, of course, to revision, when arrays are mastered and EEPROM used from GUI.

[code]
// rev 1 - May 30, 2022  --  Boiler Hydronic Program  --  by DDDD Co. 
//           Items that Owner can change  ///////////////////////////////////////////////////////

//           Decision for DAY/NIGHT or Automatic from Timeclock program, change as needed, then upload.
int decisionTimeClock = 2;   // Insert 0 for NIGHT, 
                            // Insert 1 for DAY, 
                           // Insert 2 for Automatic by Python TimeClock program on Raspberry Pi

//           Decision for Garage valve. change as needed, then upload.
int decisionGar_Valve = 0;        // insert 1 for follow the action of BDRM2ndFL valve
                                 // Insert 0 for Closed , remember the valve can be opened my manual lever


//           Decision for Laundry valve. change as needed, then upload.
int decisionLaundry_Valve = 0;        // insert 1 for follow the action of BDRM1stFL valve
                                 // Insert 0 for Closed , remember the valve can be opened my manual lever
                                 
//          DAY setpoints                                                     
float DAYSetBDRM1stFL =     50;  // change this as needed then upload, and saving file is automatic.
float DAYSetLivingRoom =    99;  // change this as needed
float DAYSetKitchen =       71.3;  // change this as needed
float DAYSetBDRM2ndFL =     98;  // change this as needed
float DAYSetSouthBDRM3FL =  71.5;  // change this as needed
float DAYSetNorthBDRM3FL =  71.6;  // change this as needed

//          NIGHT setpoints
float NIGHTSetBDRM1stFL =     61.1;  // change this as needed then upload, and saving file is automatic.
float NIGHTSetLivingRoom  =   61.2;  // change this as needed
float NIGHTSetKitchen  =      61.3;  // change this as needed
float NIGHTSetBDRM2ndFL =     61.4;  // change this as needed
float NIGHTSetSouthBDRM3FL =  61.5;  // change this as needed
float NIGHTSetNorthBDRM3FL =  61.6;  // change this as needed

// DO NOT CHANGE ANYTHING BELOW THIS LINE //////////////////////////////////////////////////

//---Apartment (BDRM1stFL)
float read1    = 0;             //read 20 times and then take average of all 20
float total1   = 0;         // the running total
float average1 = 0;        // the average
//---

//---Living Room (LivingRoom)
float read2    = 0;
float total2   = 0;         // the running total
float average2 = 0;        // the average
//---

//---Kitchen (Kitchen)
float read3    = 0;
float total3   = 0;         // the running total
float average3 = 0;        // the average
//---

//---BDRM2ndFL (BDRM2ndFL) 2nd level
float read4    = 0;
float total4   = 0;         // the running total
float average4 = 0;        // the average
//---

//---SouthBDRM3FL (SouthBDRM3FL) 3rd Level
float read5    = 0;
float total5   = 0;         // the running total
float average5 = 0;        // the average
//---

//---NorthBDRM3FL 3rd Level (NorthBDRM3FL)
float read6    = 0; 
float total6   = 0;         // the running total
float average6 = 0;        // the average
//---

//---HotWaterSupply from Boiler (HWS)
float read7    = 0;
//---

//---HotWaterReturn from Boiler (HWR)
float read8    = 0; 
//---

//---Outside Air Temperature (OAT)
float read9    = 0; 
//---

//    BDRM1stFL data
int   BDRM1stFLThermistorPin = 0;
int   Vo1;
float logR2z1, R2z1, BDRM1stFLT;
int   BDRM1stFL_Valve = 2;     
int   BDRM1stFLHeatState = 0; 

//    LivingRoom data
int   LivingRoomThermistorPin = 1;
int   Vo2;
float logR2z2, R2z2, LivingRoomT;
int   LivingRoom_Valve = 3;      
int   LivingRoomHeatState = 0; 

//    Kitchen data
int   KitchenThermistorPin = 2;
int   Vo3;
float logR2z3, R2z3, KitchenT;
int   Kitchen_Valve = 4;      
int   KitchenHeatState = 0; 

//    BDRM2ndFL data
int   BDRM2ndFLThermistorPin = 3;
int   Vo4;
float logR2z4, R2z4, BDRM2ndFLT;
int   BDRM2ndFL_Valve = 5;      
int   BDRM2ndFLHeatState = 0;

//    SouthBDRM3FL data
int   SouthBDRM3FLThermistorPin = 4;
int   Vo5;
float logR2z5, R2z5, SouthBDRM3FLT;
int   SouthBDRM3FL_Valve = 6;      
int   SouthBDRM3FLHeatState = 0; 

//    NorthBDRM3FL data
int   NorthBDRM3FLThermistorPin = 5;
int   Vo6;
float logR2z6, R2z6, NorthBDRM3FLT;
int   NorthBDRM3FL_Valve = 7;      
int   NorthBDRM3FLHeatState = 0;

//    Garage (Gar) Valve.   Valve opens and closes with BDRM2ndFL valve "IF" Gar  decision is "1" on line 10
int   Gar_Valve = 8;                // otherwise, valve stays closed
int   GarHeatState = 0;

//    Laundry Valve.   Valve opens and closes with BDRM1stFL valve "IF" Laundry  decision is "1" on line 15
int   Laundry_Valve = 9;                // otherwise, valve stays closed
int   LaundryHeatState = 0;

//    HWS data
int   HWSThermistorPin = 6;
int   Vo7;
float logR2z7, R2z7, HWS;

//    HWR data
int   HWRThermistorPin = 7;
int   Vo8;
float logR2z8, R2z8, HWR;
 
//for all zones
float diff = 1;      //Change this if needed.  example: if setpoint 69 and diff is 1, it will
                     //     go on at 68.0 and go off at 70.0, so +/- is the differential.
                     // If diff is set for 1.5, and setpoint is 69, it will go on at 67.5
                     //     and go off at 70.5.  
                        
float R1s1 =  10040;     // thermistor divider resistor actual measured ohms by meter
float R1s2 =  10000;     // --- using these actual values rather than nominal 10k improves accuracy
float R1s3 =   9920; 
float R1s4 =  10030; 
float R1s5 =  10070;     
float R1s6 =  10020;     
float R1s7 =  10000; 
float R1s8 =   9990;

//     these are factors required for thermistor calculations since temp vs ohm curve is non-linear
float c1   = 1.009249522e-03, c2 = 2.378405444e-04, c3 = 2.019202697e-07;

int   timeClock = 10;     //pin 10 accepts 3.3v from GPIO 14 (pin 8) from RPi4 python program for NIGHT and DAY cycles
int   timeRead  =  0;     // digital read of input from timeclock
int   led_built_in = 13;  // built in led on Nano board. When lit, it indicates time clock program is calling for DAY
                          // if setpoints do not follow clock output, then maybe user choice on line 5 is wrong
                          
int   interReadingDelay = 100;  // delayDelay in microseconds between the 20 readings for each sensor for each loop
                                  // required because Thermistors need calculations and that causes "noise".
// the following lines are for the ninth sensor, DS18B20. I needed 9 sensors and the Arduini Nano only had 8 AO inputs
#include <OneWire.h>                      // the DS18B20 uses a digital input pin instead of needing an analog pin
#include <DallasTemperature.h>
 
// Data wire is plugged into pin 11 on the Arduino
#define ONE_WIRE_BUS 11
 
// Setup a oneWire instance to communicate with any OneWire devices 
// (not just Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
 
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
float sensorread = 0;
//end DB18B20 program

void setup() {
  Serial.begin(9600); 
  // Start up the DB18B20 sensor library
  sensors.begin();
                             // declare the valves as an OUTPUT:  valves are closed when not powered on
  pinMode (BDRM1stFL_Valve, OUTPUT);
  pinMode (LivingRoom_Valve,  OUTPUT);
  pinMode (Kitchen_Valve,  OUTPUT);
  pinMode (BDRM2ndFL_Valve, OUTPUT);
  pinMode (SouthBDRM3FL_Valve, OUTPUT);
  pinMode (NorthBDRM3FL_Valve, OUTPUT);
  pinMode (Gar_Valve,  OUTPUT);
  pinMode (Laundry_Valve, OUTPUT);
  pinMode (led_built_in, OUTPUT);  // declare led 13 as an output
  pinMode (timeClock, INPUT);      // declare timeClock as INPUT
  //  normally, Arduino (Atmega) pins default to inputs, so they don't need to be explicitly 
  //   declared as inputs with pinMode() when you're using them as inputs.
  }

void loop() {

  sensors.requestTemperatures(); // Send the command to get temperature from ds18b20
  Serial.println (" Hydronic Computerized Control System, DDDD Company, (C) 2022");
  Serial.print(" Outside Air Temperature:   ");
  sensorread = ((((sensors.getTempCByIndex(0))*1.8)+32));
  Serial.println(sensorread); // Why "byIndex"? 
    // You can have more than one IC on the same bus. 
    // 0 refers to the first IC on the wire
  
// read timeclock to decide if house is NIGHT or DAY
  if (decisionTimeClock == 1) {                         // if line 5 is 1, DAY
    timeRead = HIGH;
    Serial.println(" Boiler Heat Control : House is in DAY mode by line 5 command");
    Serial.println("                Temp     DAY Setpoints  "); 
    }
    
  if (decisionTimeClock == 0) {                        // if line 5 is 0, NIGHT.
         timeRead = LOW;
         Serial.println(" Boiler Heat Control : House is in NIGHT mode by line 5 command");
         Serial.println("                 Temp   NIGHT Setpoints  ");
    }
  
  if (decisionTimeClock == 2) {                       // if line 5 is 2, then physical time clock decides
         timeRead = digitalRead(timeClock);                   
  }
  
  if ((timeRead == HIGH)&&(decisionTimeClock == 2)){
            digitalWrite(led_built_in, HIGH);    // led on board Nano ligts up if time clock is deciding
            Serial.println(" Boiler Heat Control : House is in DAY mode by clock control");
            Serial.println("               Temp      DAY Setpoints  "); 
    }
    
  if ((timeRead == LOW)&&(decisionTimeClock == 2)) {
            digitalWrite(led_built_in, LOW);
            Serial.println(" Boiler Heat Control : House is in NIGHT mode by clock control");
            Serial.println("               Temp     NIGHT Setpoints  ");
            
    }
  
// program for BDRM1stFL, ground level apartment
  float total1 = 0;
  for (int i = 0; i < 20; i++) {                      // take 20 readings and average them to smooth readings, typical.
      Vo1 = analogRead(BDRM1stFLThermistorPin);
      R2z1      = R1s1 * (1023.0 / (float)Vo1 - 1.0);
      logR2z1   = log(R2z1);
      read1 = (1.0 / (c1 + c2*logR2z1 + c3*logR2z1*logR2z1*logR2z1));
      delay (interReadingDelay);
      total1 = total1 + read1;
  }
  average1 = total1/20;
  BDRM1stFLT    = average1 - 273.15;             // Kelvin temperature converted to Celsius
  BDRM1stFLT    = ((BDRM1stFLT * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit

  //  DAY, as determined by external timeClock or command in program
  // open the valve if below the DAY setpoint less the differential
  if ((BDRM1stFLT < (DAYSetBDRM1stFL-1))&&(timeRead==HIGH)) {    // "HIGH" means DAY and "LOW" means NIGHT.
    digitalWrite(BDRM1stFL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the DAY setpoint plus the differential
  if ((BDRM1stFLT > (DAYSetBDRM1stFL+1))&&(timeRead==HIGH)) {
    digitalWrite(BDRM1stFL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  // NIGHT, as determined by external timeClock
 // open the valve if below the NIGHT setpoint less the differential
  if ((BDRM1stFLT < (NIGHTSetBDRM1stFL-1))&&(timeRead==LOW)) {
    digitalWrite(BDRM1stFL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the NIGHT setpoint plus the differential
  if ((BDRM1stFLT > (NIGHTSetBDRM1stFL+1))&&(timeRead==LOW)) {
    digitalWrite(BDRM1stFL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  
  BDRM1stFLHeatState = digitalRead (BDRM1stFL_Valve);
  Serial.print (" BDRM1stFL:     "); 
  Serial.print (BDRM1stFLT);
  Serial.print ("    SetP= ");
  if (timeRead == HIGH){
    Serial.print(DAYSetBDRM1stFL);
  }
  else{
    Serial.print(NIGHTSetBDRM1stFL);    
  }
    
  if (BDRM1stFLHeatState == LOW ){
    Serial.println ("    Valve is Closed ");
  }
  else{
    Serial.println ("    Valve is Open ");
  }


// program for LivingRoom, LivingRooming room
 float total2 = 0;
  for (int i = 0; i < 20; i++) {
      Vo2 = analogRead(LivingRoomThermistorPin);
      R2z2      = R1s2 * (1023.0 / (float)Vo2 - 1.0);
      logR2z2   = log(R2z2);
      read2 = (1.0 / (c1 + c2*logR2z2 + c3*logR2z2*logR2z2*logR2z2));
      delay (interReadingDelay);
      total2 = total2 + read2;
  }
  average2 = total2/20;
  LivingRoomT    = average2 - 273.15;             // Kelvin temperature converted to Celsius
  LivingRoomT    = ((LivingRoomT * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit
  //  DAY, as determined by external timeClock
  // open the valve if below the DAY setpoint less the differential
  if ((LivingRoomT < (DAYSetLivingRoom-1))&&(timeRead==HIGH)) {
    digitalWrite(LivingRoom_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the DAY setpoint plus the differential
  if ((LivingRoomT > (DAYSetLivingRoom+1))&&(timeRead==HIGH)) {
    digitalWrite(LivingRoom_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  // NIGHT, as determined by external timeClock
 // open the valve if below the NIGHT setpoint less the differential
  if ((LivingRoomT < (NIGHTSetLivingRoom-1))&&(timeRead==LOW)) {
    digitalWrite(LivingRoom_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the NIGHT setpoint plus the differential
  if ((LivingRoomT > (NIGHTSetLivingRoom+1))&&(timeRead==LOW)) {
    digitalWrite(LivingRoom_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  
  LivingRoomHeatState = digitalRead (LivingRoom_Valve);
  Serial.print (" LivingRoom:    "); 
  Serial.print (LivingRoomT);
  Serial.print ("    SetP= ");
  if (timeRead == HIGH){
    Serial.print(DAYSetLivingRoom);
  }
  else{
    Serial.print(NIGHTSetLivingRoom);    
  }
    
  if (LivingRoomHeatState == LOW ){
    Serial.println ("    Valve is Closed ");
  }
  else{
    Serial.println ("    Valve is Open ");
  }

// program for Kitchen, Kitchenchen
   float total3 = 0;
  for (int i = 0; i < 20; i++) {
      Vo3 = analogRead(KitchenThermistorPin);
      R2z3      = R1s3 * (1023.0 / (float)Vo3 - 1.0);
      logR2z3   = log(R2z3);
      read3 = (1.0 / (c1 + c2*logR2z3 + c3*logR2z3*logR2z3*logR2z3));
      delay (interReadingDelay);
      total3 = total3 + read3;
  }
  average3 = total3/20;
  KitchenT    = average3 - 273.15;             // Kelvin temperature converted to Celsius
  KitchenT    = ((KitchenT * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit
  //  DAY, as determined by external timeClock
  // open the valve if below the DAY setpoint less the differential
  if ((KitchenT < (DAYSetKitchen-1))&&(timeRead==HIGH)) {
    digitalWrite(Kitchen_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the DAY setpoint plus the differential
  if ((KitchenT > (DAYSetKitchen+1))&&(timeRead==HIGH)) {
    digitalWrite(Kitchen_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  // NIGHT, as determined by external timeClock
 // open the valve if below the NIGHT setpoint less the differential
  if ((KitchenT < (NIGHTSetKitchen-1))&&(timeRead==LOW)) {
    digitalWrite(Kitchen_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the NIGHT setpoint plus the differential
  if ((KitchenT > (NIGHTSetKitchen+1))&&(timeRead==LOW)) {
    digitalWrite(Kitchen_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  
  KitchenHeatState = digitalRead (Kitchen_Valve);
  Serial.print (" Kitchen:       "); 
  Serial.print (KitchenT);
  Serial.print ("    SetP= ");
  if (timeRead == HIGH){
    Serial.print(DAYSetKitchen);
  }
  else{
    Serial.print(NIGHTSetKitchen);    
  }
    
  if (KitchenHeatState == LOW ){
    Serial.println ("    Valve is Closed ");
  }
  else{
    Serial.println ("    Valve is Open ");
  }

// program for BDRM2ndFL, on middle level of house
  float total4 = 0;
  for (int i = 0; i < 20; i++) {
      Vo4 = analogRead(BDRM2ndFLThermistorPin);
      R2z4      = R1s4 * (1023.0 / (float)Vo4 - 1.0);
      logR2z4   = log(R2z4);
      read4 = (1.0 / (c1 + c2*logR2z4 + c3*logR2z4*logR2z4*logR2z4));
      delay (interReadingDelay);
      total4 = total4 + read4;
  }
  average4 = total4/20;
  BDRM2ndFLT    = average4 - 273.15;             // Kelvin temperature converted to Celsius
  BDRM2ndFLT    = ((BDRM2ndFLT * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit
  //  DAY, as determined by external timeClock
  // open the valve if below the DAY setpoint less the differential
  if ((BDRM2ndFLT < (DAYSetBDRM2ndFL-1))&&(timeRead==HIGH)) {
    digitalWrite(BDRM2ndFL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the DAY setpoint plus the differential
  if ((BDRM2ndFLT > (DAYSetBDRM2ndFL+1))&&(timeRead==HIGH)) {
    digitalWrite(BDRM2ndFL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  // NIGHT, as determined by external timeClock
 // open the valve if below the NIGHT setpoint less the differential
  if ((BDRM2ndFLT < (NIGHTSetBDRM2ndFL-1))&&(timeRead==LOW)) {
    digitalWrite(BDRM2ndFL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the NIGHT setpoint plus the differential
  if ((BDRM2ndFLT > (NIGHTSetBDRM2ndFL+1))&&(timeRead==LOW)) {
    digitalWrite(BDRM2ndFL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  
  BDRM2ndFLHeatState = digitalRead (BDRM2ndFL_Valve);
  Serial.print (" BDRM2ndFL:     "); 
  Serial.print (BDRM2ndFLT);
  Serial.print ("    SetP= ");
  if (timeRead == HIGH){
    Serial.print(DAYSetBDRM2ndFL);
  }
  else {
    Serial.print(NIGHTSetBDRM2ndFL);    
  }
    
  if (BDRM2ndFLHeatState == LOW ){
    Serial.println ("    Valve is Closed ");
  }
  else {
    Serial.println ("    Valve is Open ");
  }

// program for SouthBDRM3FL room, on top level, south side
  float total5 = 0;
  for (int i = 0; i < 20; i++) {
      Vo5 = analogRead(SouthBDRM3FLThermistorPin);
      R2z5      = R1s5 * (1023.0 / (float)Vo5 - 1.0);
      logR2z5   = log(R2z5);
      read5 = (1.0 / (c1 + c2*logR2z5 + c3*logR2z5*logR2z2*logR2z5));
      delay (interReadingDelay);
      total5 = total5 + read5;
  }
  average5 = total5/20;
  SouthBDRM3FLT    = average5 - 273.15;             // Kelvin temperature converted to Celsius
  SouthBDRM3FLT    = ((SouthBDRM3FLT * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit
  //  DAY, as determined by external timeClock
  // open the valve if below the DAY setpoint less the differential
  if ((SouthBDRM3FLT < (DAYSetSouthBDRM3FL-1))&&(timeRead==HIGH)) {
    digitalWrite(SouthBDRM3FL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the DAY setpoint plus the differential
  if ((SouthBDRM3FLT > (DAYSetSouthBDRM3FL+1))&&(timeRead==HIGH)) {
    digitalWrite(SouthBDRM3FL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  // NIGHT, as determined by external timeClock
 // open the valve if below the NIGHT setpoint less the differential
  if ((SouthBDRM3FLT < (NIGHTSetSouthBDRM3FL-1))&&(timeRead==LOW)) {
    digitalWrite(SouthBDRM3FL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the NIGHT setpoint plus the differential
  if ((SouthBDRM3FLT > (NIGHTSetSouthBDRM3FL+1))&&(timeRead==LOW)) {
    digitalWrite(SouthBDRM3FL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  
  SouthBDRM3FLHeatState = digitalRead (SouthBDRM3FL_Valve);
  Serial.print (" SouthBDRM3FL:  "); 
  Serial.print (SouthBDRM3FLT);  
  Serial.print ("    SetP= ");
  if (timeRead == HIGH){
    Serial.print(DAYSetSouthBDRM3FL);
  }
  else {
    Serial.print(NIGHTSetSouthBDRM3FL);    
  }
    
  if (SouthBDRM3FLHeatState == LOW ){
    Serial.println ("    Valve is Closed ");
  }
  else {
    Serial.println ("    Valve is Open ");
  }

// program for 3rd Fl NorthBDRM3FL, on top level, north side
   float total6 = 0;
  for (int i = 0; i < 20; i++) {
      Vo6 = analogRead(NorthBDRM3FLThermistorPin);
      R2z6      = R1s6 * (1023.0 / (float)Vo6 - 1.0);
      logR2z6   = log(R2z6);
      read6 = (1.0 / (c1 + c2*logR2z6 + c3*logR2z6*logR2z6*logR2z6));
      delay (interReadingDelay);
      total6 = total6 + read6;
  }
  average6 = total6/20;
  NorthBDRM3FLT    = average6 - 273.15;             // Kelvin temperature converted to Celsius
  NorthBDRM3FLT    = ((NorthBDRM3FLT * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit
  //  DAY, as determined by external timeClock
  // open the valve if below the DAY setpoint less the differential
  if ((NorthBDRM3FLT < (DAYSetNorthBDRM3FL-1))&&(timeRead==HIGH)) {
    digitalWrite(NorthBDRM3FL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the DAY setpoint plus the differential
  if ((NorthBDRM3FLT > (DAYSetNorthBDRM3FL+1))&&(timeRead==HIGH)) {
    digitalWrite(NorthBDRM3FL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  // NIGHT, as determined by external timeClock
 // open the valve if below the NIGHT setpoint less the differential
  if ((NorthBDRM3FLT < (NIGHTSetNorthBDRM3FL-1))&&(timeRead==LOW)) {
    digitalWrite(NorthBDRM3FL_Valve, HIGH);                     // opens valve to allow hot water to zone
  }
  // close the valve if above the NIGHT setpoint plus the differential
  if ((NorthBDRM3FLT > (NIGHTSetNorthBDRM3FL+1))&&(timeRead==LOW)) {
    digitalWrite(NorthBDRM3FL_Valve, LOW);                      // closes valve to stop hot water to zone
  }
  
  NorthBDRM3FLHeatState = digitalRead (NorthBDRM3FL_Valve);
  Serial.print (" NorthBDRM3FL:  "); 
  Serial.print (NorthBDRM3FLT);
  Serial.print ("    SetP= ");
  if (timeRead == HIGH){
    Serial.print(DAYSetNorthBDRM3FL);
  }
  else {
    Serial.print(NIGHTSetNorthBDRM3FL);    
  }
    
  if (NorthBDRM3FLHeatState == LOW ){
    Serial.println ("    Valve is Closed ");
  }
  else {
    Serial.println ("    Valve is Open ");
  }

// Program for Garage Valve
  delay (1000);
  if ((BDRM2ndFLHeatState == HIGH) && (decisionGar_Valve == 1)) {
    digitalWrite(Gar_Valve, HIGH);                     // opens valve if BDRM2ndFL valve is open and
                                                      // if Owner chooses Garage to follow BDRM2ndFL valve
  }
  else {
        digitalWrite(Gar_Valve, LOW);                  //close the valve
     }
  
  GarHeatState = digitalRead (Gar_Valve);
  
  if (GarHeatState == LOW ){
    Serial.println (" Garage (can follow Bed2ndFloor)        Valve is Closed ");
  }
  else {
    Serial.println (" Garage (can follow Bed2ndFloor)        Valve is Open ");
  }
  

// Program for Laundry Valve
  delay (1000);
  if ((BDRM1stFLHeatState == HIGH) && (decisionLaundry_Valve == 1)) {
    digitalWrite(Laundry_Valve, HIGH);                     // opens valve if BDRM1stFL valve is open and
                                                      // if Owner chooses Garage to follow BDRM1stFL valve
  }
  else {
        digitalWrite(Laundry_Valve, LOW);                  //close the valve
     }
  
  LaundryHeatState = digitalRead (Laundry_Valve);
  
  if (LaundryHeatState == LOW ){
    Serial.println (" Laundry (can follow Bed1stFloor)       Valve is Closed ");
  }
  else {
    Serial.println (" Laundry (can follow Bed1stFloor)       Valve is Open ");
  }
  Serial.println(" ");
  
  // program for HWR.     No need to average these water sensor readings since they are just for info.
  Vo7 = analogRead(HWSThermistorPin);
  R2z7      = R1s7 * (1023.0 / (float)Vo7 - 1.0);
  logR2z7   = log(R2z7);
  read7 = (1.0 / (c1 + c2*logR2z7 + c3*logR2z7*logR2z7*logR2z7));
  HWS    = read7 - 273.15;             // Kelvin temperature converted to Celsius
  HWS    = ((HWS * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit
  
  // program for HWR
  Vo8 = analogRead(HWRThermistorPin);
  R2z8      = R1s8 * (1023.0 / (float)Vo8 - 1.0);
  logR2z8   = log(R2z8);
  read8 = (1.0 / (c1 + c2*logR2z8 + c3*logR2z8*logR2z8*logR2z8));
  HWR    = read8 - 273.15;             // Kelvin temperature converted to Celsius
  HWR    = ((HWR * 9.0)/ 5.0) + 32.0;  // Celsius converted to Farhenheit
  
  Serial.print (" Hot Water Supply Temp    "); 
  Serial.println (HWS);
  Serial.print (" Hot Water Return Temp    "); 
  Serial.println (HWR);




  
  Serial.println(" ");
  Serial.println("------------------------------------------------------------------ ");
  Serial.println(" ");
  
  delay(15000);   // delay 15 seconds
 
}
[/code]

Serial Monitor printout/readout

image

Two images to place here to add visual context aid to coding. I am not trying to make this a copper pipe/hydronic thread, so I will probably post this whole project on a plumbing forum for comments about installation etc. It is only bench tested so far. Installation is in July 2022, soon.


It looks like you upgraded to a Pi4. If I were working on this, I'd like to have a little web page on the Pi to make temporary (or permanent) changes to the setpoints without having to reload the code.

But as already advised, take the time to master arrays, better yet arrays of struct. Your code will be shorter and cleaner if you can do it.

Wildbill,
You have opened up a future line of study for me on the webpage idea. Without asking you for a tutorial can you explain your statement about "permanent" setpoint changes? Isn't the only way a Sketch can be permanent is for it to be "loaded"? Can a script to the webpage take a change of setpoint, send it to the Arduino code input and then save the Sketch new code so that after a reboot the new setpoint will still be there?
Opie
Isaac

If you store the set points in EEPROM on the Nano, the Pi can send adjustments over serial and indicate whether they're to be temporary or permanent.

Wildbill,

May I get your blessing on the following link as a place to start my study of EEPROM?

https://roboticsbackend.com/how-to-save-values-on-arduino-with-eeprom/

Personally, I would start with the Arduino reference.

One thing to beware of is that the EEPROM has a finite life - you can only write on each byte ~100K times before it starts to degrade. This shouldn't matter for the purpose of changing set points, but you need to be very certain that your code isn't doing unnecessary writes - a bug can fry the EEPROM very rapidly.

I wanted to document the session I had today in person with the "landlord" and future operator of the system I am sending to the west coast for July or August installation. I demonstrated how the setpoints are changed by editing the "sketch" for the Arduino and then reloading the Arduino. I had done it many, many times, and it seemed a breeze for me. Needless to say, the future operator's eyes glazed over and it seemed hopeless to her. I will try to both tighten the code and develop a GUI that uses EEPROM if I want this project to succeed. I am soooo resistant to going with a commercial HA type GUI overlay. Maybe I will be converted to HA thinking but I hope not. More soon.