Help with relevant Ethernet tutorials or sample code

Hi All

I am building a new project that will control a heat transfer system in my house, i know there are heaps out there already. I have made a number of basic projects before but this one is a bit more complex as it is all separated by distance and not in a nice box so I am looking for a steer in the right direction.

The system has two rooms down stairs and two rooms up stairs and the inline multi speed fan in the attic. Each room will have its own damper to control the air flow and the goal is each room will have a controller as well as an air temperature sensor for the room. The master controller will be in the lounge where the inlet grille is located as well as a temperature sensor. Each location has a dedicated Cat6 running from where the room controller will be to the attic where the fan is. (each 24v damper is wired back there as well)

I have developed some basic code (not complete but works) on a single Uno with 4 relays to control the 4 dampers and 2 relays to control fan speed (no sensors yet). I now need to investigate controlling everything remotely over ethernet. I have searched google, the tutorials and posts and they all lead to running webservers, over the web, or controlling things with PC's etc.

At this stage I would like to just have an LCD with 4 buttons (Up, dn . left, right), this will eventually be a touch screen, connected to a uno with a i2c ethernet module that communicates to another uno in the attic that controls all of the relays for the dampers and fan. That's step one. Step two will then get data (temp) from each room as well as requests "I want warm air" the main control will determine if the air in lounge is actually warm enough to send to the room. (it will also work as cooling)

Can anyone point me in the direction of some tutorials or sample code that will allow me to instead of making, for example, pin 4 HIGH or LOW on the local uno actually make pin 4 HIGH or LOW on a remote uno via ethernet (UPD, TCIP??) that will solve step one. Step two can wait.

For what its worth my code is below, but its not overly relative as it only works locally on an a single board but serves as an example of what I am trying to do, it was taken from anther project I did to make a spa pool controller.

Cheers

Al

//
//  Version   YY/MM/DD     Comments
//  V1        20260424     beginning - Adapted from Spa Controller
//  V1.1      20260508     bug fixes and timing delays fixed

//********************************************************************************
//Setup for blink of life
const byte heartbeatLED        = 13; // blinks to indicate not blocking LED 13 is onboard - change to output if external LED
unsigned long currentHB_Millis;      // stores the current time in loop
unsigned long heartbeatMillis;       // resets in HeartBeat function

unsigned long currentPOL_Millis;         // stores the current time in loop
unsigned long ProofofLifeMillis;       // resets in HeartBeat function

//Setup Libaries
#include <JC_Button.h>                // https://github.com/JChristensen/JC_Button
#include <LiquidCrystal_I2C.h>        // Include LiquidCrystal_I2C library 
LiquidCrystal_I2C lcd(0x27, 16, 2);
#include "Wire.h"

//{ // Variables
#define LEDon                HIGH       //pin---[220R]----[>|]---GND
#define LEDoff               LOW

#define isPUSHED             LOW        //INPUT_PULLUP---pin---[N.O. switch]---GND
#define isRELEASED           HIGH

#define enabled              true
#define disabled             false

#define backlightON          HIGH
#define backlightOFF         LOW

#define turnON               HIGH       //NO on relay 
#define turnOFF              LOW        //NC on Relay 

#define OpenDamper           HIGH       //NO on relay
#define CloseDamper          LOW        //NC on relay

//                                  1111111
//                         1234567890123456  
#define welcomeText       "Air Handler V1.0"    // change this to display custom message
#define FIRST_LINE 0 //text position for first line
#define SECOND_LINE 1 //text position for second line


//ANALOGS

//INPUTS
const byte Button1             = 6;  // Left
const byte Button2             = 7;  // Up
const byte Button3             = 8;  // Down
const byte Button4             = 9;  // Right

//    Button function per mode      Idle             Setruntime     Running         Setup
Button btnLEFT(Button1);    //     Damper selc neg         exit      Damper sel     exit
//Button 1 long press               Setrun time            nul                      nul
Button btnUP(Button2);      //     Damper open         time up                    val up
Button btnDOWN(Button3);    //     Damper close        time dn                    val dn
Button btnRIGHT(Button4);   //     Damp Select pos      Confirm                   Confirm
// Button 4 long press      //     Setup                 nul                       nul


//OUTPUTS
const byte Damper1Relay        = 2;  // Rotates damper Gamesroom
const byte Damper2Relay        = 3;  // Rotates damper Spare bedroom
const byte Damper3Relay        = 4;  // Rotates damper Office 
const byte Damper4Relay        = 5;  // Rotates damper Master bedroom
const byte FanLowRelay         = 10;  // runs fan at lower speed
const byte FanHighRelay        = 11;  // Runs fan at higher spped

//Flags
//bool Backlightflag          = false;     // could move local - don't know what this did
bool CheckSettingsFlag        = true;

bool GamesroomDamperFlag      =false;
bool Guest_BedroomDamperFlag  =false;
bool Office_DamperFlag        =false;
bool Master_BedroomDamperFlag =false;
bool FanLowFlag               =false;
bool FanHighFlag              =false;

//timing variables to store begin times
unsigned long currentMillis;   //stores current millis
unsigned long previousMillis;  //stores previous millis

unsigned long SensorMillis;          // could move local to loop
unsigned long readLcdMillis;         // Time to Read LCD displays
unsigned long powerUpMillis;         // could move local Delay time for startup
unsigned long backlightMillis;       // Stores time for backlight timeout
unsigned long damperMillis;          // Stores time for damper movement

//timers for delays
const unsigned long readLcdDelay       = 3  * 1000ul;
const unsigned long powerUpDelay       = 5  * 1000ul;
const unsigned long settingsDelay      = 20 * 1000ul;
const unsigned long timeoutDelay       = 20 * 1000ul;
const unsigned long backlightDelay     = 60 * 1000ul;
const unsigned long backlightTimeOut   = 40 * 1000ul; // was in typedef struct in spa control
const unsigned long damperDelay        = 10 * 1000ul; // reduced for testing

//Runs the menu system
byte menuList = 1; // List of menu items 1-9 in a group
byte menuGroup = 0; //Group of menus 0 being top layer
byte menuItems; // definds how many menuitems in menulist
byte PreviousmenuGroup; //defines last menu group for menu depth

//Main States MainStateMachine(); Called from void loop                                                       
//                0         1                    2              3        4            5             6             7         8         9        10
enum MSTATES {m_StartUp, m_CheckSettings, m_ConfirmSettings, m_Idle, m_SetDampers, m_CheckSys, m_MoveDampers, m_RunUp, m_Running, m_Shutdown, m_Settings}; 

MSTATES mState = m_StartUp;                                                                                      
MSTATES lastMstate;

//********************************************************************************
void setup() {
  // put your setup code here, to run once:

Serial.begin(9600);
pinMode(heartbeatLED,  OUTPUT);

  //************************************
  //LCD is 16 columns by 2 rows
  lcd.init();
  lcd.backlight();     // Backlight on

  //Relay outputs
  pinMode(Damper1Relay, OUTPUT);
  pinMode(Damper2Relay, OUTPUT);
  pinMode(Damper3Relay, OUTPUT);
  pinMode(Damper4Relay, OUTPUT);
  pinMode(FanLowRelay,  OUTPUT);
  pinMode(FanHighRelay, OUTPUT);


  //Switch inputs
 btnLEFT.begin();
 btnRIGHT.begin();
 btnUP.begin();
 btnDOWN.begin();


}

//********************************************************************************
void loop() {

//*************************
// Run Blink of life every loop
BlinkofLife();
//*************************
ProofofLife();
 currentHB_Millis = millis(); // capture arduino run time just for blink of life
 currentMillis = millis(); //Set current millis for various functions

  //***************************************
  // Read button inputs

  btnLEFT.read();
  btnRIGHT.read();
  btnUP.read();
  btnDOWN.read();

    //****************************
  // check sensors once per second
/*
  if (mState == m_StartUp) // check the sensors every loop to stabilise values
  {
    Sensors();
  }
  else if (currentMillis - SensorMillis > 10 * 1000ul)
  {
    //restart the TIMER
    SensorMillis = currentMillis;
    Sensors();
  }
*/

BackLightControl(); //sets backlight for LCD
  
  //Check Statemachine
MainStateMachine();

}
//********************************************************************************
//END of LOOP
//********************************************************************************



//**********************************************
//FUNCTIONS
//**********************************************

// Standard Blink of life to be in all programs

void BlinkofLife() // basic function to blink LED to show code is not blocked
{

  currentHB_Millis = millis();

if (currentHB_Millis - heartbeatMillis >= 800)
  {
    //restart the TIMER
    heartbeatMillis = currentHB_Millis;

    //Toggle heartbeat LED
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

}

void ProofofLife() // basic function serial print for debugging comment out in final vervion
{

  currentPOL_Millis = millis();

if (currentPOL_Millis - ProofofLifeMillis >= 2000)
  {
    //restart the TIMER
    ProofofLifeMillis = currentHB_Millis;
    Serial.println(mState);
    Serial.println(FanLowFlag);
    Serial.println(FanHighFlag);
    Serial.println(GamesroomDamperFlag);
    Serial.println(Guest_BedroomDamperFlag);
    Serial.println(Office_DamperFlag);
    Serial.println(Master_BedroomDamperFlag);


  }

}
//********************************************************************************
// Welcome message at startup - set message in Define statements

void welcomeMessage()
{
  //            C  R
  lcd.setCursor(0, 0);
  //                   111111
  //         0123456789012345
  lcd.print(welcomeText);   // set in #define statements
} //END of welcomeMessage()



//timeout function
void isTimeout() 
{
  if (currentMillis >= previousMillis + timeoutDelay) {
    lcd.clear();
   mState    = lastMstate;
   menuList = 1;
   menuGroup   = 0;
  }// end if

  if (btnLEFT.isPressed() || btnUP.isPressed() || btnDOWN.isPressed() || btnRIGHT.isPressed())
  {
    previousMillis = currentMillis; // reset the time out if a button is pushed
  }
}// end timeout

void BackLightControl()
{
  if (currentMillis - backlightMillis > backlightTimeOut) //this had a reference to ContSettings in spa control
  {
    //restart the TIMER
    backlightMillis = currentMillis;
    lcd.setBacklight(backlightOFF);
  //  Backlightflag = false;
  }
  else if (btnLEFT.isPressed() || btnUP.isPressed() || btnDOWN.isPressed() || btnRIGHT.isPressed())
  {
    lcd.backlight();
    backlightMillis = currentMillis; 
    previousMillis = currentMillis; // reset the time out if a button is pushed
  //  Backlightflag = true;
  }
}


//*******************************************************************************
//                     M A I N  s t a t e M a c h i n e ( )
//********************************************************************************
void MainStateMachine()
{
  switch (mState)
  {
    //********************* 0
    case m_StartUp:
      {
     
        if (powerUpMillis > powerUpDelay)
        {
          //do the following once after power up delay
          lcd.backlight();
          lcd.clear();

          //RTC.read(tm);

          //LCD_Clock_Function();         // check RTC is working

          readLcdMillis = currentMillis;
          mState = m_CheckSettings;       // Progress to next state
        }
        else
        (powerUpMillis = currentMillis);
        welcomeMessage();            // Show Welcome Message until power up delay reached


      } //END case
      break;
    //********************1
    case m_CheckSettings:
      {

        if (currentMillis >= readLcdMillis + readLcdDelay) // show date and time on LCD until delay reached
        {
            lcd.clear();
            mState = m_ConfirmSettings;
        }
        else
        {
           readLcdMillis = currentMillis;
        //  Show_Time(FIRST_LINE);
        //  Show_Date(SECOND_LINE);
        //   welcomeMessage(); // Show Welcome Message - place holder until settings functions added
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(F("waiting"));
        }

      }
      break;
     //**********************2
    case m_ConfirmSettings:
      {

      //  CheckSettingsFlag = ConfirmTimeDate(); // returns true if accepted or false if edit required.

        if (CheckSettingsFlag == true) // currently fixed in flags as true, needs to be changed to false
        {
          lcd.clear();
          mState = m_Idle;   // only progress once Check Settings Flag is true. 
        }
      }
      break;   
 //*********************3
    case m_Idle:
      {
        lcd.setCursor(0, 0);
        lcd.print(F("Idle"));
        lcd.setCursor(0, 1);
        lcd.print(F("Select Room"));
 
         //Buttons
        buttoninputs();// this moves to to set dampers

      lastMstate = m_Idle; // ant timeout will lead here

      } //END case
      break;  
  //*********************4   
    case m_SetDampers:
      {
        isTimeout();     // times out to lastMstate
        menuButtons();   // calls buttons to navigate menu 
        MenuFunctions(); // calls the main menu

      }
      break;
//*********************5
    case m_CheckSys:
      {
        Serial.println("check sys");
// first if tests if all dampers now set to close but fan is still running then move to shutdown proceedure to turn off fan and close dampers
    if(GamesroomDamperFlag == false && Guest_BedroomDamperFlag  == false && Office_DamperFlag == false && Master_BedroomDamperFlag == false && FanLowFlag == true){ 
    FanLowFlag = false;
    FanHighFlag = false;
    damperMillis = currentMillis;
    lcd.clear();
    mState =m_Shutdown;}

// Test if either gamesroom or guest bedroom has been set to open and the fan is already running then increase fan speed
    else if ((GamesroomDamperFlag == true || Guest_BedroomDamperFlag == true) && FanLowFlag == true){
      FanLowFlag = true;
      FanHighFlag = true;
      lcd.clear();
      damperMillis = currentMillis;
      mState = m_MoveDampers;
    }
// Test if Office or master beedroom has been opened while the other is running then increase fan speed
    else if (Office_DamperFlag == true && Master_BedroomDamperFlag == true && FanLowFlag == true){
      FanLowFlag = true;
      FanHighFlag = true;
      lcd.clear();
      damperMillis = currentMillis;
      mState = m_MoveDampers;
    }
// remaining option is a either office or masterbedroom is now running by itself and to decrease fan to low
    else if (FanLowFlag == true && (Office_DamperFlag == true || Master_BedroomDamperFlag == true)){
    FanHighFlag = false;
    lcd.clear();
    damperMillis = currentMillis;
    mState = m_MoveDampers;
    }
//If the program enters the system check and the fan is not going then proceed to startup to open first damper    
    else if (FanLowFlag == false){
      FanLowFlag = true; // fanlow must always be running
      lcd.clear();
      damperMillis = currentMillis;
      mState = m_RunUp;
    }

      } //END case
      break;  
       //*********************6
    case m_RunUp:
    {

      lcd.setCursor(0,0);
      lcd.print (F("STARTUP")); 

      Serial.println("runup");
      //start opens the first damper that is true as only one can be set at a time the first one will tigger startup
      if(GamesroomDamperFlag == true){ 
            digitalWrite (Damper1Relay, OpenDamper);
            FanHighFlag = true;
            } // Closes damper Gamesroom
      
      if(Guest_BedroomDamperFlag == true){ 
            digitalWrite (Damper2Relay, OpenDamper);
            FanHighFlag = true;
            }  // Closes damper Spare bedroom
      
      if(Office_DamperFlag == true){ 
            digitalWrite (Damper3Relay, OpenDamper);
            } // Closes damper Office 
      
      if(Master_BedroomDamperFlag == true){ 
            digitalWrite (Damper4Relay, OpenDamper);
            } //Closes damper Master bedroom

    //  if(GamesroomDamperFlag == TRUE || Guest_bedroomDamperFlag == TRUE){
    //  FanHighFlag = True;
    //  }
      
     // if (Office_DamperFlag == True && Master_BedroomDamperFlag == TRUE){
     // FanHighFlag = True;  
     // }

      if (currentMillis >= damperMillis + damperDelay) { // delays while damper opens before going to running


          if(FanLowFlag == true){
            digitalWrite(FanLowRelay, turnON);
          }
          if(FanHighFlag == true){
            digitalWrite(FanHighRelay, turnON);
          }
        lcd.clear();  
        mState    = m_Running;
        menuList  = 1;
        menuGroup = 0;
      }// end if
   
      //damperMillis = currentMillis;

    }
    break;
 //*********************7
    case m_MoveDampers:
      {
  // this state only entered if the fan and one damper is already open, test each damper to find any that are to open or close and then set

     if ((digitalRead(Damper1Relay == CloseDamper) && GamesroomDamperFlag == true)){
      digitalWrite(Damper1Relay, OpenDamper);
     }
     else {
      digitalWrite(Damper1Relay, CloseDamper);
     } 
     if ((digitalRead(Damper2Relay == CloseDamper) && Guest_BedroomDamperFlag == true)){
      digitalWrite(Damper2Relay, OpenDamper);
     }
     else {
      digitalWrite(Damper2Relay, CloseDamper);
     }
     if ((digitalRead(Damper3Relay == CloseDamper) && Office_DamperFlag == true)){
      digitalWrite(Damper3Relay, OpenDamper);
     }
     else {
      digitalWrite(Damper3Relay, CloseDamper);
     }
       if ((digitalRead(Damper4Relay == CloseDamper) && Master_BedroomDamperFlag == true)){
      digitalWrite(Damper4Relay, OpenDamper);
       }
     else {
      digitalWrite(Damper4Relay, CloseDamper);
     }
        if ((digitalRead(Damper1Relay == CloseDamper) && GamesroomDamperFlag == true)){
      digitalWrite(Damper1Relay, OpenDamper);
        }
     else {
      digitalWrite(Damper1Relay, CloseDamper);  
     }
     if (currentMillis >= damperMillis + damperDelay) { // delays while damper opens to prevent damper being changed while moving
      lcd.clear();
      mState    = m_Running;
      menuList  = 1;
      menuGroup = 0;
      }// end if
     
      damperMillis = currentMillis; 


      } //END case
      break;  
      
//***********************8
case m_Running:
{
        lcd.setCursor(0, 0);
        lcd.print(F("Running"));

        if(FanHighFlag == true && FanHighFlag == true){
          lcd.setCursor(0,1);
          lcd.print(F("fan high"));
        }
        else if (FanHighFlag == false){
          lcd.setCursor(0,1);
          lcd.print(F("fan low"));
        }
         //Button
        buttoninputs();  //moves to set dampers

     if(FanHighFlag == true){
      digitalWrite(FanHighRelay, turnON);
       }
      else digitalWrite(FanHighRelay, turnOFF);
}
break;
 //*********************9  
    case m_Shutdown:
      {
      lcd.setCursor(0, 0);
      lcd.print (F("SHUTDOWN")); 
      digitalWrite (FanHighRelay, turnOFF);
      digitalWrite (FanLowRelay,  turnOFF);
      FanLowFlag   =false; 
      FanHighFlag  =false;
      digitalWrite (Damper1Relay, CloseDamper); // Closes damper Gamesroom
      digitalWrite (Damper2Relay, CloseDamper); // Closes damper Spare bedroom
      digitalWrite (Damper3Relay, CloseDamper); // Closes damper Office 
      digitalWrite (Damper4Relay, CloseDamper); //Closes damper Master bedroom

      if (currentMillis >= damperMillis + damperDelay) { // delays while damper closes before going to idle
      lcd.clear();
      mState      = m_Idle;
      menuList    = 1;
      menuGroup   = 0;
      }// end if
  
      lcd.setCursor(0, 1)
      lcd.print(f("shutting Down"));    
         //shutdown function leds to idle state;

      } //END case
      break; 
 //*********************10
    case m_Settings:
      {
        isTimeout();    //times out to previous state eith idle or run
        lcd.setCursor(0, 0);
        lcd.print(F("Settings"));

        //Buttons
        buttoninputs();
 


      } //END case
      break; 
   }     

}

void buttoninputs() //used in main state machine
{
//****************************************************  
if(btnLEFT.wasPressed()){
   backlightMillis = currentMillis;
        if(mState == m_Idle ){
                lcd.clear();
                 mState = m_SetDampers;
              }
        else if(mState == m_SetDampers){
                 lcd.clear();
                mState = lastMstate; //cancels out of set dampers
              }
        else if(mState == m_Running){
                    // If timer expires users can add 20 min to spa time
                lcd.clear();
                mState = m_SetDampers;
              }

              
   }// end Left Button was pressed
//**************************************************************************
/*
if(btnLEFT.pressedFor(1000)){
   backlightMillis = currentMillis;
        if(mState == m_InUse){
                 ExitTimeMillis = currentMillis;  // start the exit delay timer
                 lcd.clear();
                 mState = m_ExitDelay;
              }
             
   }// end if Left Button Pressed for 1000ms
   
*/

//R I G H T  B U T T O N **************************************************************************

if (btnRIGHT.wasPressed()){
   backlightMillis = currentMillis;
        if(mState == m_Idle || mState == m_Running){
                lcd.clear();
                 mState = m_SetDampers;
        } 

              
} //end if button right was pressed

/*
//**************************************************************************
if(btnRIGHT.pressedFor(1000)){
   backlightMillis = currentMillis;
        if(mState == m_Idle || mState == m_Run){
                 lcd.clear();
                 mState = m_Settings;
              }
        else if(mState == m_Setflags){
                 lcd.clear();
                //call menu turn on set 
              }
             
   }// end if Left Button Pressed for 1000ms
*/
//U P  B U T T O N ****************************************************************************

if (btnUP.wasPressed()){
        if(mState == m_Idle || mState == m_Running){
                 lcd.clear();
                 mState == m_SetDampers;
               
              }
} //end if button up was pressed

//D O W N  B U T T O N ***************************************************************************

if (btnDOWN.wasPressed()){
   backlightMillis = currentMillis;

        if(mState == m_Idle || mState == m_Running){
                 lcd.clear();
                 mState == m_SetDampers;

               
              }
}



} //End Button Input
//*************************************************


void menuButtons()
{
  if (btnUP.wasPressed()){
    Serial.println("button up");
    lcd.clear();
    if (menuList < menuItems) menuList++; //0 being the base menu
  }

  if (btnDOWN.wasPressed()){
    Serial.println("button dn");
    lcd.clear();
    if (menuList > 1) menuList--;
  }

  if (btnRIGHT.wasPressed()){
    Serial.println("button right");
    if(menuGroup == 0){
      lcd.clear();
      menuGroup = menuList * 10; //moves to 10 20 30 40
      menuList = 1;
    }
    else if (menuGroup != 0){
      lcd.clear();
      menuGroup = menuGroup + menuList; //moves to 11 12 13 21 22 22 23 
      menuList = 1;
    }
  }

  if(btnLEFT.wasPressed()){

    if (menuGroup == 0){
      lcd.clear();
      menuGroup = 0;// add in here that if menuList is equal of less than 5 and menuGroup 0 then set dampers off by going to menu case 1 2 3 . otherwise exit turning on would be 10 11 12
      menuList = 1;
      mState = lastMstate; //exits out
    }
    else if (menuGroup = PreviousmenuGroup){ //if we are in a sub menu move back
      menuGroup = 0;
      menuList = 1;
    }
    else
    {
     menuGroup = PreviousmenuGroup; // this needs checking
     menuList = 1;
    }
  }
  
}

//bool GamesroomDamperFlag      =false;
//bool Guest_BedroomDamperFlag  =false;
//bool Office_DamperFlag        =false;
//bool Master_BedroomDamperFlag =false;

void MenuFunctions(){

isTimeout();      // times out to lastMstate

switch (menuGroup){
  case 0:
  {
    //show main menu 1-9
    PreviousmenuGroup = 0;
    MainMenu();
  }
  break;
    case 10:
  {

     Master_BedroomDamperFlag = !Master_BedroomDamperFlag;
     lcd.clear();
     menuGroup = 0;
     menuList = 1;
     mState = m_CheckSys;   
   
  }
  break;
      case 20:
  {
     Office_DamperFlag = !Office_DamperFlag;
     lcd.clear();
     menuGroup = 0;
     menuList = 1;
     mState = m_CheckSys;   
  }
  break;
      case 30:
  {
     Guest_BedroomDamperFlag  = !Guest_BedroomDamperFlag;
     lcd.clear();
     menuGroup = 0;
     menuList = 1;
     mState = m_CheckSys;   
  }
  break;
      case 40:
  {

     GamesroomDamperFlag = !GamesroomDamperFlag;
     lcd.clear();
     menuGroup = 0;
     menuList = 1;
     mState = m_CheckSys;   
  }
  break;
  case 50:
  {
    GamesroomDamperFlag       =false;  
    Guest_BedroomDamperFlag   =false;  
    Office_DamperFlag         =false; 
    Master_BedroomDamperFlag  =false;
    mState = m_Shutdown;
  }
  break;
  case 60:
  {
    // show Submenu 2 items
    PreviousmenuGroup = 20;
    Submenu2(); // select comfort times to edit
  }
  break;
  case 70:
  {
    Submenu3();
  }
  case 71:
  {
   // mState = m_EditDateTime;
   // tState = EditTime;
    menuGroup = 0;
    menuList = 1;
  }
  break;
 }
}

//bool GamesroomDamperFlag      =false;
//bool Guest_BedroomDamperFlag  =false;
//bool Office_DamperFlag        =false;
//bool Master_BedroomDamperFlag =false;
//bool FanLowFlag               =false;
//bool FanHighFlag              =false;

void MainMenu(){
  
  menuItems = 7;
  switch(menuList){
     case 1:
     {

//                          1111111
//                 1234567890123456 
    if(Master_BedroomDamperFlag == false){
      lcd.setCursor(0,0);
      lcd.print(F(" Master bedroom "));
      lcd.setCursor(0,1);
      lcd.print(F("           ON-> "));

    }
    else if (Master_BedroomDamperFlag == true){
      lcd.print(F(" Master bedroom "));
      lcd.setCursor(0,1);
      lcd.print(F("          OFF-> "));

    }
     }
     break;
     case 2:
     {
     
    if(Office_DamperFlag == false){
      lcd.setCursor(0,0);
//                          1111111
//                 1234567890123456  
      lcd.print(F("     Office     "));
      lcd.setCursor(0,1);
      lcd.print(F("           ON-> "));

    }
    else if (Office_DamperFlag == true){
      lcd.print(F("     Office    "));
      lcd.setCursor(0,1);
      lcd.print(F("          OFF-> "));
       }
     }
     break;
     case 3:
     {
    if(Guest_BedroomDamperFlag == false){
      lcd.setCursor(0,0);
//                          1111111
//                 1234567890123456        
      lcd.print(F(" Guest Bedroom  "));
      lcd.setCursor(0,1);
      lcd.print(F("           ON-> "));

    }
    else if (Guest_BedroomDamperFlag == true){
      lcd.print(F(" Guest Bedroom   "));
      lcd.setCursor(0,1);
      lcd.print(F("          OFF-> "));
     }
     }
     break;
     case 4:
     {
    if(GamesroomDamperFlag == false){
      lcd.setCursor(0,0);
//                          1111111
//                 1234567890123456  
      lcd.print(F("   Games Room   "));
      lcd.setCursor(0,1);
      lcd.print(F("           ON-> "));

    }
    else if (GamesroomDamperFlag == true){
      lcd.print(F("   Games Room   "));
      lcd.setCursor(0,1);
      lcd.print(F("          OFF-> "));
     }
     }
     break;
     case 5:
     {
     lcd.setCursor(0,0);
//                          1111111
//                 1234567890123456 
      lcd.print(F("    All Off     "));
      lcd.setCursor(0,1);
      lcd.print(F("          Yes-> "));
     }
     break;
     case 6:
     {
     lcd.setCursor(0,0);
//                          1111111
//                 1234567890123456 
      lcd.print(F(" Set Run Time   "));
      lcd.setCursor(0,1);
      lcd.print(F("         Enter->"));
     }
     break;
     case 7:
     {
     lcd.setCursor(0,0);
//                          1111111
//                 1234567890123456 
      lcd.print(F("    Setting     "));
      lcd.setCursor(0,1);
      lcd.print(F("         Enter->"));
     }
     break;
  }
}

void Submenu2(){

  menuItems = 1;
  
   switch (menuList){    
     case 1:
     {
     lcd.setCursor(0,0); 
     lcd.print(F("Set Timer")); 
     }
     break;
}
}

void Submenu3(){

  menuItems = 1;
  
   switch (menuList){    
     case 1:
     {
     lcd.setCursor(0,0); 
     lcd.print(F("Edit Time / Date")); 
     }
     break;
}
}


//******************************************************************************************************************
//End Program
//*******************************************************************************************************************
//

recently been evaluating a ESP32 4-Channel Relay Module with WiFi/Bluetooth IoT Development Board controlling the relays using code from ESP32 Relay Module – Control AC Appliances (Web Server)

may give you some ideas for your ethernet based control

also found ESP32-S3 ETH Development Board useful when building Ethernet systems as it support POE

Do you want to be able to control it remotely over the internet, for example from your mobile phone? Or just locally in the building?

Read through this, it explains with example code how to use TCP to do the kind of thing you want to do. The examples assume WiFi as the means of connection, but that should not matter, just use Ethernet instead:

If you want to understand UDP there are examples in the IDE for both using UDP to get the time from a network time server and for using it for general communication.

For TCP you get error correction and guaranteed delivery at the expense of delivery time and the need to maintain a connection.

For UDP you get speed at the expense of getting errors, lost and duplicated packets.

I asked about whether you want access across the internet because doing so complicates things. If you only want local access and you have wires in place then why do you want to use UDP / TCP rather than something simple such as connecting some buttons to the wires or using serial or something like RS485?

simple example of UNO/Mega using a webpage to control LED

// UNO/Mega Ethernet webpage LED control using Ethernet Sheild V1

// from following with minor EDITs
//     https://projecthub.arduino.cc/tylerpeppy/ethernet-controlled-led-727ad1

#include <SPI.h>
#include <Ethernet.h>

// MAC address for shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
EthernetServer server(80);  //  Using port 80
int led = 13;               // LED attached to pin 7
void setup() {
  pinMode(led, OUTPUT);  // Led set as an output
  for (int i = 0; i < 10; i++) {
    digitalWrite(led, HIGH);
    delay(100);
    digitalWrite(led, LOW);
    delay(100);
  }
  Ethernet.begin(mac);  // Start the Ethernet  shield
  server.begin();
  Serial.begin(115200);               // Start serial communication
  Serial.println("Server address:");  // Print server address
  // (Arduino  shield)
  Serial.println(Ethernet.localIP());
}
void loop() {
  EthernetClient client = server.available();
  if (client) {
    boolean currentLineIsBlank = true;
    String buffer = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();  // Read from the Ethernet  shield
        buffer += c;             // Add character to string buffer
        // Client  sent request, now waiting for response
        if (c == '\n' && currentLineIsBlank) {
          client.println("HTTP/1.1 200 OK");  // HTTP response
          client.println("Content-Type:  text/html");
          client.println();  // HTML code
          client.print("<center><br><h1>Internet  Controlled Led </h1><br><br><br><FORM>");
          // EDIT: \\ on following lines changed to \     :
          client.print("<P> <INPUT  type=\"submit\" name=\"status\" value=\"ON\">");
          client.print("<P>  <INPUT type=\"submit\" name=\"status\" value=\"OFF\">");
          client.print("</FORM></center>");
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
          buffer = "";
        } else if (c == '\r') {  // Command from webpage
          //Serial.print("command from webpage ");  // for debugging
          //Serial.println(buffer);
          // Did the on button get pressed
          if (buffer.indexOf("GET /?status=ON") >= 0) {
            digitalWrite(led, HIGH);
            Serial.println("command from webpage ON");
          }
          // Did the off button get pressed
          if (buffer.indexOf("GET /?status=OFF") >= 0) {  // EDIT: removed extra space after GET
            digitalWrite(led, LOW);
            Serial.println("command from webpage OFF");
          }
        } else {
          currentLineIsBlank = false;
        }
      }
    }
    client.stop();  // End server
  }
}

serial monitor displays

Server address:
192.168.1.131
command from webpage OFF
command from webpage ON
command from webpage ON
command from webpage OFF
command from webpage ON
command from webpage OFF

web client displays

what Ethernet controller are you using?

Then you just want a simple peer-to-peer network using UDP.
See the UDPSendReceiveString.ino example.
You will need a crossover cable to connect the two Arduinos.

Be careful when using the shield on your Cat6 cable. Ground the shield ONLY on the master location. Never ground it at the other locations. Your house wiring uses both sides of the 220 VAC power to your house. 120 volts plus common/ground. If there is any difference in voltage at the common/ground between locations, you will have AC current flowing on your ground connections. Ethernet is transformer coupled, so there is no ground with ethernet and it avoids the problem.

Thanks Paul. I'm in New Zealand so our 230v wiring standards are slightly different but I take your point.

Thanks Jim

If I go down the UDP route will that limit me in the future when I eventually have 4 room controllers and a master controller plus the attic controller all talking?

Cheers

Al

Hi Horace

I am using the W5500 Ethernet Network Modules For Arduino TCP/IP 51/STM32 SPI Interface 3.3V 5V I/O MCU

Thanks for the sample code. I am not looking to control the system via a webpage. I want the system to be an on the wall controller dedicated controller. However over project I am thinking about will be via web so this will help a lot. Cheers

I only want to control it locally. End goal it to have a something like a 5inch touch display in the lounge that shows the whole system and maybe 3inch displays in the rooms. Everything controlled by those. But this is wee way down the track, need to be able to walk before I can run.

Thanks for the link. I will go through that information.

As for using ethernet. The furtherest controller would be around 22-25m from the attic. Just from what I read I understood this would be too far and really limited me to some form of ethernet connection, but happy to explore other options if they are better. Second reason, if it all works then potentially I can change the code to be wifi based at a later date and other people that do not have cables prewired in their house can use it.

used the W5500 on ESP32 and RP2040 microcontrollers but not the UNO
have a look at arduino-ethernet-module tutorial
make sure your W5500 module is compatible with 5V logic if using with a UNO

sounds like it may be suitable for a mesh based wireless solution

I certainly hope it works with 5v given it says 3.3v 5v in description. They arrive tomorrow

Thanks for the tutorials I will look into those

Edit: that tutorial is more about sending information to the web. It seems a lot of tutorials are based on web servers and controlling via web pages rather then connecting devices.

I will stick with hardwired Ethernet at this stage as the infrastructure is already there (and paid for) but maybe in future mesh wifi could be an option.

Cheers

Al

What do you think people used before Ethernet was an option? Ethernet is good for 100m, other ways of working are good for far longer distances up to many kilometres or more. 25m is fine for either some buttons on the end of the cable or serial of some kind. I2C is not designed for 25m, indeed there are people here who will tell you it won't work over 25m, I have it working just fine over about that distance, although I did have to do some work to make it reliable.

I don't want to discourage you too much from using Ethernet, but don't use it just because you think nothing else will work, there are other options. For 'better' you have to define what better means for what you are doing. No method is 'better' than any other, just more or less suitable for what you are doing. I learnt a great deal about WiFi using it to collect weather data, now I use a wired distribution system based on serial data because wires are more reliable than WiFi. Whatever you use you will learn loads, which for this hobby can only be a good thing.

I think your options are:

  • Ethernet as you are considering
  • WiFi, which is very similar to using Ethernet but with the added advantages and disadvantages of using a radio based link
  • Simple signalling using buttons and other simple devices over the cables you have
  • Point to point serial data
  • Point to multipoint serial data (RS485)

With apologies for replying to the question you asked @jim-p .
UDP and TCP are the same in terms of applicability on your local network, or over the internet. Either will work in your situation for point to point, point to multipoint and multipoint to multipoint in any combination you can think of. They differ in terms of what you need to do to keep them reliable. My own experience is UDP is easier but you have to deal with the lost or duplicated packets and with the errors. How you deal with them depends on how much they matter for what you are doing. If you want any kind of multipoint using Ethernet you will need at least a switch and possibly a router.

you should be OK then

have a look at Ethernet_W5500 UDPSendReceiveString
although it communicates using text string you can adapt it to any type of data - I tend to communicates using binary structures
you will probably have to work out some protocol for communication between the devices - probably get the master to poll the client devices

No but you you will need a router and a lot of cables.

Thank you Perry and everyone else for your help.

to answer your question, what do I think people use before the internet, My Father, being an electrician, had me running all sorts of cables, from 2p or 4p security, cat3/4/5/5e (who would ever need the speed of 5e we would say), twin fire, pre made DMX lighting and even fibre optic in the 80's through to the 2000's. However at this time I had no idea what made the endpoints work or what language or protocols they used. I have come late to the party of programming and electronics.

As for my small project, from the help and advice so far, I think I will explore ethernet connections using TCP/IP starting with the hardware I have (Uno) but then getting better hardware once I have my head around things, UDP may work for transmitting sensor data (temp) but there is other information I want to transfer that I don't want lost, from there I can also dabble in Wifi.
However I also will look into RS428 having just read up on this, I never thought of this being an option and that's why you ask the question from those that know a lot more than you.
The buttons connected to wires may work, but its not the style of project I am after and pushing the limits of I2C is probably a recipe for a lot of frustration at my level.

Thank you everyone and I'm sure you'll see my name pop up in other parts of the forum when get stuck.

Cheers

Al

Hi All

I am at a loss. I am trying to learn how to connect multiple Arduino's via ethernet (not internet or webpages, just send instructions via tcp/ip over ethernet/lan between them). My project is a ducted air handler with 4 relays that control dampers to various rooms, I want to make provision for 10 relays. I have big ambitions of having touch screen controllers in each room with temp sensors that then feed back to a master controller on the wall in the lounge. Data to control the relays in the attic would be by a Struct sent over ethernet. (later will try wifi but sticking with ethernet now) so I have read and read and read how to do this and get I am going to have to learn quite a lot. The code all works on a single board but I am now trying to separate it out between the main controller and the controller in the attic just as step 1.

My baby step was just understanding the Ethernet library with assigning mac addresses and Ip addresses. I found a basic code that would create a webserver (even though I'm not using webpages for this) to setup and print on the serial monitor that it is connected and then a web page that says "hello from Arduion no 1" and "Hello from Arduino no 2" . I have two Uno's connected to a router and I have failed at step one which is depressing. One code works and the other does not for the exact same code. excuse the commeting out of various mac address I was trying to collect a few to use

Code for Uno 1

#include <Ethernet.h>

// Network configuration
byte mac[] = {0x4D, 0xA2, 0x76, 0x8A, 0xE3, 0xE8}; // MAC address board 2
//byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address
IPAddress ip(192, 168, 0, 178); // Static IP address board 2
EthernetServer server(80); // Port 80 for HTTP

/*
0x4D, 0xA2, 0x76, 0x8A, 0xE3, 0xE8
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
0xD4, 0x28, 0xB2, 0xFF, 0xA0, 0xA1
0x1E, 0xE8, 0x09, 0xE7, 0x56, 0xD8

f2:45:80:45:9f:2e
69:3d:bb:2b:2e:2f
55:f5:4b:97:3f:29
fa:0d:bc:88:38:a2
*/

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for Serial Monitor to open
  }

  Ethernet.begin(mac, ip);

  byte macBuffer[6];  // create a buffer to hold the MAC address
  Ethernet.MACAddress(macBuffer); // fill the buffer
  Serial.print("The MAC address is: ");
  for (byte octet = 0; octet < 6; octet++) {
    Serial.print(macBuffer[octet], HEX);
    if (octet < 5) {
      Serial.print('-');
    }
  }

  Serial.println("Initializing Ethernet...");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    Ethernet.begin(mac, ip); // Use static IP if DHCP fails

  }

  Serial.print("Ethernet IP Address: ");
  Serial.println(Ethernet.localIP());
  server.begin();
}

void loop() {
  EthernetClient client = server.available(); // Check for incoming clients

  if (client) {
    Serial.println("New client connected");
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c); // Print incoming data to Serial Monitor

        // Respond to HTTP GET requests
        if (c == '\n') {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<html><body><h1>Hello from Arduino no 1!</h1></body></html>");
          break;
        }
      }
    }
    client.stop(); // Disconnect the client
    Serial.println("Client disconnected");
  }
}

This produces the output
19:46:48.540 -> The MAC address is: 4D-A2-76-8A-E3-E8Initializing Ethernet...

19:47:48.762 -> Failed to configure Ethernet using DHCP

19:47:48.828 -> Ethernet IP Address: 192.168.0.178

This still works as the entering the IP into Chrome returns the web page

the second code which is identical except different mac and different IP

#include <Ethernet.h>

// Network configuration
byte mac[] = {0x4D, 0xA2, 0x76, 0x8A, 0xE3, 0xE7}; // MAC address board 2
//byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address
IPAddress ip(192, 168, 0, 170); // Static IP address board 2
EthernetServer server(80); // Port 80 for HTTP

/*
0x4D, 0xA2, 0x76, 0x8A, 0xE3, 0xE8
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
0xD4, 0x28, 0xB2, 0xFF, 0xA0, 0xA1
0x1E, 0xE8, 0x09, 0xE7, 0x56, 0xD8

f2:45:80:45:9f:2e
69:3d:bb:2b:2e:2f
55:f5:4b:97:3f:29
fa:0d:bc:88:38:a2
*/

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // Wait for Serial Monitor to open
  }

  Ethernet.begin(mac, ip);

  byte macBuffer[6];  // create a buffer to hold the MAC address
  Ethernet.MACAddress(macBuffer); // fill the buffer
  Serial.print("The MAC address is: ");
  for (byte octet = 0; octet < 6; octet++) {
    Serial.print(macBuffer[octet], HEX);
    if (octet < 5) {
      Serial.print('-');
    }
  }

  Serial.println("Initializing Ethernet...");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    Ethernet.begin(mac, ip); // Use static IP if DHCP fails

  }

  Serial.print("Ethernet IP Address: ");
  Serial.println(Ethernet.localIP());
  server.begin();
}

void loop() {
  EthernetClient client = server.available(); // Check for incoming clients

  if (client) {
    Serial.println("New client connected");
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c); // Print incoming data to Serial Monitor

        // Respond to HTTP GET requests
        if (c == '\n') {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<html><body><h1>Hello from Arduino no 2!</h1></body></html>");
          break;
        }
      }
    }
    client.stop(); // Disconnect the client
    Serial.println("Client disconnected");
  }
}

This produces the output
19:48:36.173 -> The MAC address is: 4D-A2-76-15-C7-CFInitializing Ethernet...

19:49:36.286 -> Failed to configure Ethernet using DHCP

19:49:36.338 -> Ethernet IP Address: 129.80.1.84

Why is it not working to set the Mac address and why is it failing to set the static IP address of 192.168.0.170 when the other one works fine. I don't understand why it is setting it as 129.80.1.84. i even tried a completely identical ethernet board and got the same result, they are all w5200 Wiznet

I am at a loss here.

I also do not know why the code is not using DHCP. DHCP o fthe router is working as it supplies an IP to my laptop. (192.168.0.11) dhcp is set to a range of 192.168.0.10 to 192.168.0.100.

Any pointers appreciated.

Cheers
Al

I don't have the board & shield to try it, but using Go to Definition on <Ethernet.h> opens the file, which says in part

public:
	// Initialise the Ethernet shield to use the provided MAC address and
	// gain the rest of the configuration through DHCP.
	// Returns 0 if the DHCP configuration failed, and 1 if it succeeded
	static int begin(uint8_t *mac, unsigned long timeout = 60000, unsigned long responseTimeout = 4000);
	static int maintain();
	static EthernetLinkStatus linkStatus();
	static EthernetHardwareStatus hardwareStatus();

	// Manual configuration
	static void begin(uint8_t *mac, IPAddress ip);

So try that first begin with just the MAC to try DHCP (instead of the manual config you tried). The second and third arguments have default values, so you can leave them out.