顯示具有 單晶片學習 標籤的文章。 顯示所有文章
顯示具有 單晶片學習 標籤的文章。 顯示所有文章

2023年12月27日 星期三

ESP32 3-axis controller

Purpose:
我利用手上沒在使用的三個滑軌和三個57步進馬達架構一個XYZ三軸方向的移動平台, 並使用ESP32作為3Axis controller控制移動方向. 有單軸指令, 例如 :X10  :Y10  :Z10跟三軸連動指令:P10,10,10

I used three unused slide rails and three 57 stepper motors to construct a mobile platform in the XYZ three-axis direction, and used ESP32 as the 3Axis controller to control the movement direction. There are single-axis instructions, for example: X10:Y10: Z10 and three-axis linkage command: P10,10,10

Architectures:  




Circuit:
Y axis set and Z axis set are the same method.

#define MACHINE_NAME            "ESP32_V4"

#define X_STEP_PIN              GPIO_NUM_12
#define X_DIRECTION_PIN         GPIO_NUM_14
#define Y_STEP_PIN              GPIO_NUM_26
#define Y_DIRECTION_PIN         GPIO_NUM_15
#define Z_STEP_PIN              GPIO_NUM_27
#define Z_DIRECTION_PIN         GPIO_NUM_33

#define X_LIMIT_PIN             GPIO_NUM_17
#define Y_LIMIT_PIN             GPIO_NUM_4
#define Z_LIMIT_PIN             GPIO_NUM_16


// OK to comment out to use pin for other features
#define STEPPERS_DISABLE_PIN    GPIO_NUM_13

#define SPINDLE_TYPE            SpindleType::PWM
#define SPINDLE_OUTPUT_PIN      GPIO_NUM_2   // labeled SpinPWM
#define SPINDLE_ENABLE_PIN      GPIO_NUM_22  // labeled SpinEnbl

#define COOLANT_MIST_PIN        GPIO_NUM_21  // labeled Mist
#define COOLANT_FLOOD_PIN       GPIO_NUM_25  // labeled Flood
#define PROBE_PIN               GPIO_NUM_32  // labeled Probe

Fundamental:

DM542 microstep driver
The DM542 is a new generation microstep stepper motor driver. Due to the adoption of the advanced bipolar constant-current chopper driver technology, it shows stable operation, provides excellent high torque. Moreover, it significantly reduces the noises and vibration of the operating motor. The DM542 has the feature of low-noise, low-vibration and low-heating. The DM542is DC18-50V power supply. It applies to 2-phase hybrid stepper motor under 4.2Acurrent The DM542 has 15 kinds of microsteps. The maximum step number is 25600 steps/rev (microstep is 1/128). The peak operating current ranges from 1.0A to 4.2A., and the output current has 8 stalls. The DM542 has automatic semi-flow, motor misconnected , over-voltage, under-voltage, andover-current protection functions.

Reference the previous article link on blog:
Arduino 控制三顆馬達

YouTube Demo:



ESP32 Code:
#include <AccelStepper.h>
#include <MultiStepper.h>
//--------- Flag structure --------------------------------------
typedef struct _vFlag
{
  uint8_t BTFlag = 0;
  uint8_t initial_Flag = 0;
} vFlag;
vFlag *flag_Ptr;
vFlag flag;

//------LED------------------
#define LED_BUILTIN 2
char ctemp[20];
//--------- uart structure --------------------------------------
//----------uart--------------
#define LINE_BUFFER_LENGTH 64
typedef struct _vUart
{
  char c;
  int lineIndex = 0;
  int line1Index = 0;
  int BTlineIndex = 0;
  bool lineIsComment;
  bool lineSemiColon;
  char line[128];
  char BTline[20];
  String inputString;
  String BTinputString;
  int V[16];
  char ctemp[30];
  char I2C_Data[80];
  float Voltage[16];
  int Buffer[128];
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//--------- motor structure --------------------------------------
typedef struct _axis_state_t
{
  uint8_t pulPin[6]; //--X,Y,Z,A,B,C
  uint8_t dirPin[6];
  uint8_t enaPin[6];
  uint8_t HomePin[6];
  long RotatorAngle[6];
  boolean AXISFlag[6];
  uint8_t HomeFlag[6];
  uint8_t RotatorFlag[6];
  float FeedRate[6];
  float MaxSpeed[6];
  float Acceleration[6];
  int AXISNumber[2];
  int DC_Speed[6];
} axis_state_t;
axis_state_t *gc_Ptr;
axis_state_t gc;
//----------multi motor ---------------------------------------
MultiStepper steppers;
long positions[3];  
#define Stepper_ENABLE 13  
#define X_LIMIT_PIN    17
#define Y_LIMIT_PIN    4
#define Z_LIMIT_PIN    16
AccelStepper stepperX (1,12,14); // name of stepper motor (1 = driver, pin 2 = pulse, pin 3 = direction)  
AccelStepper stepperY (1,26,15); // name of stepper motor (1 = driver, pin 4 = step, pin 5 = direction)
AccelStepper stepperZ (1,27,33); // name of stepper motor (1 = driver, pin 6 = step, pin 7 = direction)
//------------------------------------------------------------------------------
//-------------------------------------
TaskHandle_t huart;
TaskHandle_t hfunction;
TaskHandle_t hmultimotor;  //TaskHandler

void MultiMotorTask(void *pvParameters);
void vUARTTask(void *pvParameters);
void vFunctionTask(void *pvParameters);
//------------------------------------------------------------------------------
void initial()
{
  Serial.println(F("Create Task"));
  //----------------------------------------------------------------------
  xTaskCreatePinnedToCore(
    vUARTTask, "UARTTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 3 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &huart //handle
    ,
    0);

  //----------------------------------------------------------------------
  initMultiMotorTask();
  //----------------------------------------------------------------------
}
void initMultiMotorTask(void)
{
  // Create the task
  xTaskCreatePinnedToCore(MultiMotorTask, "MultiMotorTask", 1024, NULL, 3, &hmultimotor, 1);
  // Check the results
  if (hmultimotor == NULL)
  {
    Serial.println("Create MultiMotor task failed");
  }
  else
  {
    Serial.println("MultiMotor task up and running");
    vTaskSuspend(hmultimotor);
  }
}
//------------------------------------------------------------------------------
void MultiMotorTask(void *pvParameters)
{
  (void)pvParameters;

  while(1)
  {
    digitalWrite(Stepper_ENABLE, LOW);
    if (gc.AXISFlag[0] || gc.AXISFlag[1] || gc.AXISFlag[2])
    {
      if (gc.RotatorFlag[0] == 1)
      {
        //Serial.println( "1" );
        gc.RotatorAngle[0] = gc.RotatorAngle[0] * 500;
        //gc.RotatorAngle[0] = gc.RotatorAngle[0] * 100;   //F4設定
        positions[0] = gc.RotatorAngle[0];
      }
      else
      {
        positions[0] = stepperX.currentPosition();
      }

      if (gc.RotatorFlag[1] == 1)
      {
        //Serial.println( "2" );
        gc.RotatorAngle[1]=gc.RotatorAngle[1]*500;//F4設定
        positions[1] = gc.RotatorAngle[1];
      }
      else
      {
        positions[1] = stepperY.currentPosition();
      }

      if (gc.RotatorFlag[2] == 1)
      {
        //Serial.println( "3" );
        gc.RotatorAngle[2] = gc.RotatorAngle[2] * 100;
        //gc.RotatorAngle[2] = gc.RotatorAngle[2] * 250;//F4設定
        positions[2] = gc.RotatorAngle[2];
      }
      else
      {
        positions[2] = stepperZ.currentPosition();
      }
      //----multi motor
      //positions[0]=gc.RotatorAngle[0];
      //positions[1]=gc.RotatorAngle[1];
      //vTaskSuspend(hled);
      steppers.moveTo(positions);
      steppers.runSpeedToPosition();
      //steppers.run();

      if (abs(stepperX.currentPosition()) == abs(gc.RotatorAngle[0]))
      {
        gc.AXISFlag[0] = false;
        if (gc.RotatorFlag[0] == 1)
        {
          gc.AXISNumber[0]--;
        }
        Serial.println( "X_OK" );
        //vTaskSuspend( hmultimotor );
      }
      if (abs(stepperY.currentPosition()) == abs(gc.RotatorAngle[1]))
      {
        gc.AXISFlag[1] = false;
        if (gc.RotatorFlag[1] == 1)
        {
          gc.AXISNumber[0]--;
        }
        Serial.println( "Y_OK" );
        //vTaskSuspend( hmultimotor );
      }
      if (abs(stepperZ.currentPosition()) == abs(gc.RotatorAngle[2]))
      {
        gc.AXISFlag[2] = false;
        if (gc.RotatorFlag[2] == 1)
        {
          gc.AXISNumber[0]--;
        }
        Serial.println( "Z_OK" );
        //vTaskSuspend( hmultimotor );
      }
      if (gc.AXISNumber[0] == 0)
      {
        //vTaskResume(hled);
        for (int i = 0; i < 6; i++)
        {
          gc.RotatorFlag[i] = 0;
        }
        //vTaskSuspend(hmultimotor);
      }
    }
   
    vTaskDelay(1);
    //vTaskDelay(configTICK_RATE_HZ);
  }
}
void initMultiMotorSetup(void)
{
  gc.pulPin[0] = 26;
  gc.dirPin[0] = 25;
  //gc.HomePin[0] = 8;
  //gc.enaPin[0]=8;
  gc.pulPin[1] = 32;
  gc.dirPin[1] = 33;
  //gc.enaPin[1]=8;
  //gc.HomePin[1] = 9;
  gc.pulPin[2] = 34;
  gc.dirPin[2] = 35;
  //gc.enaPin[2]=8;
  //gc.HomePin[2] = 10;

  for (int i = 0; i < 6; i++)
  {
    //pinMode(gc.pulPin[i], OUTPUT);
    //pinMode(gc.dirPin[i], OUTPUT);
    //pinMode(gc.enaPin[i], OUTPUT);
    //digitalWrite(gc.enaPin[i], HIGH);

    gc.RotatorAngle[i] = 0;
    gc.AXISFlag[i] = false;
    gc.HomeFlag[i] = 0;
    gc.RotatorFlag[i] = 0;

    gc.FeedRate[i] = 1000;
    gc.MaxSpeed[i] = 4000;
    gc.Acceleration[i] = 800;
  }

  gc.AXISNumber[0] = 0;
  gc.AXISNumber[1] = 0;


  pinMode(Stepper_ENABLE, OUTPUT);
  digitalWrite(Stepper_ENABLE, HIGH);
  //pinMode(26, OUTPUT);
  stepperX.setMaxSpeed(4000);
  stepperX.setAcceleration(1000);
  stepperX.setSpeed(4000); //:f4000,4000,4000


  stepperY.setMaxSpeed(4000); //:ms8000,8000,8000
  stepperY.setAcceleration(1000);
  stepperY.setSpeed(1000);

  stepperZ.setMaxSpeed(4000);
  stepperZ.setAcceleration(800);
  stepperZ.setSpeed(2000);

  // Then give them to MultiStepper to manage
  steppers.addStepper(stepperX);
  steppers.addStepper(stepperY);
  steppers.addStepper(stepperZ);
}
//-------------------------------------------------
void setup()
{
  Serial.begin(9600);
  Serial.println(F("init"));
  initial();
  initMultiMotorSetup();
}
//-----------------------------------------
void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while(1)
  {
    vTaskDelay(1);
  }
}
//----------------------------------------
void processCommand(char *data)
{
  int len, xlen, ylen, zlen, alen;
  int tempDIO;
  String stemp;

  len = Uart.inputString.length();
  //memset(ctemp, 0, sizeof(ctemp));
  //---------------------------------------
  if (strstr(data, "VER") != NULL)
  {
    Serial.println(F("ESP32_20231226"));
  }
  //-----------Motor----------------------
  if (strstr(data, ":P") != NULL)
  {
    //:p200,100,300
    for (int i = 2; i < len; i++)
    {
      ctemp[i - 2] = data[i];
      if (data[i] == ',')
      {
        xlen = i;
        ctemp[xlen] = '\0';
        gc.RotatorAngle[0] = atoi(ctemp);
        //Serial.println(xlen);
        //Serial.println(gc.RotatorAngle[0]);
        break;
      }
    }

    int j = (xlen + 1);
    for (int i = j; i < len; i++)
    {
      ctemp[i - j] = data[i];
      if (data[i] == ',')
      {
        ylen = i;
        ctemp[ylen] = '\0';
        gc.RotatorAngle[1] = atoi(ctemp);
        //Serial.print("debug- ");
        //Serial.println(gc.RotatorAngle[1]);
        break;
      }
    }
    int k = (ylen + 1);
    for (int i = k; i < len; i++)
    {
      zlen = i;
      ctemp[i - k] = data[i];
    }
    ctemp[zlen] = '\0';
    gc.RotatorAngle[2] = atoi(ctemp);
    //Serial.println(zlen);
    //Serial.println(gc.RotatorAngle[2]);

    gc.AXISFlag[0] = true;
    gc.AXISFlag[1] = true;
    gc.AXISFlag[2] = true;
    gc.RotatorFlag[0] = 1;
    gc.RotatorFlag[1] = 1;
    gc.RotatorFlag[2] = 1;
    gc.AXISNumber[0] = 3;
    vTaskResume(hmultimotor);
  }

  if (strstr(data, ":F"))
  {
    //:f4000,4000,2000
    for (int i = 2; i < len; i++)
    {
      Uart.ctemp[i - 2] = data[i];
      if (data[i] == ',')
      {
        xlen = i;
        Uart.ctemp[xlen] = '\0';
        gc.FeedRate[0] = atoi(Uart.ctemp);
        //Serial.println(xlen);
        //Serial.println(gc.RotatorAngle[0]);
        break;
      }
    }
    int j = (xlen + 1);
    for (int i = j; i < len; i++)
    {
      Uart.ctemp[i - j] = data[i];
      if (data[i] == ',')
      {
        ylen = i;
        Uart.ctemp[ylen] = '\0';
        gc.FeedRate[1] = atoi(Uart.ctemp);
        //Serial.println(ylen);
        //Serial.println(gc.RotatorAngle[1]);
        break;
      }
    }
    int k = (ylen + 1);
    for (int i = k; i < len; i++)
    {
      zlen = i;
      Uart.ctemp[i - k] = data[i];
    }
    Uart.ctemp[zlen] = '\0';
    gc.FeedRate[2] = atoi(Uart.ctemp);

    stepperX.setSpeed(gc.FeedRate[0]);
    stepperY.setSpeed(gc.FeedRate[1]);
    stepperZ.setSpeed(gc.FeedRate[2]);
  }

  if (strstr(data, ":MS"))
  {
    //float MaxSpeed[6];
    //:p200,100,300
    for (int i = 2; i < len; i++)
    {
      Uart.ctemp[i - 2] = data[i];
      if (data[i] == ',')
      {
        xlen = i;
        Uart.ctemp[xlen] = '\0';
        gc.MaxSpeed[0] = atoi(Uart.ctemp);
        //Serial.println(xlen);
        //Serial.println(gc.RotatorAngle[0]);
        break;
      }
    }
    int j = (xlen + 1);
    for (int i = j; i < len; i++)
    {
      Uart.ctemp[i - j] = data[i];
      if (data[i] == ',')
      {
        ylen = i;
        Uart.ctemp[ylen] = '\0';
        gc.MaxSpeed[1] = atoi(Uart.ctemp);
        //Serial.println(ylen);
        //Serial.println(gc.RotatorAngle[1]);
        break;
      }
    }
    int k = (ylen + 1);
    for (int i = k; i < len; i++)
    {
      zlen = i;
      Uart.ctemp[i - k] = data[i];
    }
    Uart.ctemp[zlen] = '\0';
    gc.MaxSpeed[2] = atoi(Uart.ctemp);

    stepperX.setMaxSpeed(gc.MaxSpeed[0]);
    stepperY.setMaxSpeed(gc.MaxSpeed[1]);
    stepperZ.setMaxSpeed(gc.MaxSpeed[2]);
  }
  if (strstr(data, ":ACC"))
  {
    //float Acceleration[6];
    //:ACC1000,1000,1000
    for (int i = 2; i < len; i++)
    {
      Uart.ctemp[i - 2] = data[i];
      if (data[i] == ',')
      {
        xlen = i;
        Uart.ctemp[xlen] = '\0';
        gc.Acceleration[0] = atoi(Uart.ctemp);
        //Serial.println(xlen);
        //Serial.println(gc.RotatorAngle[0]);
        break;
      }
    }
    int j = (xlen + 1);
    for (int i = j; i < len; i++)
    {
      Uart.ctemp[i - j] = data[i];
      if (data[i] == ',')
      {
        ylen = i;
        Uart.ctemp[ylen] = '\0';
        gc.Acceleration[1] = atoi(Uart.ctemp);
        //Serial.println(ylen);
        //Serial.println(gc.RotatorAngle[1]);
        break;
      }
    }
    int k = (ylen + 1);
    for (int i = k; i < len; i++)
    {
      zlen = i;
      Uart.ctemp[i - k] = data[i];
    }
    Uart.ctemp[zlen] = '\0';
    gc.Acceleration[2] = atoi(Uart.ctemp);

    stepperX.setAcceleration(gc.Acceleration[0]);
    stepperY.setAcceleration(gc.Acceleration[1]);
    stepperZ.setAcceleration(gc.Acceleration[2]);
  }

  if (strstr(data, ":X") != NULL)
  {
    gc.HomeFlag[0] = 3;
    gc.AXISFlag[0] = true;
    for (int i = 2; i < len; i++)
    {
      ctemp[i - 2] = data[i];
    }
    ctemp[len - 2] = '\0';
    gc.RotatorAngle[0] = atoi(ctemp);
    gc.RotatorFlag[0] = 1;
    //Serial.println(gc.RotatorAngle[0]);
    gc.AXISNumber[0] = 1;
    vTaskResume(hmultimotor);
  }
  if (strstr(data, ":Y") != NULL)
  {
    gc.AXISFlag[1] = true;
    for (int i = 2; i < len; i++)
    {
      ctemp[i - 2] = data[i];
    }
    ctemp[len - 2] = '\0';
    gc.RotatorAngle[1] = atoi(ctemp);
    //Serial.println(gc.RotatorAngle[1]);
    gc.RotatorFlag[1] = 1;

    gc.AXISNumber[0] = 1;
    vTaskResume(hmultimotor);
  }

  if (strstr(data, ":Z") != NULL)
  {
    gc.AXISFlag[2] = true;
    for (int i = 2; i < len; i++)
    {
      ctemp[i - 2] = data[i];
    }
    ctemp[len - 2] = '\0';
    gc.RotatorAngle[2] = atoi(ctemp);
    //Serial.println(gc.RotatorAngle[2]);
    gc.RotatorFlag[2] = 1;

    gc.AXISNumber[0] = 1;
    vTaskResume(hmultimotor);
  }
 
}
//-----------------------------------------

//-------------------------------------------
void vUARTTask(void *pvParameters)
{
  (void)pvParameters;

  Serial.print(F("UARTTask at core:"));
  Serial.println(xPortGetCoreID());
  for (;;)
  {
    while (Serial.available() > 0)
    {
      Uart.c = Serial.read();
 
      if ((Uart.c == '\n') || (Uart.c == '\r'))
      { // End of line reached
        if (Uart.lineIndex > 0)
        { // Line is complete. Then execute!
          Uart.line[Uart.lineIndex] = '\0'; // Terminate string
          //Serial.println( F("Debug") );
          //Serial.println( Uart.inputString );
          processCommand(Uart.line);
          Uart.lineIndex = 0;
          Uart.inputString = "";
        }
        else
        {
          // Empty or comment line. Skip block.
        }
        Uart.lineIsComment = false;
        Uart.lineSemiColon = false;
        Serial.println(F("ok>"));
      }
      else
      {
        //Serial.println( c );
        if ((Uart.lineIsComment) || (Uart.lineSemiColon))
        {
          if (Uart.c == ')')
            Uart.lineIsComment = false; // End of comment. Resume line.
        }
        else
        {
          if (Uart.c == '/')
          { // Block delete not supported. Ignore character.
          }
          else if (Uart.c == '~')
          { // Enable comments flag and ignore all characters until ')' or EOL.
            Uart.lineIsComment = true;
          }
          else if (Uart.c == ';')
          {
            Uart.lineSemiColon = true;
          }
          else if (Uart.lineIndex >= LINE_BUFFER_LENGTH - 1)
          {
            Serial.println("ERROR - lineBuffer overflow");
            Uart.lineIsComment = false;
            Uart.lineSemiColon = false;
          }
          else if (Uart.c >= 'a' && Uart.c <= 'z')
          { // Upcase lowercase
            Uart.line[Uart.lineIndex] = Uart.c - 'a' + 'A';
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += (char)(Uart.c - 'a' + 'A');
          }
          else
          {
            Uart.line[Uart.lineIndex] = Uart.c;
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += Uart.c;
          }
        }
      }
    } //while (Serial.available() > 0)
   
    vTaskDelay(1);
  }
}



2023年11月15日 星期三

ESP32 IOT smart farm with WIFI mesh

Purpose:
利用ESP32 mesh網絡架構一個智慧農場的控制系統, 包含主控監視端為Node1 includes Xbox joystick, 並利用之前寫過的文章做成各監控端的Node節點並將資料發送到 MQTT 伺服器並透過 Node-red 儀表板顯示其數值., 整合成一個智慧農場的雛型.

Use the ESP32 mesh network to construct a control system for a smart farm, including the main monitoring terminal Node1 with Xbox joystick, and use previously written articles to create other nodes for each monitoring terminal, then send the value to the MQTT server and displayed through the Node-red dashboard. Integrating them into a prototype of a smart farm.

Fundamental:
Previous article :
ESP32 Wifi mesh Control another ESP32 with Relay
ESP32與DS18B20溫度感測器的C#圖形化資料收集
ESP32 WiFi painlessmesh network Application
ESP32 WiFi Mesh 控制 4 port Relay模組
ESP32 WiFi mesh中控介面的實踐
NodeMCU-32S (WiFi Mesh 與 BT BLE應用)

Architecture:



Circuit:
Node_1 Main


Node_2 RelayBoard


Node_3 DHT22


Node_4 Water Sensor


YouTube Demo:

ESP32 Main Node1 code:
#include "painlessMesh.h"
//#include <ArduinoJson.h>
#include <Arduino_JSON.h>
#include <ezButton.h>
//-----Global variable---------------------------------------
#define LED_BUILTIN 2
//--------joystick------------------------------------------
#define VRX_PIN_L  33 // ESP32 pin GPIO33 (ADC0)
#define VRY_PIN_L  32 // ESP32 pin GPIO32 (ADC0)
#define VRX_PIN_R  35 // ESP32 pin GPIO33 (ADC0)
#define VRY_PIN_R  34 // ESP32 pin GPIO32 (ADC0)
#define SW_X       18
#define SW_Y       16
#define SW_A       17
#define SW_B       19

#define LEFT_THRESHOLD_L  2200  
#define RIGHT_THRESHOLD_L 900
#define UP_THRESHOLD_L    2200  
#define DOWN_THRESHOLD_L  900  

#define LEFT_THRESHOLD_R  2200  
#define RIGHT_THRESHOLD_R 900
#define UP_THRESHOLD_R    2200  
#define DOWN_THRESHOLD_R  900  

#define COMMAND_NO_L     0x00
#define COMMAND_LEFT_L   0x01
#define COMMAND_RIGHT_L  0x02
#define COMMAND_UP_L     0x04
#define COMMAND_DOWN_L   0x08

#define COMMAND_NO_R     0x00
#define COMMAND_LEFT_R   0x01
#define COMMAND_RIGHT_R  0x02
#define COMMAND_UP_R     0x04
#define COMMAND_DOWN_R   0x08

int valueX_L = 0 ; // to store the X-axis value
int valueY_L = 0 ; // to store the Y-axis value
int command_L = COMMAND_NO_R;
int valueX_R = 0 ; // to store the X-axis value
int valueY_R = 0 ; // to store the Y-axis value
int command_R = COMMAND_NO_R;
//----------------------------------------------

int bValue_A = 0; // To store value of the button
int bValue_B = 0; // To store value of the button
int bValue_X = 0; // To store value of the button
int bValue_Y = 0; // To store value of the button
ezButton buttonA(SW_A);
ezButton buttonB(SW_B);
ezButton buttonX(SW_X);
ezButton buttonY(SW_Y);
//-----------painlessmesh------------------------------------
#define   MESH_PREFIX     "Peter1015"
#define   MESH_PASSWORD   "No18141814"
#define   MESH_PORT       5555
Scheduler userScheduler;  // to control your personal task
painlessMesh  mesh;

//Number for this node
int nodeNumber = 1;
void sendMessage() ;
String readings;
String getReadings();
Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );
//--------------------Json data ------------------------------
StaticJsonDocument<200> json_doc;
char json_output[100];
DeserializationError json_error;
const char* payload_node;
const char* payload_function;
const char* payload_data;
//------------------------------------------------------------
//char line[16];
//--------- Flag structure --------------------------------------

#define LINE_BUFFER_LENGTH 1024
typedef struct _vFlag
{
  uint8_t LEDFlag=0;
  uint8_t BTFlag=0;
  uint8_t FunctionFlag=1;
  uint8_t SendFlag=0;
}vFlag;
vFlag *flag_Ptr;
vFlag flag;
//--------- uart structure --------------------------------------
//----------uart--------------
#define LINE_BUFFER_LENGTH 64
typedef struct _vUart
{
  char c;
  int lineIndex = 0;
  int line1Index = 0;
  int BTlineIndex = 0;
  bool lineIsComment;
  bool lineSemiColon;
  char line[128];
  char BTline[20];
  String inputString;
  String BTinputString;
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//-------------------------------------
TaskHandle_t hled;
TaskHandle_t huart;

void vLEDFlashTask(void *pvParameters);
void vUARTTask(void *pvParameters);

void initial()
{
  Serial.println(F("Create Task"));
  //----------------------------------------------------------------------
  // Now set up two tasks to run independently.
  xTaskCreatePinnedToCore(
    vLEDFlashTask, "LEDTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &hled //handle
    ,
    0);

  xTaskCreatePinnedToCore(
    vUARTTask, "UARTTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 3 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &huart //handle
    ,
    0);

  //----------------------------------------------------------------------
}
String getReadings()
{
  JSONVar jsonReadings;
 
  //Serial.print(F("meshTask at core:"));
  //Serial.println(xPortGetCoreID());
  jsonReadings["node"] = nodeNumber;
  //jsonReadings["node"] = String(normAccel.XAxis);
  //jsonReadings["node ID"] = String(mesh.getNodeId());
  if(flag.FunctionFlag==1)
  {
    jsonReadings["function"] = "Joystick";
  }
 
  readings = JSON.stringify(jsonReadings);
  return readings;
}
void sendMessage()
{
  //String msg = "Hello from node 1 ";
  String msg = getReadings();
  //msg += mesh.getNodeId();

  if(flag.SendFlag ==1)
  {
    msg=Uart.BTinputString;
    Uart.BTinputString="";
    flag.SendFlag=0;
  }
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 2 ));
}

void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
  flag.LEDFlag=1;
}

void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);  
}

void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
  flag.LEDFlag=0;
}

void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  Serial.begin(9600);
  /**240(default 240 160 80 40 20 and 10Mhz)***/
  setCpuFrequencyMhz(160);
  initial();
  //-----------------------------------------------------------------
  buttonA.setDebounceTime(50); // set debounce time to 50 milliseconds
  buttonB.setDebounceTime(50); // set debounce time to 50 milliseconds
  buttonX.setDebounceTime(50); // set debounce time to 50 milliseconds
  buttonY.setDebounceTime(50); // set debounce time to 50 milliseconds
  //------------------------------------------------
  //-------------------------------------------------------------------
  //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages
  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
  //-------------------------------------------
  Serial.println(F("System On!"));
  //-------------------------------------------
}

void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while (1)
  {
    buttonA.loop(); // MUST call the loop() function first
    buttonB.loop(); // MUST call the loop() function first
    buttonX.loop(); // MUST call the loop() function first
    buttonY.loop(); // MUST call the loop() function first
    // Read the button value
    bValue_A = buttonA.getState();
    bValue_B = buttonB.getState();
    bValue_X = buttonX.getState();
    bValue_Y = buttonY.getState();

    if (buttonA.isPressed()) {
      Serial.println("The buttonA is pressed");
      Uart.BTinputString="{\"node\":10,\"ButtonA\":\"OFF\"}";
      flag.SendFlag=1;
    }
    if (buttonA.isReleased()) {
      Serial.println("The buttonA is released");
     
    }
    if (buttonB.isPressed()) {
      Serial.println("The buttonB is pressed");
      Uart.BTinputString="{\"node\":10,\"ButtonB\":\"OFF\"}";
      flag.SendFlag=1;
    }
    if (buttonB.isReleased()) {
      Serial.println("The buttonB is released");
      // TODO do something here
    }
    if (buttonX.isPressed()) {
      Serial.println("The buttonX is pressed");
      Uart.BTinputString="{\"node\":10,\"ButtonX\":\"OFF\"}";
      flag.SendFlag=1;
    }
    if (buttonX.isReleased()) {
      Serial.println("The buttonX is released");
      // TODO do something here
    }
    if (buttonY.isPressed()) {
      Serial.println("The buttonY is pressed");
      Uart.BTinputString="{\"node\":10,\"ButtonY\":\"OFF\"}";
      flag.SendFlag=1;
    }
    if (buttonY.isReleased()) {
      Serial.println("The buttonY is released");
      // TODO do something here
    }
    //--------------------------------------------------------
    valueX_L = analogRead(VRX_PIN_L);
    valueY_L = analogRead(VRY_PIN_L);
    valueX_R = analogRead(VRX_PIN_R);
    valueY_R = analogRead(VRY_PIN_R);
    // converts the analog value to commands
    // reset commands
    command_L = COMMAND_NO_L;
    command_R = COMMAND_NO_R;
    // check left/right commands
   
    if (valueX_L > LEFT_THRESHOLD_L)
      command_L = command_L | COMMAND_LEFT_L;
    else if (valueX_L < RIGHT_THRESHOLD_L)
      command_L = command_L | COMMAND_RIGHT_L;
   
    // check up/down commands
    if (valueY_L > UP_THRESHOLD_L)
      command_L = command_L | COMMAND_UP_L;
    else if (valueY_L < DOWN_THRESHOLD_L)
      command_L = command_L | COMMAND_DOWN_L;


    // print command to serial and process command
   
    if (command_L & COMMAND_LEFT_L) {
      Serial.println("COMMAND LEFT_L");
      Uart.BTinputString="{\"node\":10,\"ButtonX\":\"ON\"}";
      flag.SendFlag=1;
    }

    if (command_L & COMMAND_RIGHT_L) {
      Serial.println("COMMAND RIGHT_L");
      Uart.BTinputString="{\"node\":10,\"ButtonB\":\"ON\"}";
      flag.SendFlag=1;
    }

    if (command_L & COMMAND_UP_L) {
      Serial.println("COMMAND UP_L");
      Uart.BTinputString="{\"node\":10,\"ButtonY\":\"ON\"}";
      flag.SendFlag=1;
    }

    if (command_L & COMMAND_DOWN_L) {
      Serial.println("COMMAND DOWN_L");
      Uart.BTinputString="{\"node\":10,\"ButtonA\":\"ON\"}";
      flag.SendFlag=1;
    }
   
    //-----------------------------------------------------
    if (valueX_R > LEFT_THRESHOLD_R)
      command_R = command_R | COMMAND_LEFT_R;
    else if (valueX_R < RIGHT_THRESHOLD_R)
      command_R = command_R | COMMAND_RIGHT_R;
   
    // check up/down commands
    if (valueY_R > UP_THRESHOLD_R)
      command_R = command_R | COMMAND_UP_R;
    else if (valueY_R < DOWN_THRESHOLD_R)
      command_R = command_R | COMMAND_DOWN_R;


    // print command to serial and process command
   
    if (command_R & COMMAND_LEFT_R) {
      Serial.println("COMMAND LEFT_R");
      // TODO: add your task here
    }

    if (command_R & COMMAND_RIGHT_R) {
      Serial.println("COMMAND RIGHT_R");
      // TODO: add your task here
    }

    if (command_R & COMMAND_UP_R) {
      Serial.println("COMMAND UP_R");
      // TODO: add your task here
    }

    if (command_R & COMMAND_DOWN_R) {
      Serial.println("COMMAND DOWN_R");
      // TODO: add your task here
    }
    //-----------------------------------------------------
    //-----------painlessmesh------------------------------
    mesh.update();
  }//----while(1)-------------------------------------------

}
//--------------------------------------------------------
/*--------------------------------------------------*/
void vLEDFlashTask(void *pvParameters) // This is a task.
{
  (void)pvParameters;
 
  Serial.print(F("LEDTask at core:"));
  Serial.println(xPortGetCoreID());
  pinMode(LED_BUILTIN, OUTPUT);
  //oldMillis = millis();
  for (;;) // A Task shall never return or exit.
  {
    if(flag.LEDFlag==1)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(200);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(200);
    }
    else{
      vTaskDelay(10);
    }
  }
}

//-------------------------------------------
void vUARTTask(void *pvParameters)
{
  (void)pvParameters;

  Serial.print(F("UARTTask at core:"));
  Serial.println(xPortGetCoreID());
  for (;;)
  {
    while (Serial.available() > 0)
    {
      Uart.c = Serial.read();
 
      if ((Uart.c == '\n') || (Uart.c == '\r'))
      { // End of line reached
        if (Uart.lineIndex > 0)
        { // Line is complete. Then execute!
          Uart.line[Uart.lineIndex] = '\0'; // Terminate string
          //Serial.println( F("Debug") );
          //Serial.println( Uart.inputString );
          processCommand(Uart.line); // do something with the command
          Uart.lineIndex = 0;
          Uart.inputString = "";
        }
        else
        {
          // Empty or comment line. Skip block.
        }
        Uart.lineIsComment = false;
        Uart.lineSemiColon = false;
        Serial.println(F("ok>"));
      }
      else
      {
        //Serial.println( c );
        if ((Uart.lineIsComment) || (Uart.lineSemiColon))
        {
          if (Uart.c == ')')
            Uart.lineIsComment = false; // End of comment. Resume line.
        }
        else
        {
          if (Uart.c == '/')
          { // Block delete not supported. Ignore character.
          }
          else if (Uart.c == '~')
          { // Enable comments flag and ignore all characters until ')' or EOL.
            Uart.lineIsComment = true;
          }
          else if (Uart.c == ';')
          {
            Uart.lineSemiColon = true;
          }
          else if (Uart.lineIndex >= LINE_BUFFER_LENGTH - 1)
          {
            Serial.println("ERROR - lineBuffer overflow");
            Uart.lineIsComment = false;
            Uart.lineSemiColon = false;
          }
          else if (Uart.c >= 'a' && Uart.c <= 'z')
          { // Upcase lowercase
            Uart.line[Uart.lineIndex] = Uart.c - 'a' + 'A';
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += (char)(Uart.c - 'a' + 'A');
          }
          else
          {
            Uart.line[Uart.lineIndex] = Uart.c;
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += Uart.c;
          }
        }
      }
    } //while (Serial.available() > 0)
    vTaskDelay(10);
  }
}
//------------------------------------------------------------
void processCommand(char *data)
{
  int len, xlen, ylen, zlen, alen;
  char ctemp[20];

  len = Uart.inputString.length();
  if (strstr(data, "VER") != NULL)
  {
    Serial.println(F("W_ATE_Board_20231109"));
  }

}


ESP32 RelayBiard Node2 code:
#include "painlessMesh.h"
#include <Arduino_JSON.h>
//-------------------------------------------------------------
#define   MESH_PREFIX     "Peter1015"
#define   MESH_PASSWORD   "No18141814"
#define   MESH_PORT       5555
//-----Global variable---------------------------------------
#define LED_BUILTIN 2
//-----Relay------------------
#define RELAY1       15
#define RELAY2       13
#define RELAY3       32
#define RELAY4       33
//--------- Flag structure --------------------------------------
typedef struct _vFlag
{
  uint8_t LEDFlag=0;
  uint8_t NodeFlag=0;
  uint8_t FunctionFlag=1;
  uint8_t SendFlag=0;
  uint8_t Relay1=0;
  uint8_t Relay2=0;
  uint8_t Relay3=0;
  uint8_t Relay4=0;
}vFlag;
vFlag *flag_Ptr;
vFlag flag;
typedef struct _vUart
{
  String inputString;
  String BTinputString;
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//---------------------------------------------------
Scheduler userScheduler; // to control your personal task
painlessMesh  mesh;

//Number for this node
int nodeNumber = 2;
// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain
String readings;
String getReadings();
Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

TaskHandle_t hled;

void vLEDFlashTask(void *pvParameters);

void initial()
{
  Serial.println(F("Create Task"));
  //----------------------------------------------------------------------
  // Now set up two tasks to run independently.
  xTaskCreatePinnedToCore(
    vLEDFlashTask, "LEDTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &hled //handle
    ,
    0);
  //----------------------------------------------------------------------
}
String getReadings()
{
  JSONVar jsonReadings;
 
  //Serial.print(F("meshTask at core:"));
  //Serial.println(xPortGetCoreID());
  jsonReadings["node"] = nodeNumber;
  //jsonReadings["node"] = String(normAccel.XAxis);
  //jsonReadings["node ID"] = String(mesh.getNodeId());
  if(flag.FunctionFlag==1)
  {
    jsonReadings["function"] = "RelayBoard";
  }
  if(flag.Relay1==1)
  {
    jsonReadings["Relay1"] = "ON";
  }
  else
  {  
    jsonReadings["Relay1"] = "OFF";
  }
  if(flag.Relay2==1)
  {
    jsonReadings["Relay2"] = "ON";
  }
  else
  {  
    jsonReadings["Relay2"] = "OFF";
  }
  if(flag.Relay3==1)
  {
    jsonReadings["Relay3"] = "ON";
  }
  else
  {  
    jsonReadings["Relay3"] = "OFF";
  }
  if(flag.Relay4==1)
  {
    jsonReadings["Relay4"] = "ON";
  }
  else
  {  
    jsonReadings["Relay4"] = "OFF";
  }

  readings = JSON.stringify(jsonReadings);
  return readings;
}
void sendMessage() {
  String msg = getReadings();
  //msg += mesh.getNodeId();

  if(flag.SendFlag ==1)
  {
    msg=Uart.BTinputString;
    Uart.BTinputString="";
    flag.SendFlag=0;
  }
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 2 ));
}

// Needed for painless library
void receivedCallback( uint32_t from, String &msg )
{
  JSONVar myObject = JSON.parse(msg.c_str());
  if (myObject.hasOwnProperty("node")) {
    //Serial.print("myObject[\"node\"] = ");
    //Serial.println((int) myObject["node"]);
    if((int) myObject["node"]==10)
    {
      flag.NodeFlag=1;
    }
  }
  if (myObject.hasOwnProperty("function")) {
    //Serial.print("myObject[\"function\"] = ");
    //Serial.println((const char*) myObject["function"]);
  }
  if (myObject.hasOwnProperty("ButtonA")) {
   
    if(flag.NodeFlag==1)
    {
      //Serial.print("myObject[\"ButtonA\"] = ");
      //Serial.println((const char*) myObject["ButtonA"]);
      String str=(const char*)myObject["ButtonA"];
      if(str=="ON")
      {
        digitalWrite(RELAY1, LOW);
        flag.Relay1=1;
      }
      if(str=="OFF")
      {
        digitalWrite(RELAY1, HIGH);
        flag.Relay1=0;
      }
    }
  }
  if (myObject.hasOwnProperty("ButtonB")) {
   
    if(flag.NodeFlag==1)
    {
      //Serial.print("myObject[\"ButtonA\"] = ");
      //Serial.println((const char*) myObject["ButtonA"]);
      String str=(const char*)myObject["ButtonB"];
      if(str=="ON")
      {
        digitalWrite(RELAY2, LOW);
        flag.Relay2=1;
      }
      if(str=="OFF")
      {
        digitalWrite(RELAY2, HIGH);
        flag.Relay2=0;
      }
    }
  }
  if (myObject.hasOwnProperty("ButtonY")) {
   
    if(flag.NodeFlag==1)
    {
      //Serial.print("myObject[\"ButtonA\"] = ");
      //Serial.println((const char*) myObject["ButtonA"]);
      String str=(const char*)myObject["ButtonY"];
      if(str=="ON")
      {
        digitalWrite(RELAY3, LOW);
        flag.Relay3=1;
      }
      if(str=="OFF")
      {
        digitalWrite(RELAY3, HIGH);
        flag.Relay3=0;
      }
    }
  }
  if (myObject.hasOwnProperty("ButtonX")) {
   
    if(flag.NodeFlag==1)
    {
      //Serial.print("myObject[\"ButtonA\"] = ");
      //Serial.println((const char*) myObject["ButtonA"]);
      String str=(const char*)myObject["ButtonX"];
      if(str=="ON")
      {
        digitalWrite(RELAY4, LOW);
        flag.Relay4=1;
      }
      if(str=="OFF")
      {
        digitalWrite(RELAY4, HIGH);
        flag.Relay4=0;
      }
    }
  }

  Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
  flag.LEDFlag=1;
}

void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
   
}

void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
  flag.LEDFlag=0;
}

void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  Serial.begin(9600);
  setCpuFrequencyMhz(160);
  initial();
  //---------------------------------------------------
  pinMode(RELAY1, OUTPUT);
  digitalWrite(RELAY1, HIGH);
  pinMode(RELAY2, OUTPUT);
  digitalWrite(RELAY2, HIGH);
  pinMode(RELAY3, OUTPUT);
  digitalWrite(RELAY3, HIGH);
  pinMode(RELAY4, OUTPUT);
  digitalWrite(RELAY4, HIGH);
  //---------------------------------------------------
  //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages
  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
}

void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while (1)
  {
    mesh.update();
  }

}

/*--------------------------------------------------*/
void vLEDFlashTask(void *pvParameters) // This is a task.
{
  (void)pvParameters;
 
  Serial.print(F("LEDTask at core:"));
  Serial.println(xPortGetCoreID());
  pinMode(LED_BUILTIN, OUTPUT);
  //oldMillis = millis();
  for (;;) // A Task shall never return or exit.
  {
    if(flag.LEDFlag==1)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(200);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(200);
    }
    else{
      vTaskDelay(10);
    }
  }
}

//-------------------------------------------


ESP32 DHT22 Node3 code:
#include "painlessMesh.h"
#include <Arduino_JSON.h>
#include "DHT.h"
//-------------------------------------------------------------
#define   MESH_PREFIX     "Peter1015"
#define   MESH_PASSWORD   "No18141814"
#define   MESH_PORT       5555
//-----Global variable---------------------------------------
#define LED_BUILTIN 2
//---------------DHT22--------------------
#define DHTPIN 14
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
float Humidity=0;
float Temperature=0;
//--------- Flag structure --------------------------------------
typedef struct _vFlag
{
  uint8_t LEDFlag=0;
  uint8_t NodeFlag=0;
  uint8_t FunctionFlag=1;
  uint8_t SendFlag=0;
  uint8_t dht22=1;

}vFlag;
vFlag *flag_Ptr;
vFlag flag;
typedef struct _vUart
{
  String inputString;
  String BTinputString;
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//---------------------------------------------------
Scheduler userScheduler; // to control your personal task
painlessMesh  mesh;

//Number for this node
int nodeNumber = 3;
// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain
String readings;
String getReadings();
Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

TaskHandle_t hled;

void vLEDFlashTask(void *pvParameters);

void initial()
{
  Serial.println(F("Create Task"));
  //----------------------------------------------------------------------
  // Now set up two tasks to run independently.
  xTaskCreatePinnedToCore(
    vLEDFlashTask, "LEDTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &hled //handle
    ,
    0);
  //----------------------------------------------------------------------
}
String getReadings()
{
  JSONVar jsonReadings;
 
  //Serial.print(F("meshTask at core:"));
  //Serial.println(xPortGetCoreID());
  jsonReadings["node"] = nodeNumber;

  if(flag.FunctionFlag==1)
  {
    jsonReadings["function"] = "DHT22";
  }
  jsonReadings["Temperature"] = round(100*Temperature + 0.5)/100;//小數點後兩位
  jsonReadings["Humidity"] = round(100*Humidity + 0.5)/100;//小數點後兩位
 
  readings = JSON.stringify(jsonReadings);
  return readings;
}
void sendMessage() {
  String msg = getReadings();
  //msg += mesh.getNodeId();

  if(flag.SendFlag ==1)
  {
    msg=Uart.BTinputString;
    Uart.BTinputString="";
    flag.SendFlag=0;
  }
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 2 ));
}

// Needed for painless library
void receivedCallback( uint32_t from, String &msg )
{
  JSONVar myObject = JSON.parse(msg.c_str());
  if (myObject.hasOwnProperty("node")) {
    //Serial.print("myObject[\"node\"] = ");
    //Serial.println((int) myObject["node"]);
    if((int) myObject["node"]==10)
    {
      flag.NodeFlag=1;
    }
  }
  if (myObject.hasOwnProperty("function")) {
    //Serial.print("myObject[\"function\"] = ");
    //Serial.println((const char*) myObject["function"]);
  }
 
  Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
  flag.LEDFlag=1;
}

void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
   
}

void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
  flag.LEDFlag=0;
}

void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  Serial.begin(9600);
  setCpuFrequencyMhz(160);
  initial();
  //---------------------------------------------------
  dht.begin();
  //---------------------------------------------------
  //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages
  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
}

void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while (1)
  {
    if(flag.dht22==1)
    {    
      float h = dht.readHumidity();
      Humidity = dht.readHumidity();
      // Read temperature as Celsius (the default)
      float t = dht.readTemperature();
      Temperature = dht.readTemperature();
      // Read temperature as Fahrenheit (isFahrenheit = true)
      float f = dht.readTemperature(true);
     
      // Check if any reads failed and exit early (to try again).
      if (isnan(h) || isnan(t) || isnan(f))
      {
        Serial.println("Failed to read from DHT sensor!");
        return;
      }

      // Compute heat index in Fahrenheit (the default)
      float hif = dht.computeHeatIndex(f, h);
      // Compute heat index in Celsius (isFahreheit = false)
      float hic = dht.computeHeatIndex(t, h, false);

      Serial.print("Humidity: ");
      Serial.print(h);
      Serial.print(" % ");
      Serial.print("Temperature: ");
      Serial.print(t);
      Serial.print(" *C ");
      Serial.print(f);
      Serial.print(" *F ");
      Serial.print("Heat index: ");
      Serial.print(hic);
      Serial.print(" *C ");
      Serial.print(hif);
      Serial.println(" *F");
      vTaskDelay(100);
    }
   
    mesh.update();
  }

}

/*--------------------------------------------------*/
void vLEDFlashTask(void *pvParameters) // This is a task.
{
  (void)pvParameters;
 
  Serial.print(F("LEDTask at core:"));
  Serial.println(xPortGetCoreID());
  pinMode(LED_BUILTIN, OUTPUT);
  //oldMillis = millis();
  for (;;) // A Task shall never return or exit.
  {
    if(flag.LEDFlag==1)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(200);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(200);
    }
    else{
      vTaskDelay(10);
    }
   
  }
}

//-------------------------------------------

ESP32 Water Sensor Node4 code:
#include <WiFi.h>
//#include <WiFiClient.h>
#include <PubSubClient.h>
#include "painlessMesh.h"
#include <Arduino_JSON.h>
//-------------------------------------------------------------
#define   MESH_PREFIX     "Peter1015"
#define   MESH_PASSWORD   "No18141814"
#define   MESH_PORT       5555
//----------ssid---------------
// ------ 以下修改成你自己的WiFi帳號密碼 ------
char* ssid = "Winson_Y52";
char* password = "8888888888";
//char* ssid = "ras_2.4";
//char* password = "181415181415";
// ------ MQTT setting------
//char* MQTTServer = "broker.mqttgo.io";//https://broker.mqttgo.io/
char* MQTTServer = "test.mosquitto.org";
int MQTTPort = 1883;//MQTT Port
char* MQTTUser = "";//
char* MQTTPassword = "";//
//main level
char* MQTTPubTopic1 = "winsondiy/ESP32/WaterSensor";
char* MQTTPubTopic2 = "winsondiy/ESP32/DHT22";
char* MQTTSubTopic1 = "winsondiy/ESP32/relay1";
long MQTTLastPublishTime;//此變數用來記錄推播時間
long MQTTPublishInterval = 1000;//每5秒推撥一次
WiFiClient WifiClient;
PubSubClient MQTTClient(WifiClient);
// Example MQTT Json message
const char* sensor = "WaterLevel";
const char* exampleMQTT = "{\"sensor\":\"WaterLevel\",\"data\":[20,2]}";
//const char* exampleMQTT = "{\"data\":[20,3]}";
// Calculate needed JSON document bytes with example message
const size_t CAPACITY = JSON_OBJECT_SIZE(sizeof(exampleMQTT) + 20);

//-----Global variable---------------------------------------
#define LED_BUILTIN 2
//-----Water sensor------------------
#define POWER        34
#define SIGNAL       35
int value=0;
int level=0;
int maplevel=0;
//-------DHT22-----------------------------------------
double Humidity=0;
double Temperature=0;
//--------- Flag structure --------------------------------------
typedef struct _vFlag
{
  uint8_t LEDFlag=0;
  uint8_t NodeFlag=0;
  uint8_t FunctionFlag=1;
  uint8_t SendFlag=0;
  uint8_t sensor_Flag=1;
}vFlag;
vFlag *flag_Ptr;
vFlag flag;
typedef struct _vUart
{
  String inputString;
  String BTinputString;
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//---------------------------------------------------
Scheduler userScheduler; // to control your personal task
painlessMesh  mesh;

//Number for this node
int nodeNumber = 4;
// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain
String readings;
String getReadings();
Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

TaskHandle_t hled;

void vLEDFlashTask(void *pvParameters);

void initial()
{
  Serial.println(F("Create Task"));
  //----------------------------------------------------------------------
  // Now set up two tasks to run independently.
  xTaskCreatePinnedToCore(
    vLEDFlashTask, "LEDTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &hled //handle
    ,
    0);
  //----------------------------------------------------------------------
}
String getReadings()
{
  JSONVar jsonReadings;
 
  //Serial.print(F("meshTask at core:"));
  //Serial.println(xPortGetCoreID());
  jsonReadings["node"] = nodeNumber;
  //jsonReadings["node"] = String(normAccel.XAxis);
  //jsonReadings["node ID"] = String(mesh.getNodeId());
  if(flag.FunctionFlag==1)
  {
    jsonReadings["function"] = "WaterLevel";
  }
  jsonReadings["Level"] = maplevel;
 
  readings = JSON.stringify(jsonReadings);
  return readings;
}
void sendMessage() {
  String msg = getReadings();
  //msg += mesh.getNodeId();

  if(flag.SendFlag ==1)
  {
    msg=Uart.BTinputString;
    Uart.BTinputString="";
    flag.SendFlag=0;
  }
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 2 ));
}

// Needed for painless library
void receivedCallback( uint32_t from, String &msg )
{
  JSONVar myObject = JSON.parse(msg.c_str());
  if (myObject.hasOwnProperty("node")) {
    //Serial.print("myObject[\"node\"] = ");
    //Serial.println((int) myObject["node"]);
    if((int) myObject["node"]==3)
    {
      flag.NodeFlag=1;
    }
  }

  if (myObject.hasOwnProperty("function")) {
    //Serial.print("myObject[\"function\"] = ");
    //Serial.println((const char*) myObject["function"]);
  }
  //---------------------------------------
  if(flag.NodeFlag==1)
  {
    if (myObject.hasOwnProperty("Temperature")) {
      //Serial.print("myObject[\"function\"] = ");
      //Serial.println((const char*) myObject["function"]);
      //Temperature=(int) myObject["Temperature"];
      Temperature=myObject["Temperature"];
     
    }
    if (myObject.hasOwnProperty("Humidity")) {
      //Serial.print("myObject[\"function\"] = ");
      //Serial.println((const char*) myObject["function"]);
      //Humidity=(int) myObject["Humidity"];
      Humidity=myObject["Humidity"];
    }
    Serial.print("T:");
    Serial.print(Temperature);
    Serial.print("H:");
    Serial.println(Humidity);



  }
 
 




  Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
  flag.LEDFlag=1;
}

void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
   
}

void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
  flag.LEDFlag=0;
}

void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  Serial.begin(9600);
  setCpuFrequencyMhz(160);
  initial();
  //---------------------------------------------------
 
  //---------------------------------------------------
  //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages
  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA, 6 );
  //-----------------------------------------------------------------
  mesh.onReceive(&receivedCallback);
  //--------------------------
  mesh.stationManual(ssid, password);  
  //mesh.setHostname(HOSTNAME);
  mesh.setRoot(true);
  mesh.setContainsRoot(true);
  //-------------------------
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
}

void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while (1)
  {
    if (WiFi.status() != WL_CONNECTED)
    {
      WifiConnect();
    }
   
    if (!MQTTClient.connected())
    {
      MQTTConnect();
    }
    //如果距離上次傳輸已經超過10秒,則Publish溫溼度
    if ((millis() - MQTTLastPublishTime) >= MQTTPublishInterval )
    {
      StaticJsonDocument<CAPACITY> doc, doc1;
      doc["Level"] = maplevel;
      // Serialize JSON doc to char buffer with variable capacity (MQTT client needs char / char*)
      char JSONmessageBuffer[CAPACITY];
      //serializeJson(doc, Serial);
      serializeJson(doc, JSONmessageBuffer);
      MQTTClient.publish(MQTTPubTopic1, JSONmessageBuffer);
      //Serial.println("STATUS: Sent data via MQTT");
      Serial.println("water Data Publish to MQTT Broker");
      doc1["Temperature"] = Temperature;
      doc1["Humidity"] = Humidity;
      char JSONmessageBuffer1[CAPACITY];
      serializeJson(doc1, JSONmessageBuffer1);
      MQTTClient.publish(MQTTPubTopic2, JSONmessageBuffer1);

      doc = NULL;
      doc1 = NULL;
      MQTTLastPublishTime = millis();
    }
    MQTTClient.loop();//update status
    delay(50);
    //---------mesh----------------------
    mesh.update();
  }

}
void WifiConnect()
{
  //WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi連線成功");
  Serial.print("IP Address:");
  Serial.println(WiFi.localIP());
}
//-----------------------------------------------
void MQTTConnect()
{
  MQTTClient.setServer(MQTTServer, MQTTPort);
  MQTTClient.setCallback(MQTTCallback);
  while (!MQTTClient.connected())
  {
    //以亂數為ClietID
    String MQTTClientid = "esp32-" + String(random(1000000, 9999999));
    if (MQTTClient.connect(MQTTClientid.c_str(), MQTTUser, MQTTPassword))
    {
      //連結成功,顯示「已連線」。
      Serial.println("MQTT已連線");
      //訂閱SubTopic1主題
      MQTTClient.subscribe(MQTTSubTopic1);
    }
    else
    {
      //若連線不成功,則顯示錯誤訊息,並重新連線
      Serial.print("MQTT連線失敗,狀態碼=");
      Serial.println(MQTTClient.state());
      Serial.println("五秒後重新連線");
      delay(5000);
    }
  }
}
//------------------------------------------
//接收到訂閱時
void MQTTCallback(char* topic, byte* payload, unsigned int length)
{
  Serial.print(topic); Serial.print("訂閱通知:");
  String payloadString;//將接收的payload轉成字串
  //顯示訂閱內容
  for (int i = 0; i < length; i++)
  {
    payloadString = payloadString + (char)payload[i];
  }
  Serial.println(payloadString);
  //比對主題是否為訂閱主題1
  if (strcmp(topic, MQTTSubTopic1) == 0)
  {
    Serial.println("改變燈號:" + payloadString);
    if (payloadString == "ON")
    {
      digitalWrite(16, HIGH);
    }
    if (payloadString == "OFF")
    {
      digitalWrite(16, LOW);
    }
  }
}
/*--------------------------------------------------*/
void vLEDFlashTask(void *pvParameters) // This is a task.
{
  (void)pvParameters;
 
  Serial.print(F("LEDTask at core:"));
  Serial.println(xPortGetCoreID());
  pinMode(LED_BUILTIN, OUTPUT);
  //oldMillis = millis();
  for (;;) // A Task shall never return or exit.
  {
    if(flag.LEDFlag==1)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(200);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(200);
    }
    else{
      vTaskDelay(10);
    }
    if(flag.sensor_Flag==1)
    {
      level=waterSensor();
      maplevel=map(level, 0, 1060, 0, 4);
      Serial.print("Water Level:");
      Serial.println(maplevel);
      delay(10);
    }
  }
}
//--------------------------------------------
int waterSensor()
{
  //digitalWrite(POWER,HIGH);
  //delay(10);
  value=analogRead(SIGNAL);
  //delay(10);
  //digitalWrite(POWER,LOW);
  return value;
}
//-------------------------------------------