2023年11月29日 星期三

Control Your Music by ESP32 Bluetooth Speaker

Purpose:
利用上一篇文章ESP32 Bluetooth Speaker再加上Play, Pause, Next and Previous的按鍵去控制Mobile APP的動作並且可以自動連接上一個連接過的裝置!

Use the ESP32 Bluetooth Speaker in the previous article add the Play, Pause, Next and Previous buttons to control the actions of the Mobile APP and automatically connect to a connected device!

Fundamental:
Reference https://en.wikipedia.org/wiki/List_of_Bluetooth_profiles

藍牙規範(Bluetooth profile),藍牙技術聯盟定義了許多Profile。Profile目的是要確保Bluetooth裝置間的互通性(interoperability)。但Bluetooth產品無須實現所有的Bluetooth規範Profile。Bluetooth 版本 1.1 定義13個Profiles。
目前WinCE6.0提供的Profile有DUN GATEWAY、HF/740T、Audio Gateway、LAP、PAN、HID。

This Project 用到的BT規範(A2DP and AVRCP):
藍牙立體聲音訊傳輸規範(A2DP
藍牙立體聲音訊傳輸規範(Advance Audio Distribution Profile),規定了使用藍牙非同步傳輸信道方式,傳輸高品質音樂檔案數據的協定堆棧軟體和使用方法,基於該協定就能通過以藍牙方式傳輸高品質的立體聲音樂。分為1.1版和1.2版,只要連接雙方支援A2DP協定都能以16 bits,44.1 kHz的品質傳輸聲音訊號。假如有一方沒有支援A2DP的話,只能以8 bits,8 kHz的品質的免手持裝置規範(Handsfree Profile)傳輸模式,聲音品質會大打折扣。
音訊/影片遠端控制設定檔(AVRCP
音訊/影片遠端控制設定檔(Audio Video Remote Control Profile,簡稱AVRCP)設計用於提供控制 TV、Hi-Fi 裝置等的標準[1]介面。此設定檔用於許可單個遠端控制裝置。

Reference the previous article link on blog:


YouTube Demo:




ESP32 Code:

#include "BluetoothA2DPSink.h"
#include <ezButton.h>

BluetoothA2DPSink a2dp_sink;
esp_a2d_connection_state_t last_state;
bool is_active = true;
int LEDFlag = 0;
#define PLAY      33
#define STOP      32
#define NEXT      22
#define PREVIOUS  23
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(PLAY);
ezButton buttonB(STOP);
ezButton buttonX(NEXT);
ezButton buttonY(PREVIOUS);

void avrc_metadata_callback(uint8_t id, const uint8_t *text)
{
  Serial.printf("==> AVRC metadata rsp: attribute id 0x%x, %s\n", id, text);
}
//---------------------------------------------------------------------------------
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
TaskHandle_t hled;
void initial()
{
  Serial.println(F("Create Task"));
  //--------------- create task----------------------------------
  xTaskCreatePinnedToCore(
    vLEDTask, "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);
  //----------------------------------------
  //----------------------------------------------------------------------
}

void setup()
{
  Serial.begin(9600);
  Serial.println(F("init"));
  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
  static const i2s_config_t i2s_config = {
        .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
        .sample_rate = 44100, // corrected by info from bluetooth
        .bits_per_sample = (i2s_bits_per_sample_t) 16, /* the DAC module will only take the 8bits from MSB */
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB,
        .intr_alloc_flags = 0, // default interrupt priority
        .dma_buf_count = 8,
        .dma_buf_len = 64,
        .use_apll = false
    };
  a2dp_sink.set_i2s_config(i2s_config);
  a2dp_sink.set_auto_reconnect(true);
  a2dp_sink.set_avrc_metadata_callback(avrc_metadata_callback);
  a2dp_sink.start("MyMusic");  
}


void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while (1)
  {
    buttonA.loop();
    buttonB.loop();
    buttonX.loop();
    buttonY.loop();
    //----------------------------
    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");
      Serial.println("play");
      a2dp_sink.play();
      LEDFlag=2;
    }
    if (buttonA.isReleased()) {
      Serial.println("The buttonA is released");
      // TODO do something here
    }
    if (buttonB.isPressed()) {
      Serial.println("The buttonB is pressed");
      Serial.println("pause");
      a2dp_sink.pause();
      LEDFlag=1;
    }
    if (buttonB.isReleased()) {
      Serial.println("The buttonB is released");
      // TODO do something here
    }
    if (buttonX.isPressed()) {
      Serial.println("The buttonX is pressed");
      Serial.println("next");
      a2dp_sink.next();
      LEDFlag=2;
    }
    if (buttonX.isReleased()) {
      Serial.println("The buttonX is released");
      // TODO do something here
    }
    if (buttonY.isPressed()) {
      Serial.println("The buttonY is pressed");
      Serial.println("previous");
      a2dp_sink.previous();
      LEDFlag=2;
    }
    if (buttonY.isReleased()) {
      Serial.println("The buttonY is released");
      // TODO do something here
    }
    //--------------------------------------------------------
    // check state
    esp_a2d_connection_state_t state = a2dp_sink.get_connection_state();
    bool is_connected = state == ESP_A2D_CONNECTION_STATE_CONNECTED;
    if (last_state != state)
    {
      Serial.println(is_connected ? "Connected" : "Not connected");    
      last_state = state;
      if(is_connected)
      {
        LEDFlag=1;
      }
    }
 
    //------------------------------------------
    if (a2dp_sink.get_audio_state()==ESP_A2D_AUDIO_STATE_STARTED)
    {
      //Serial.println("changing state...");
      is_active = !is_active;
   
      if (is_active)
      {
        //Serial.println("play");
        //a2dp_sink.play();
        LEDFlag=2;
      }
      else
      {
        //Serial.println("pause");
        //a2dp_sink.pause();
        //LEDFlag=1;
      }
    }
    delay(10);
  } //---while(1)
 
}

//-------------------------------------------------------------------------
static void vLEDTask(void *pvParameters)
{
  (void)pvParameters;

  Serial.println(F("LEDTask at core:"));
  Serial.println(xPortGetCoreID());
  pinMode(LED_BUILTIN, OUTPUT);
  for (;;) // A Task shall never return or exit.
  {
    if(LEDFlag == 0)
    {
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(800);
    }
    else if(LEDFlag == 1)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(800);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(800);
    }
    else if(LEDFlag == 2)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(300);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(300);
    }
    else
    {
      vTaskDelay(10);
    }
  }
}

2023年11月25日 星期六

ESP32 Bluetooth Speaker

Purpose:
Use ESP32 A2DP Sink (Music Receiver) to build your own Bluetooth Speaker.
This creates a new Bluetooth device with the name “MyMusic” and the output will be sent to the output directly to the internal DAC of the ESP32.
The output goes now to the DAC pins GPIO25 (Channel 1) and GPIO26 (Channel 2).
Architectures:
BOM(Bill of Material):
Single Speaker

Single Speaker

ESP32

Amplifier

Fundamental:

Features of the ESP32 include the following:
Processors:
CPU: Xtensa dual-core (or single-core) 32-bit LX6 microprocessor, operating at 160 or 240 MHz and performing at up to 600 DMIPS
Ultra low power (ULP) co-processor
Memory: 520 KiB RAM, 448 KiB ROM
Wireless connectivity:
Wi-Fi: 802.11 b/g/n
Bluetooth: v4.2 BR/EDR and BLE (shares the radio with Wi-Fi)
Peripheral interfaces:
34 × programmable GPIOs
4 × SPI
2 × I²S interfaces
2 × I²C interfaces
3 × UART

I2S is an electrical serial bus interface standard used for connecting digital audio devices together. It is used to communicate PCM audio data between integrated circuits in an electronic device.

ESP32 A2DP Library
Reference https://github.com/pschatzmann/ESP32-A2DP

XHM189 2 x 50W TPA3116D2 2-Channel High-end Digital Amplifier Board 24V Stereo
YouTube Demo:

ESP32 Code:
#include "BluetoothA2DPSink.h"

BluetoothA2DPSink a2dp_sink;

//----------------------------------------------------------------
//---------------------------------------------------------------------------------
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
TaskHandle_t hled;
void initial()
{
  Serial.println(F("Create Task"));
  //--------------- create task----------------------------------
  xTaskCreatePinnedToCore(
    vLEDTask, "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);
  //----------------------------------------
  //----------------------------------------------------------------------
  //vTaskSuspend(hfunction); //暫停TASK運行
  //----------------------------------------------------------------------
}

void setup()
{
  Serial.begin(9600);
  Serial.println(F("init"));
  initial();
  static const i2s_config_t i2s_config = {
        .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
        .sample_rate = 44100, // corrected by info from bluetooth
        .bits_per_sample = (i2s_bits_per_sample_t) 16, /* the DAC module will only take the 8bits from MSB */
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB,
        .intr_alloc_flags = 0, // default interrupt priority
        .dma_buf_count = 8,
        .dma_buf_len = 64,
        .use_apll = false
    };

  a2dp_sink.set_i2s_config(i2s_config);
  a2dp_sink.start("MyMusic");  

}


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

//-------------------------------------------------------------------------
static void vLEDTask(void *pvParameters)
{
  (void)pvParameters;

  Serial.println(F("LEDTask at core:"));
  Serial.println(xPortGetCoreID());
  pinMode(LED_BUILTIN, OUTPUT);
  for (;;) // A Task shall never return or exit.
  {
    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);
  }
}

2023年11月23日 星期四

Understand diagnostic trouble codes of your car

Purpose:
本專題利用ESP32+MCP2515 CAN Bus module搭配CANHacker軟體和Torque APP去分析接收到ECU Simulator的診斷錯誤碼的PID資料.
This topic uses the ESP32+MCP2515 CAN Bus module with CANHacker software and Torque APP to analyze the PID data of the diagnostic trouble codes received from the ECU Simulator.

Fundamental:
Reference https://en.wikipedia.org/wiki/OBD-II_PIDs#Service_03
Diagnostic Trouble Codes (DTC) or Fault Codes
Under OBDII / EOBD systems, all manufacturers must use a universal 5 digits code system.
These universal 5 digits codes are made up of:
• The 1st character in the DTC indicates a letter which identifies the “main
system” where the fault occurred (Power train, Body, Chassis or
Network)
• The 2nd character is a numerical digit which identifies “Generic or
Manufacturer Specific”
• The 3rd character is also a numerical digit which identifies the specific
systems or sub-systems where the problem is located.
• The 4th and 5th characters are also numerical digits which identifies the
section of the system that is malfunctioning.

CAN Hacker module (ESP32+MCP2515)
Reference the previous article link on blog:
ECU Simulator
模擬器提供下列模式:
系統狀態、即時資料:MODE 01
故障診斷:MODE 03,07,0A
故障清除:MODE 04
凍結資料:MODE 02
氧感測器測試:MODE 05
監測結果:MODE 06
車輛資訊:MODE 09 
模擬器提供六組模擬訊號調整旋鈕。由左至右編號為1~6。
預設模擬訊號源為:空氣流量、冷卻液溫度、轉速、車速、進氣溫度、節氣門位置
Reference the previous article link on blog:

YouTube Demo:



ECU fault code analysis
Understand diagnostic trouble code of your car
Car DTC analysis via ESP32 CAN Bus module


2023年11月20日 星期一

Catch your vehicle Data via cheap CAN bus Tool

Purpose:
這個專案是利用便宜的方案去抓取你的車子CANBUS的資料.
This project uses a cheap solution to capture your car’s actionable information of CANBUS data.

Architectures:
利用OBD2接頭延長線連接汽車, ELM327 and CAN Hacker module (ESP32+MCP2515).
再使用手機TORQUE APP 讀取車子的資訊並且利用PC的CANHACKER software接收資料並儲存以利後續分析PID資料.
Use the OBD2 connector extension cable to connect the car, ELM327 and CAN Hacker module (ESP32+MCP2515).
Then use the TORQUE APP on your mobile phone to read the car's information and use the PC's CANHACKER software to receive the data and store it for subsequent analysis of the PID data.

BOM(Bill of Material) Cost:  682NT

Fundamental:
CAN Hacker module (ESP32+MCP2515)
Reference the previous article link on blog:
ESP32 + MCP2515 use CanHacker on CAN Bus system 

OBD2(On-Board Diagnostics 2)
OBD-II systems provide access to health information and access to numerous parameters and sensors from the Electronic Control Units (ECUs). Thus, the OBDII system offers valuable information, including diagnostic trouble codes when troubleshooting problems.
if your car was manufactured after 1996, it features a Diagnostic Link Connector (DLC) or OBD2 port.

ELM327(Bluetooth interface)


ELM327 device acts as a bridge between the computer/mobile and the car. The OBD adapter is attached to the car's physical 16-pin OBD connector plug (OBD2 port), typically located near the dashboard below the steering wheel.
There are different types of interfaces that the ELM327 adapters can have, for example, Bluetooth, WiFi, USB, or serial port. For the best option for your needs.

Torque APP is for Android phones to use it with the ELM327 devices.
Reference https://wiki.torque-bhp.com/view/Main_Page

Torque is an OBD2 performance and diagnostic tool for any device that runs the Android operating system. It will allow you to access the many sensors within your vehicles Engine Management System, as well as allow you to view and clear trouble codes.


YouTube Demo:





2023年11月16日 星期四

ESP32+MCP2515 CAN Bus Scanner with ELM327 and ECU simulator

Purpose:
In this project it will be demonstrated that the ELM327 (Bluetooth interface), ECU emulator and ESP32 + MCP2515 module communicate with each other through the Controller Area Network (CAN) bus in the car's OBD-II (On-Board Diagnostics 2) connector.
在這個專案中將展示ELM327(藍牙介面)、ECU模擬器和ESP32 + MCP2515模組透過汽車的 OBD-II(車載診斷 2)連接器中的控制器區域網路 (CAN ) 匯流排相互通訊。

Fundamental:
ELM327 Bluetooth Automotive Code Readers
The ELM327 is a multi-protocol adapter that allows a pc or other device to communicate with a car's OBD-II engine management system through the Controller Area Network (CAN) bus. The ELM327 OBD2 interface is a great tool for any car owner, and it will help you keep on top of any issues that may arise with your vehicle.
reference https://en.wikipedia.org/wiki/ELM327

The previous article link on blog:
CAN BUS 通訊研究

Circuit:

YouTube Demo:



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;
}
//-------------------------------------------