Heltec-LoRa-WebOTA

สำหรับนักพัฒนา IOT แล้ว  การทำ OTA มีความสำคัญมากครับ  เพราะจะช่วยให้เราสามารถอัพเดทโปรแกรมให้กับผู้ใช้งานได้ง่ายมาก
โดยในบทความนี้เราจะมาทำการ OTA  กันด้วยการ กดรีเซ็ท (SW1)  และการตั้งเวลาให้ทำการอัพเดท จาก Server
สำหรับวิธีการกดรีเซ็ทนั้น  เราจะใช้การกดและนับจำนวนครั้ง 10 ครั้ง  เพื่อป้องกันการเผลอเรอ   โดยการนับ จำนวนครั้งเราจะใช้ฟังก์ชั่น  Interrupt Serivice Routine ( ISR )  โดยเรามีโค้ดตัวอย่างมาจาก เวบ  lastminutesengineer.com  โดยบอร์ดหรืออุปกรณ์ของเราจะต้องมีปุ่มสำหรับการกดรีเซ็ทไว้ด้วยตามรูป
 
  
เมื่อทำการกดรีเซ็ท SW1 ครบหรือมากกว่า 10 ครั้ง  ระบบก็จะทำการดาวน์โหลด   ไฟล์  bin ที่เราเตรียมไว้ก่อนหน้าแล้ว จากนั้นระบบก็ทำงานไปตามโปรแกรมใหม่  แต่หากว่าโปรแกรมนั้นเหมือนกับโปรแกรมเดิมที่มีอยู่ในบอร์ดแล้ว  มันก็ไม่ใช้โปรแกรมใหม่ครับ
สำหรับโค๊ด ต้นแบบการนับนั้นเป็นดังนี้ครับ

struct Button {
  const uint8_t PIN;
  uint32_t numberKeyPresses;
  bool pressed;
};
Button button1 = {36, 0, false}; // GPIO36
void IRAM_ATTR isr() {
  button1.numberKeyPresses += 1;
  button1.pressed = true;
}
void setup() {
  Serial.begin(115200);
  pinMode(button1.PIN, INPUT_PULLUP);
  attachInterrupt(button1.PIN, isr, FALLING);
}
void loop() {
  if (button1.pressed) {
      Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
      button1.pressed = false;
  }
  //Detach Interrupt after 1 Minute
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 60000) {
    lastMillis = millis();
    detachInterrupt(button1.PIN);
  Serial.println("Interrupt Detached!");
  }
}

โดย 36 คือ ตำแหน่งขาของ  Heltec LoRa esp32
 
สำหรับวิธีที่ 2  เราจะกำหนดให้โปรแกรมรับสถานะคำสั่งว่าเราต้องการ  OTA  ที่เวลาไหน  โดยเราจะตั้งมาจาก  Timer ที่ 10 ของแพลตฟอร์ม  หรือ  ใครจะดัดแปลงส่งคำสั่งอะไรมากระตุ้นสถานะของ RR_OTA ก็ได้
 

และยังสามารถกำหนดมาจากแอพมือถืออีกทางหนึ่ง

 
และ โค้ดสำหรับการทำ OTA  นั้นค่อนข้างจะยาว
โดยมีส่วนที่เกี่ยวข้องกับ OTA  ดังนี้
บรรทัด  335-390 คือการนำโค้ด  ในการนับมารวมในโค้ดหลัก  และกำหนดเงื่อนไขเมื่อกดจำนวน 10 ครั้ง แล้ว  ให้ไปเรียก  ฟังกชั่น ota_run  ในบรรทัด  374 โดย  ota_run อยู่ในบรรทัดที่   435-589 นั่นเอง
ส่วนของ  OTA  ต้องขอขอบคุณอาจารย์ สมศักดิ์  แซ่ลิ้ม  จากการอบรมภายใต้โครงการ  We Love Smart Farm&IOT ช่วงเดือนกันยายน-พฤศจิกายน 2564  ที่ได้ให้คำแนะนำการใช้งานจนใช้การได้ดีเยี่ยม
ส่วนโค๊ดอื่นๆ  ก็แถมมาเหมือนเดิมครับ  โดยตั้งใจเก็บไว้ดูกันลืม   แต่ในโค๊ดก็เป็นโค๊ดประจำของบอร์ด  SMT-IOT-005 จากสมองไอโทีเช่นเคย  ที่มีลูกเล่นองค์ประกอบครบถ้วน  RS845, Relay มากมาย port อ่าน PZEM,  I2C และ AI,DI มากมายก่ายกอง  หากสนใจทักทายกันมานะครับ ทางอีเมล์  paipatamp@gmail.com

/////////////////////////////////////////////
// DEFINE
/////////////////////////////////////////////
#define VERSION "3.0"
/////////////////////////////////////////////
// INCLUDE
/////////////////////////////////////////////
extern "C" {
  #include "freertos/FreeRTOS.h"  // มีปัญหากับ esp8266
  #include "freertos/timers.h"
}
// CONFIG
#include <ArduinoJson.h>
#ifdef ESP32
  #include <SPIFFS.h>
#else
  #include <FS.h>
#endif
// WIFI
#ifdef ESP32
  #include <WiFi.h>
  #include <WiFiMulti.h>
#else
  #include <ESP8266WiFi.h>
  #include <ESP8266WiFiMulti.h>
  #define ARDUINO_EVENT_WIFI_STA_GOT_IP WIFI_EVENT_STAMODE_GOT_IP
  #define ARDUINO_EVENT_WIFI_STA_DISCONNECTED WIFI_EVENT_STAMODE_DISCONNECTED
#endif
// OTA
#ifdef ESP32
  #include <Update.h>
#else
  #include <ESP8266httpUpdate.h>
#endif
#include <SoftwareSerial.h>  // https://github.com/PaulStoffregen/SoftwareSerial
#include <Convert.h>
#include "ModbusMaster.h" //https://github.com/4-20ma/ModbusMaster
#include <ModbusRtu.h>
#include <HardwareSerial.h>
#include <Arduino.h>
#include <Ticker.h>
#include "heltec.h"
#include <Wire.h>
//#include "Wire.h"
#include "define.h"
#include <math.h>
#include <ArduinoJson.h>
#include <Arduino_JSON.h>
//==============
///============
#define RX_PIN     22    //Serial Receive pin 16
#define TX_PIN     23    //Serial Transmit pin 17
#define MAX485_RE_NEG    25    //LED
#define RS485Transmit    HIGH
#define RS485Receive     LOW
#define  LED      0
float temp,pH ;
//========== XYMD02 =========
HardwareSerial RTU_Serial(2); // 1
//#define  RXD      22
//#define  TXD      23
//#define  RS485DE  25
//#define  LED      0
Modbus      master(0, RTU_Serial, MAX485_RE_NEG); // this is master and RS-232 or USB-FTDI
modbus_t    telegram[4];
bool restart;
uint16_t    au16data[8];
float   humidity;
float   temperature;
uint8_t  RTU_Slave_ID;
uint8_t  RTU_NEW_Slave_ID;
uint8_t  Slave_ID;
//======
SoftwareSerial RS485Serial(RX_PIN, TX_PIN);
//Creation of class object
Convert convert;
//==========
#include <PZEM004Tv30.h>
#if !defined(PZEM_RX_PIN) && !defined(PZEM_TX_PIN)
#define PZEM_RX_PIN 17
#define PZEM_TX_PIN 13
#endif
#if !defined(PZEM_SERIAL)
#define PZEM_SERIAL Serial1
#endif
#if defined(ESP32)
/*************************
 *  ESP32 initialization
 * ---------------------
 *
 * The ESP32 HW Serial interface can be routed to any GPIO pin
 * Here we initialize the PZEM on Serial2 with RX/TX pins 16 and 17
 */
PZEM004Tv30 pzem(PZEM_SERIAL, PZEM_RX_PIN, PZEM_TX_PIN);
#elif defined(ESP8266)
/*************************
 *  ESP8266 initialization
 * ---------------------
 *
 * Not all Arduino boards come with multiple HW Serial ports.
 * Serial2 is for example available on the Arduino MEGA 2560 but not Arduino Uno!
 * The ESP32 HW Serial interface can be routed to any GPIO pin
 * Here we initialize the PZEM on Serial2 with default pins
 */
//PZEM004Tv30 pzem(Serial1);
#else
/*************************
 *  Arduino initialization
 * ---------------------
 *
 * Not all Arduino boards come with multiple HW Serial ports.
 * Serial2 is for example available on the Arduino MEGA 2560 but not Arduino Uno!
 * The ESP32 HW Serial interface can be routed to any GPIO pin
 * Here we initialize the PZEM on Serial2 with default pins
 */
PZEM004Tv30 pzem(PZEM_SERIAL);
#endif
//
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
//
//=========
//#include <WiFi.h>
//======
//===================
#include <Adafruit_Sensor.h>
#include <DHT.h>  // กรณีนี้ต้องใช้คู่กันกับ  DHT_U.h
#include <DHT_U.h>
// See guide for details on sensor wiring and usage:
// https://learn.adafruit.com/dht/overview
uint32_t delayMS;
//===========
#define Slave_ID1    1  // see dip swith if connect to Transpower
#include "heltec.h"
#include "images.h"
#define BAND    915E6  //you can set band here directly,e.g. 868E6,915E6,433E6
// instantiate ModbusMaster object
ModbusMaster modbus;
///
/////////////////////////////////////////////
// GLOBAL VARIABLES
/////////////////////////////////////////////
// CONFIG
// OTA
WiFiClient otaClient;
//===
Ticker  _service_rtc;
Ticker  _service_sensor;
Ticker  _service_led;
//Ticker  _service_test_1;
//Ticker  _service_test_2;
//Ticker  _service_test_3;
TwoWire I2C0 = TwoWire(0);
adsGain_t   m_gain;
uint8_t     m_bitShift;
uint16_t    m_dataRate;
uint8_t   Relay, gpio_in;
uint16_t  delayx, count_read;
uint8_t   i2c_address_adc;
int16_t   adc0, adc1, adc2, adc3;
float     volts0, volts1, volts2, volts3;
uint32_t  lastreading;
char      cstr[8];
byte global_second, global_minute, global_hour, global_dayOfWeek, global_dayOfMonth, global_month, global_year;
//===
double res_dbl0;
double res_dbl1;
double res_dbl ;
unsigned int counter = 0;
String rssi = "RSSI --";
String packSize = "--";
String packet ;
String RS485_Data[13];
//======
const char* ssid      = "xxxxxxxx";
const char* password  = "xxxxxxxxx";
const char* ssid1     = "xxxxxxxx";
const char* password1 = "xxxxxxxx";
const char* ssid2     = "xxxxxxxx";
const char* password2 = "xxxxxxxx";
//=====
const char* host = "xxxxxxxx.com";
char* code = "xxxxxxxx";
char* dID = "xxxx";
float dustDensity = 35;
String response ="0";
String response_c = "0";
String a ;
int npress ;
//
//
float temp_0 = 0;
float humid_0 = 0;
float vHumidity = 0;
float vTemperature = 0;
float vPower=0;
float vVolt=0;
float iamp=0;
float vEnergy=0;
float voltage;
float current;
float power;
float energy;
float frequency;
float pf;
String data0 ; // for data request ;
String data1 ;
String datasend ;
String datasend1 ;
String datasend2 ;
String data2 ;
String data3 , dataa3 ;
String data4 , dataa4 ;
String data5 ;
String data6 ;
String data7 ;
String data8 ;
String data9 ;
String JSONSerial = "";
float jdata1,jdata2,jdata3,jdata4;
/// ===
String sdata1 ;
String sdata2 ; // standard
String sdata3 ;
String sdata4 ;
String sdata5 ;
String sdata6 ;
String sdata7 ;
String sdata8 ;
String sdata11;
String sdata12 ;
String master_state ;
String FlowLowStatus ;
String ResetPinValue ;
String MainPump ;
String PumpA ;
String PumpB ;
String flowsensor ;
String led1 = "00:00";
String led2 = "00:00";
String led3 = "1";
String Time1 ;
String Time2 ;
String R1,R2,R3,R4,R5,R6,R7,R8 ;
String RR1,RR2,RR3,RR4,RR5,RR6,RR7,RR8,R_OTA,RR_OTA ;
String CommandR3Slave = "Off" ;  // to control R3 on Nano
String CommandR4Slave ; // to control R4 on board
float data10 = 0;
float data11 = 0;
float data12 = 0;
float data13 = 0;
float data14 = 0;
float data15 = 0;
float data16 = 0;
float data17 = 0;
float data18 = 0;
float data19 = 0;
float data20 = 0;
float EC ;
float temperatureC = 0;
float temperatureF = 0;
float sensorValue = 0;
float rainmm = 0;
//int counter = 1;
int i ;
int sentcount = 0;
String url ;
int Relay1 = 12 ;
int Relay2 = 2 ;
//
String iddevice = dID;
String cccode = code;
String ccode =  code;
long lastMillis = 100;
/// ===
String sentpacket ;
String str;
char charBuf[100];
// Convent 32bit to float
//------------------------------------------------
float HexTofloat(uint32_t x)
{
  return (*(float*)&x);
}
uint32_t FloatTohex(float x)
{
  return (*(uint32_t*)&x);
}
//------------------------------------------------
////  GPIO36 User Switch
struct Button {
  const uint8_t PIN;
  uint32_t numberKeyPresses;
  bool pressed;
};
Button button1 = {36, 0, false}; // GPIO36
void IRAM_ATTR isr() {
  button1.numberKeyPresses += 1;
  button1.pressed = true;
  npress = button1.numberKeyPresses;
}
////
/////////
void OTA_reset_setup() {
  pinMode(button1.PIN, INPUT_PULLUP);
  attachInterrupt(button1.PIN, isr, FALLING);
}
void OTA_SW_loop() {
      //RR_OTA = "0";
  if (button1.pressed) {
      Serial.printf("Button 1 has been pressed %u times\n", button1.numberKeyPresses);
      button1.pressed = false;
      //RR_OTA = "1";
  }
   if(npress >= 10) {
    npress=0;
    button1.numberKeyPresses=0;
    Serial.println("Otrix-OTA: START");
      char* ota_host = "xxxxxxxx.com";
      uint16_t ota_port = 80;
      char* ota_path = "/xxxxx/xxxx.bin";
      ota_run(ota_host, ota_port, ota_path);
      //Serial.println("Restarting with new firmware ... ");
      //restart = true;
   }
/*
  //Detach Interrupt after 1 Minute
  static uint32_t lastMillis = 0;
  if (millis() - lastMillis > 60000) {
    lastMillis = millis();
    detachInterrupt(button1.PIN);
  Serial.println("Interrupt Detached!");
  }
  */
}
///////
/// OTRIXOTA
void Otrix_OTA()
{
   if(RR_OTA == "1") {
    Serial.println("Otrix-OTA: START");
      char* ota_host = "xxxxxxxx.com";
      uint16_t ota_port = 80;
      char* ota_path = "/xxxxxx/xxxx.bin";
      ota_run(ota_host, ota_port, ota_path);
      //Serial.println("Restarting with new firmware ... ");
      //restart = true;
   }
}
///
/////////////////////////////////////////////
// CONFIG
/////////////////////////////////////////////
/////////////////////////////////////////////
// WIFI
/////////////////////////////////////////////
//void wifi_event(WiFiEvent_t event) {
//  if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) { //                  ARDUINO_EVENT_WIFI_STA_GOT_IP
//    Serial.println("WIFI: Connected");
//    Serial.print("WIFI: IP=");
//    Serial.println(WiFi.localIP());
//  } else if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { // ARDUINO_EVENT_WIFI_STA_DISCONNECTED
//    Serial.println("WIFI: Disconnected");
//  }
//}
/////////////////////////////////////////////
// MQTT
/////////////////////////////////////////////
/////////////////////////////////////////////
// OTA Over-The-Air
/////////////////////////////////////////////
String ota_getHeaderValue(String header, String headerName) {
  return header.substring(strlen(headerName.c_str()));
}
void ota_run(char *ota_host, uint16_t ota_port, char *ota_path) {
#ifdef ESP8266
  ESPhttpUpdate.update(otaClient, ota_host, ota_port, ota_path);
#else // ESP32
  long contentLength = 0;
  bool isValidContentType = false;
  Serial.println("OTA: READY");
  Serial.println("Connecting to: " + String(ota_host));
  // Connect to S3
  if (otaClient.connect(ota_host, ota_port)) {
    // Connection Succeed.
    // Fecthing the bin
    Serial.println("Fetching Bin: " + String(ota_path));
    // Get the contents of the bin file
    otaClient.print(String("GET ") + ota_path + " HTTP/1.1\r\n" +
                 "Host: " + ota_host + "\r\n" +
                 "Cache-Control: no-cache\r\n" +
                 "Connection: close\r\n\r\n");
    // Check what is being sent
     //   Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
     //                "Host: " + host + "\r\n" +
     //                "Cache-Control: no-cache\r\n" +
     //                "Connection: close\r\n\r\n");
    unsigned long timeout = millis();
    while (otaClient.available() == 0) {
      if (millis() - timeout > 5000) {
        Serial.println("Client Timeout !");
        otaClient.stop();
        return;
      }
    }
    // Once the response is available,
    // check stuff
    /*
       Response Structure
        HTTP/1.1 200 OK
        x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
        x-amz-request-id: 2D56B47560B764EC
        Date: Wed, 14 Jun 2017 03:33:59 GMT
        Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
        ETag: "d2afebbaaebc38cd669ce36727152af9"
        Accept-Ranges: bytes
        Content-Type: application/octet-stream
        Content-Length: 357280
        Server: AmazonS3
        {{BIN FILE CONTENTS}}
    */
    while (otaClient.available()) {
      // read line till /n
      String line = otaClient.readStringUntil('\n');
      // remove space, to check if the line is end of headers
      line.trim();
      // if the the line is empty,
      // this is end of headers
      // break the while and feed the
      // remaining `client` to the
      // Update.writeStream();
      if (!line.length()) {
        //headers ended
        break; // and get the OTA started
      }
      // Check if the HTTP Response is 200
      // else break and Exit Update
      if (line.startsWith("HTTP/1.1")) {
        if (line.indexOf("200") < 0) {
          Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
          break;
        }
      }
      // extract headers here
      // Start with content length
      if (line.startsWith("Content-Length: ")) {
        contentLength = atol((ota_getHeaderValue(line, "Content-Length: ")).c_str());
        Serial.println("Got " + String(contentLength) + " bytes from server");
      }
      // Next, the content type
      if (line.startsWith("Content-Type: ")) {
        String contentType = ota_getHeaderValue(line, "Content-Type: ");
        Serial.println("Got " + contentType + " payload.");
        if (contentType == "application/octet-stream") {
          isValidContentType = true;
        }
      }
    }
  } else {
    // Connect to S3 failed
    // May be try?
    // Probably a choppy network?
    Serial.println("Connection to " + String(host) + " failed. Please check your setup");
    // retry??
    // execOTA();
  }
  // Check what is the contentLength and if content type is `application/octet-stream`
  Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
  // check contentLength and content type
  if (contentLength && isValidContentType) {
    // Check if there is enough to OTA Update
    bool canBegin = Update.begin(contentLength);
    // If yes, begin
    if (canBegin) {
      Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
      // No activity would appear on the Serial monitor
      // So be patient. This may take 2 - 5mins to complete
      size_t written = Update.writeStream(otaClient);
      if (written == contentLength) {
        Serial.println("Written : " + String(written) + " successfully");
      } else {
        Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" );
        // retry??
        // execOTA();
      }
      if (Update.end()) {
        Serial.println("OTA done!");
        if (Update.isFinished()) {
          Serial.println("Update successfully completed. Rebooting.");
          ESP.restart();
        } else {
          Serial.println("Update not finished? Something went wrong!");
        }
      } else {
        Serial.println("Error Occurred. Error #: " + String(Update.getError()));
      }
    } else {
      // not enough space to begin OTA
      // Understand the partitions and
      // space availability
      Serial.println("Not enough space to begin OTA");
      otaClient.flush();
    }
  } else {
    Serial.println("There was no content in the response");
    otaClient.flush();
  }
#endif
}
/////////////////////////////////////////////
//
/////////////////////////////////////////////
//////////////////////////////////////////////////////////
void Aboutme()
{
  Serial.println("File name : SMT-005-Dev-303-Asynch-Json-mr-01-01-OK-OTA-Success-Orig");
  Serial.println("I2C-AM2315");
  Serial.println("PZEM004T V3");
  Serial.println("RS485-Soil Sensor");
  Serial.println("RE485-XYMD02");
  Serial.println("Timer 6,7,8 = Relay 6,7,8 ");
  delay(2000);
}
/////////////////////////////////////////////////////////
void setup_XYMD02()
{
  delay(250);
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  pinMode(MAX485_RE_NEG, OUTPUT);
  digitalWrite( MAX485_RE_NEG, LOW );//RX
  RTU_Serial.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
  master.start();
  master.setTimeOut( 1000 ); // if there is no answer in 2000 ms, roll over
  humidity      = 0.00;
  temperature   = 0.00;
  //RTU_Slave_ID = 0;
  //RTU_NEW_Slave_ID = 1;
  //Change_Slave_ID(RTU_Slave_ID, RTU_NEW_Slave_ID);
}
void setup_SMT005(void)
{
  delay(200);
  pinMode(ESP32_LED, OUTPUT);
  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
  Heltec.display->setContrast(255);
  Heltec.display->clear();
  delay(1000);
  I2C_Address_Scan();
  INIT_MCP23017();
  Relay = 0;
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_GPIOB, Relay);//update off all relay
  // DS3231 seconds, minutes, hours, day, date, month, year
  //set_time_ds1307(0, 47, 23, 2, 20, 9, 21);
  m_bitShift  = 0;
  m_gain      = GAIN_TWOTHIRDS;
  m_dataRate  = RATE_ADS1115_128SPS; //RATE_ADS1115_8SPS; //RATE_ADS1115_128SPS;
  delay(20);
  count_read = 0;
  //_service_rtc.attach(1, service_rtc);        // interval  1 sec
  //_service_sensor.attach(5, service_sensor);  // interval  5 sec
  _service_led.attach(0.5, service_led);      // interval  500 msec
  // _service_test_1.attach(1, service_test_1);
  // _service_test_2.attach(2, service_test_2);
  // _service_test_3.attach(3, service_test_3);
  //Show_1_OLED();
  //Show_2_OLED();
}
//===
void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, HIGH); //Switch to transmit data
}
void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, LOW); //Switch to receive data
}
//====
void logo()
{
  Heltec.display->clear();
  Heltec.display->drawXbm(0,5,logo_width,logo_height,logo_bits);
  Heltec.display->display();
}
////////
void WiFisetup()
{
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid1);
  WiFi.begin(ssid1, password1);
  delay(1500);
  if (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Failed to connected ssdi1 and WiFi setup ");
    WiFi.begin(ssid2, password2);
    delay(1500);
    if (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.println("Failed to connected ssdi 2 and WiFi setup ");
    }
    else{
       ssid = ssid2;
       password = password2;
    }
  }
  else{
  ssid = ssid1;
  password = password1;
  Serial.println("");
  Serial.println("WiFi connected OK");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  }
}
////
void RS485_setup()
{
  pinMode(MAX485_RE_NEG, OUTPUT);
  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, LOW);
  // Modbus communication runs at 9600 baud
  // Serial.begin(9600, SERIAL_8N1);
  //RS485Serial.begin(9600); // , SERIAL_8N1, RX_PIN, TX_PIN);
   Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN); // serial can be no1 , no 2  8N1
  modbus.begin(Slave_ID1, Serial2);// Serial2
  // Callbacks allow us to configure the RS485 transceiver correctly
  modbus.preTransmission(preTransmission);
  modbus.postTransmission(postTransmission);
}
void Soil_Sensor_RS485_setup()
{
  pinMode(MAX485_RE_NEG, OUTPUT);
  // Modbus communication runs at 9600 baud
  RS485Serial.begin(9600); // , SERIAL_8N1, RX_PIN, TX_PIN);
}
bool state = true;
/// for gateway
/*
void LoRaData(){
  Heltec.display->clear();
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
  Heltec.display->setFont(ArialMT_Plain_10);
  Heltec.display->drawString(0 , 15 , "Received "+ packSize + " bytes");
  Heltec.display->drawStringMaxWidth(0 , 26 , 128, packet);
  Heltec.display->drawString(0, 0, rssi);
  Heltec.display->display();
}
*/
/*
void cbk(int packetSize) {
  packet ="";
  packSize = String(packetSize,DEC);
  for (int i = 0; i < packetSize; i++) { packet += (char) LoRa.read(); }
  rssi = "RSSI " + String(LoRa.packetRssi(), DEC) ;
  LoRaData();
  //sentpacket =packet;
}
*/
/// for gateway
void WiFiForwardSetup()
{
    delay(10);
    // We start by connecting to a WiFi network
    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, password);
    /*
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    */
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
}
void setup()
{
  //WIFI Kit series V1 not support Vext control
  //mqt_setup();
  //Serial.begin(115200);
  Aboutme();
  OTA_reset_setup();
  setup_XYMD02();
  setup_SMT005();
  WiFi.setAutoReconnect(true);
  WiFi.persistent(true);
  setup_LCD();
  WiFiForwardSetup();
  Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.Heltec.Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);
  // ถ้า true หรือ Enable display จะใช้การ  I2C AM2315 ไม่ได้
  Heltec.display->init();
  Heltec.display->flipScreenVertically();
  Heltec.display->setFont(ArialMT_Plain_10);
  logo();
  delay(1500);
  Heltec.display->clear();
  Heltec.display->drawString(0, 0, "Heltec.LoRa Initial success!");
  Heltec.display->display();
  delay(1000);
  //RS485_setup();
  //Soil_Sensor_RS485_setup();
  //LoRa.onReceive(cbk);
  //LoRa.receive();
}
void loop()
{
  /*
  RS485_loop();
  datasend2 = String(res_dbl0)+","+String(res_dbl1)+","+String(45.00)+","+String(25.75)+","+String(100.05);
  datasend1 = String(cccode)+","+String(177)+","+String(res_dbl0)+","+String(res_dbl1);
  datasend = datasend1+","+datasend2;
  */
  Otrix_OTA(); // OTA
  Serial.println("======================================");
  Serial.print("Version : ");Serial.println(VERSION);
  Serial.println("======================================");
  Heltec.display->clear();
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
  Heltec.display->setFont(ArialMT_Plain_10);
  Heltec.display->drawString(0, 0, "CKC Srithanya : ");
  Heltec.display->drawString(0, 10, "Data : ");
  Heltec.display->drawString(0,20, String(datasend1));
  Heltec.display->drawString(0,30, String(datasend2));
  Heltec.display->display();
  counter=counter+1;
  // send packet
  // LoRa.beginPacket();
  Serial.println("Ser Read position 1 ...");
  // serRead();
  //delay(2000);                       // wait for a second
  //Serial_read_loop(); // remove comment if to connect serial
/*
 * LoRa.setTxPower(txPower,RFOUT_pin);
 * txPower -- 0 ~ 20
 * RFOUT_pin could be RF_PACONFIG_PASELECT_PABOOST or RF_PACONFIG_PASELECT_RFO
 *   - RF_PACONFIG_PASELECT_PABOOST -- LoRa single output via PABOOST, maximum output 20dBm
 *   - RF_PACONFIG_PASELECT_RFO     -- LoRa single output via RFO_HF / RFO_LF, maximum output 14dBm
*/
/*
  LoRa.setTxPower(14,RF_PACONFIG_PASELECT_PABOOST);
  LoRa.print(datasend);
  //LoRa.print(counter);
  LoRa.endPacket();
 */
  //Relay_ON_OFF(RELAY5, ON);
  SMT_loop();
  //RS485_loop2();
  Energy();
  //Soil_SensorTH_loop();
  XYMD02_loop();
  delay(1000);
  if (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Failed to connected and will restart WiFi setup ");
        WiFi.disconnect();
        //WiFi.begin(ssid, password);
        WiFiForwardSetup();
  }
  Forward_loop();
  datasend2 = String(data7)+","+String(80)+","+String(90)+","+String(10);
  datasend1 = String(data3)+","+String(data4)+","+String(55)+","+String(66);
  datasend = datasend1+","+datasend2;
  Serial.println(datasend);
  /*
  LoRa.setTxPower(14,RF_PACONFIG_PASELECT_PABOOST);
  LoRa.print(datasend);
  //LoRa.print(counter);
  LoRa.endPacket();
  LoRa.print(counter);
  */
  Serial.println("Start SerRead loop ... ");
  LCD_display();
  LCD_pH();
  read_master_status_FromServer();
  Relay_Actuator();
  LCD_Pump_Status();
  LCD_Time_Setting();
  LCD_Control_Setting;
  //data1 = sdata11;
  //data2 = sdata12;
  //Relay_Actuator();
  //test_on_relay();
  //test_off_relay();
  OTA_SW_loop();
}
// RS485 loop
void RS485_loop1()
{
  ///=========================
  // Toggle the coil at address 0x0002 (Manual Load Control)
  uint16_t result = modbus.writeSingleCoil(0x0002, state);
  state = !state;
  // Read 16 registers starting at 0x3100)
  result = modbus.readInputRegisters(0x3100, 16);
  if (result == modbus.ku8MBSuccess)
  {
    Serial.print("Vbatt: ");
    Serial.println(modbus.getResponseBuffer(0x04)/100.0f);
    Serial.print("Vload: ");
    Serial.println(modbus.getResponseBuffer(0xC0)/100.0f);
    Serial.print("Pload: ");
    Serial.println((modbus.getResponseBuffer(0x0D) +
                    modbus.getResponseBuffer(0x0E) << 16)/100.0f);
  }
  ///=======
  long currentMillis = millis();
  if (currentMillis - lastMillis > 1000)
  {
    float result = modbus.readHoldingRegisters(0x01,20);  // 0x32 is ok for PM2230 from 10 will get 2 voltage
    // soil sensor from address 02
    if (getResultMsg(&modbus, result))
    {
      Serial.println();
      float res_dbl = modbus.getResponseBuffer(1);
      float value = res_dbl;
      String res = "Voltage A  : " + String(res_dbl) + " Vac\r\n";
      Serial.println(res);
      data2 = String(res_dbl);
      res_dbl = value - modbus.getResponseBuffer(2) ;
      res = "Voltage B  : " + String(res_dbl) + " Vac\r\n";
      Serial.println(res);
      data3 = String(res_dbl);
      res_dbl = modbus.getResponseBuffer(3);
      res = "Voltage C : " + String(res_dbl) + "  \r\n";
      Serial.println(res);
      data4 = String(res_dbl);
      res_dbl = modbus.getResponseBuffer(4);
      res = "Frequency : " + String(res_dbl) + " Vac\r\n";
      Serial.println(res);
      data5 = String(res_dbl);
      res_dbl = modbus.getResponseBuffer(5);
      res = "Hz : " + String(res_dbl) + " Vac\r\n";
      Serial.println(res);
      data6 = String(res_dbl);
      res_dbl = modbus.getResponseBuffer(6)/100;
      res = "Value 26 : " + String(res_dbl) + " Vac\r\n";
      Serial.println(res);
      data7 = String(res_dbl);
      res_dbl = modbus.getResponseBuffer(7)/100;
      res = "Power : " + String(res_dbl) + " watt\r\n";
      Serial.println(res);
      data8 = String(res_dbl);
      res_dbl = modbus.getResponseBuffer(8);
      res = "Value 28 : " + String(res_dbl) + " Vac\r\n";
      Serial.println(res);
      //delay(2000);
      data9 = String(res_dbl);
    }
    lastMillis = currentMillis;
  }
}
bool getResultMsg(ModbusMaster *node, uint16_t result)
{
  String tmpstr2 = "\r\n";
  switch (result)
  {
  case node->ku8MBSuccess:
    return true;
    break;
  case node->ku8MBIllegalFunction:
    tmpstr2 += "Illegal Function";
    break;
  case node->ku8MBIllegalDataAddress:
    tmpstr2 += "Illegal Data Address";
    break;
  case node->ku8MBIllegalDataValue:
    tmpstr2 += "Illegal Data Value";
    break;
  case node->ku8MBSlaveDeviceFailure:
    tmpstr2 += "Slave Device Failure";
    break;
  case node->ku8MBInvalidSlaveID:
    tmpstr2 += "Invalid Slave ID";
    break;
  case node->ku8MBInvalidFunction:
    tmpstr2 += "Invalid Function";
    break;
  case node->ku8MBResponseTimedOut:
    tmpstr2 += "Response Timed Out";
    break;
  case node->ku8MBInvalidCRC:
    tmpstr2 += "Invalid CRC";
    break;
  default:
    tmpstr2 += "Unknown error: " + String(result);
    break;
  }
  Serial.println(tmpstr2);
  return false;
}
///////////////////////////////////
int value = 0;
void Forward_loop()
{
    ++value;
    Serial.print("connecting to server Otrixiot .... ");
    Serial.println(host);
    // Use WiFiClient class to create TCP connections
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host, httpPort)) {
        Serial.println("connection failed");
        return;
    }
    // We now create a URI for the request
     String url = "/api/insertData?device_id=" + String(iddevice)+"&code="+String(ccode)+"&data1=" +String(11) +"&data2="
   + String(22)+"&data3=" +String(data3)+"&data4=" +String(data4)+"&data5=" +String(55)
   +"&data6=" +String(6)+"&data7=" +String(7)+"&data8=" +String(8)+"&data9=" +String(9)
   +"&data10=" +String(10)+"&data11=" +String(11)+"&data12=" +String(12)+"&data13=" +String(13)
   +"&data14=" +String(14)+"&data15=" +String(15)+"&data16=" +String(16)+"&data17=" +String(17)
   +"&data18=" +String(18)+"&data19=" +String(19)+"&data20=" +String(20);
    Serial.print("Requesting URL: ");
    Serial.println(url);
    // This will send the request to the server
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "Connection: close\r\n\r\n");
    unsigned long timeout = millis();
    while (client.available() == 0) {
        if (millis() - timeout > 5000) {
            Serial.println(">>> Client Timeout !");
            client.stop();
            return;
        }
    }
    // Read all the lines of the reply from server and print them to Serial
    while(client.available()) {
        String line = client.readStringUntil('\r');
        //Serial.print(line);
    }
    Serial.println();
    Serial.println("closing connection");
    delay(1000);
}
//====
//====
void splint_string(char sz[]){  // สร้างฟังชันต์ชื่อ splint_string กำหนดตัวแปรนำเข้าชื่อ sz ชนิด char แบบอาเรย์
  char *p = sz;  // สร้างตัวแปรชื่อ p ชนิด Pointer มีค่าเท่ากับ sz
  char *str;  // สร้างตัวแปรชื่อ str ชนิด Pointer
  int counter = 0;  // สร้างตัวแปรชื่อ counter ชนิด int สำหรับทำการนับครั้งที่ตัด
  while ((str = strtok_r(p, ",", &p)) != NULL){  // วนทำลูป while ซ้ำ โดยเรียกฟังชันต์ strtok_r() โดยทำการตัดค่าใน p เมื่อเจอเครื่องหมาย','
   // Serial.print(counter + String(". "));  // แสดงผลจำนวนครั้งที่ตัด
   // Serial.println(str); // แสดงผลค่าที่ตัดได้
   counter++;
   if (counter ==1){ccode = str;}
   if (counter ==2){iddevice   = str;}
   if (counter ==3){data1 = str;}
   if (counter ==4){data2 = str;}
   if (counter ==5){data3 = str;}
   if (counter ==6){data4 = str;}
   if (counter ==7){data5 = str;}
   if (counter ==8){data6 = str;}
  }
  counter = 0;  // เคลียร์ค่าใน counter เป็น 0
}
void dataread_loop()
{
  //char charBuf[100];
  str = packet;
  str.toCharArray(charBuf, 100);  // คัดลอกอักขระของชุดอักขระไปยังตัวแปร charBuf
  splint_string(charBuf); // เรียกใช้งานฟังชั่น Splint String
  //delay(1000);
}
void RS485_loop2()
{
  ///=========================
  preTransmission();
  digitalWrite(RX_PIN,HIGH);
  ///=======
  long currentMillis = millis();
  if (currentMillis - lastMillis > 1000)
  {
    uint8_t result = modbus.readHoldingRegisters(0x02,2);  // 0x32 is ok for PM2230 from 10 will get 2 voltage
    if (getResultMsg(&modbus, result))
    {
      Serial.println();
      float res_dbl = modbus.getResponseBuffer(0)/10;
      float value = res_dbl;
      String res = "data11 Soil Humidity : " + String(res_dbl) + " %\r\n";
      Serial.println(res);
      sdata11 = String(res_dbl);
      res_dbl = value - modbus.getResponseBuffer(1)/10 ;
      res = "data12 Soil Temp : " + String(res_dbl) + " C\r\n";
      Serial.println(res);
      sdata12 = String(res_dbl);
    }
    lastMillis = currentMillis;
  }
}
void PumpTrial()
{
  Serial.println("Starting pump for 1 minutes ...");
  //MainPumpRun();
  //delay(1000);
}
void ResetSystem()
{
  digitalRead(3);
  ResetPinValue = String(digitalRead(3));
  if (ResetPinValue == "1")
  {
    PumpTrial();
  }
}
void MainPumpRun()
{
   //Relay_ON_OFF(RELAY8, ON);
}
void MainPumpStop()
{
   //Relay_ON_OFF(RELAY8, OFF);
}
void RequestFlowLowStatus()
{
}
////  read  control ====
void read_master_status_FromServer()
{
 Serial.print("Reading control command from Server ... connecting to ");
 Serial.println(host);
WiFiClient client;
delay(1500);
if (client.connect(host, 80))
  {
    Serial.println("reconnecting...");
         url = "/api/getRealyStatus/"+String(dID)+"/"+String(code)+"/"+"abZYrshRYR243askdSKSKSK5646dkfmTURDsand";
         Serial.print("Requesting URL: ");
         //Serial.println(url); // comment to prevent hacker
         client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + host + "\r\n" +
                 "Connection: close\r\n\r\n");
         delay(2000);
         String section="header";
         while(client.available())
         {
            //Serial.println("Connection available ");
            String line = client.readStringUntil('\r');
            //Serial.print(line);
            // we’ll parse the HTML body here
            if (section=="header")
              { // headers..
                  if (line=="\n")
                    { // skips the empty space at the beginning
                      section="json";
                    }
              }
            else if (section=="json")
              {  // print the good stuff
                  section="ignore";
                  String result = line.substring(1);
                  // Parse JSON
                  int size = result.length() + 1;
                  char json[size];
                  result.toCharArray(json, size);
                  StaticJsonDocument <2000> doc;
                  //StaticJsonBuffer<2000> jsonBuffer;
                  DeserializationError error = deserializeJson(doc, json);
                  //JsonObject& json_parsed = jsonBuffer.parseObject(json);
                  //if (!json_parsed.success())
                  //  {
                  //    Serial.println("parseObject() failed");
                  //    return;
                  //  }
                  if (error)
                     return;
                      Serial.println("parseObject() OK ...");
                      //Serial.println(result); // show all json found
                      //String led = json_parsed["led"][0]["status"];
                      String master_state = doc["result"]["masterStatus"];
                      String led1 = doc["result"]["relayStatus"]["data6_H"];
                      String led2 = doc["result"]["relayStatus"]["data6_L"];
                      String led3 = doc["result"]["relayStatus"]["data3_status"];
                      String FlowBypass = doc["result"]["relayStatus"]["data5_status"];
                      String RR1 = doc["result"]["relayStatus"]["data1_status"];
                      String RR2 = doc["result"]["relayStatus"]["data2_status"];
                      String RR3 = doc["result"]["relayStatus"]["data3_status"];
                      String RR4 = doc["result"]["relayStatus"]["data4_status"];
                      String RR5 = doc["result"]["relayStatus"]["data5_status"];
                      String RR6 = doc["result"]["relayStatus"]["data6_status"]; // Timer relay 6 in platform
                      String RR7 = doc["result"]["relayStatus"]["data7_status"]; // Timer relay 7 in platform
                      String RR8 = doc["result"]["relayStatus"]["data8_status"]; // Timer relay 8 in platform
                      String R_OTA = doc["result"]["relayStatus"]["data10_status"]; // Timer relay 8 in platform
                      R1 = RR1; R2 = RR2; R3 = RR3; R4 = RR4; R5 = RR5; R6 = RR6; R7 = RR7;  R8 = RR8;
                      RR_OTA=String(R_OTA);
                      Serial.println("Relay : "+String(R1)+" "+String(R2)+" "+String(R3)+" "+String(R4)+" "+String(R5)+" "+String(R6)+" "+String(R7)+" "+String(R8));
                      // string led = json_parsed["table name""][array number]["value of field"]
                      Serial.print("Relay 1 := ");Serial.println(led1);
                      Serial.print("Master state = ");Serial.println(master_state);
                      Serial.print("R8 for Relay : ");Serial.println(R8);
                      Serial.print("RR_OTA1  : ");Serial.println(RR_OTA);
                      Time1 = led1 ;
                      Time2 = led2 ;
                      /*
                      if (R3 == "1")
                      {
                        Relay_ON_OFF(RELAY3, ON);
                      }
                      else
                      {
                        Relay_ON_OFF(RELAY3, OFF);
                      }
                      */
                      if (master_state == "0")
                      {
                        MainPumpStop();
                        MainPump = "Stop";
                      }
                      else
                      {
                        // this loop master_state == "1
                        if (R7 == "1")
                        {
                              MainPumpRun();
                              MainPump = "Run" ;
                              //delay(5000);
                              /// check flow sensor
                              //RequestFlowLowStatus();
                              if (FlowBypass == "1")
                              {
                                  MainPumpRun();
                                  Serial.println("Water Pump in By-pass Mode .. please take care ...");
                                  FlowBypass = "By-Pass";
                                  delay(1000);
                                  flowsensor ="--";
                              }
                              else
                              {
                                  if(FlowLowStatus == "1")
                                  {
                                    MainPumpRun();
                                    flowsensor = "OK";
                                  }
                                  else
                                  {
                                    MainPumpStop();
                                    Serial.println("Flow Low pump will stop ");
                                    flowsensor = "NOK";
                                  }
                                  FlowBypass = "Protecะed";
                              }
                        }
                        else
                        {
                        Serial.println("Flow low stop pump check and reset the system ... ");
                        flowsensor = "NOK";
                        MainPumpStop();
                        }
                      }
                      /*
                      if (led1 = "0")
                      {
                        digitalWrite(13,LOW);
                        digitalWrite(15,LOW);
                      }
                      else
                      {
                        digitalWrite(13,HIGH);
                        digitalWrite(15,HIGH);
                      }
                        */
                      Serial.print("Pump Start     : ");Serial.println(led1);
                      Serial.print("Pump Stop      : ");Serial.println(led2);
                      Serial.print("Led3           : ");Serial.println(led3);
                      Serial.print("Master Status  : ");Serial.println(master_state);
                      Serial.print("Timer Relay R5 : ");Serial.println(R5);
                      Serial.print("By-Pass Status : ");Serial.println(FlowBypass);
                      Serial.println("Relay : "+String(R1)+" "+String(R2)+" "+String(R3)+" "+String(R4)+" "+String(R5)+" "+String(R6)+" "+String(R7)+" "+String(R8));
                      RR_OTA=String(R_OTA);
                      Serial.print("RR_OTA2  : ");Serial.println(RR_OTA);
                       //
                      delay(1000);
                      //LCD_Pump_Status();
             } // if found json
          } // end while client available
    }
    // end if host connected
    else
    {
      // if you couldn't make a connection:
      ///Serial.println("connection failed read server 1");
    }
}
void LCD_display()
{
}
void PumpARun() // run Pump A 10 min
{
  if (EC <= 1000)
  {
  //digitalWrite(RELAY8,HIGH);
  //Relay_ON_OFF(RELAY7, ON);
  Serial.println("Pump A Run");
  }
  else
  {
  //digitalWrite(RELAY8,LOW);
  Relay_ON_OFF(RELAY7, OFF);
  Serial.println("Pump A Stop");
  }
}
void PumpBRun() // open water valve 12 Vdc
{
  //digitalWrite(Relay2,HIGH);
  //Relay_ON_OFF(RELAY8, ON);
  Serial.println("Pump B Run");
}
/**************************************************************
  Function Name   : loop
  Description     :
  Input           :
  Return          :
**************************************************************/
void SMT_loop()
{
  service_rtc();
  service_sensor();
}
void service_rtc()
{
  String  str1 = "";
  ds1307_display_Time();
  str1 = "";
  Heltec.display->clear();
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
  Heltec.display->setFont(ArialMT_Plain_24);//ArialMT_Plain_24   ArialMT_Plain_16
  if (global_hour < 10)
  {
    str1 = "0";
  }
  str1 += String(global_hour) + " : ";
  if (global_minute < 10)
  {
    str1 += "0";
  }
  str1 += String(global_minute) + " : ";
  if (global_second < 10)
  {
    str1 += "0";
  }
  str1 += String(global_second);
  Heltec.display->drawString(0, 0, str1);
  Heltec.display->setFont(ArialMT_Plain_16);//ArialMT_Plain_24   ArialMT_Plain_16
  str1 = "";
  str1 = "Temp : ";
  str1 += String(data3) + " .C"; // temp
  Heltec.display->drawString(0, 25, str1);
  str1 = "";
  str1 = "Hum   : ";
  str1 += String(data4) + "  %";  // humidity
  Heltec.display->drawString(0, 45, str1);
  Heltec.display->display();
}
/**************************************************************
  Function Name   : service_sensor
  Description     :
  Input           :
  Return          :
**************************************************************/
void service_sensor()
{
  count_read++;
  if (!AM2315_ReadData())
  {
    Serial.println("Failed to read data from AM2315");
  }
  Serial.print(" >> Count-Read : ");
  Serial.print(count_read);
  Serial.print("  >> Temp *C: "); Serial.print(temp);
  Serial.print("  >> Hum %: "); Serial.println(humidity);
  data1 = String(temp);
  data2 = String(humidity);
}
/**************************************************************
  Function Name   : service_led
  Description     :
  Input           :
  Return          :
**************************************************************/
void service_led()
{
  digitalWrite(ESP32_LED, !digitalRead(ESP32_LED));
}
/**************************************************************
  Function Name   : AM2315_ReadData
  Description     :
  Input           :
  Return          :
**************************************************************/
uint8_t AM2315_ReadData()
{
  uint8_t reply[10];
  // Wake up the sensor
  Wire.beginTransmission(AM2315_I2CADDR);
  delay(10);
  Wire.endTransmission();
  delay(5);
  // OK lets ready!
  Wire.beginTransmission(AM2315_I2CADDR);
  Wire.write(AM2315_READREG);
  Wire.write(0x00); // start at address 0x0
  Wire.write(4);    // request 4 bytes data
  Wire.endTransmission();
  delay(10); // add delay between request and actual read!
  Wire.requestFrom(AM2315_I2CADDR, 8);
  for (uint8_t i = 0; i < 8; i++) {
    reply[i] = Wire.read();
  }
  if (reply[0] != AM2315_READREG)
    return false;
  if (reply[1] != 4)
    return false; // bytes req'd
  humidity = reply[2];
  humidity *= 256;
  humidity += reply[3];
  humidity /= 10;
  // Serial.print("H"); Serial.println(humidity);
  temp = reply[4] & 0x7F;
  temp *= 256;
  temp += reply[5];
  temp /= 10;
  // Serial.print("T"); Serial.println(temp);
  // change sign
  if (reply[4] >> 7)
    temp = -temp;
  return true;
}
//#############################################################
//###################  ADC ADS1115  ###########################
//#############################################################
/**************************************************************
  Function Name   : test_read_adc
  Description     :
  Input           :
  Return          :
**************************************************************/
void test_read_adc(uint8_t add)
{
  i2c_address_adc = add;
  adc0 = ReadADC_SingleEnded(0);
  adc1 = ReadADC_SingleEnded(1);
  adc2 = ReadADC_SingleEnded(2);
  adc3 = ReadADC_SingleEnded(3);
  volts0 = computeVolts(adc0);
  volts1 = computeVolts(adc1);
  volts2 = computeVolts(adc2);
  volts3 = computeVolts(adc3);
  if (add == ADDRESS_ADC1_U1)
  {
    Serial.print(" AIN1: "); Serial.print("  "); Serial.print(volts0, 5); Serial.println("V");
    Serial.print(" AIN2: "); Serial.print("  "); Serial.print(volts1, 5); Serial.println("V");
    Serial.print(" AIN3: "); Serial.print("  "); Serial.print(volts2, 5); Serial.println("V");
    Serial.print(" AIN4: "); Serial.print("  "); Serial.print(volts3, 5); Serial.println("V");
  }
  else
  {
    Serial.print(" AIN5: "); Serial.print("  "); Serial.print(volts0, 5); Serial.println("V");
    Serial.print(" AIN6: "); Serial.print("  "); Serial.print(volts1, 5); Serial.println("V");
    Serial.print(" AIN7: "); Serial.print("  "); Serial.print(volts2, 5); Serial.println("V");
    Serial.print(" AIN8: "); Serial.print("  "); Serial.print(volts3, 5); Serial.println("V");
  }
}
/**************************************************************
  Function Name   : ReadADC_SingleEnded
  Description     :
  Input           :
  Return          :
**************************************************************/
int16_t ReadADC_SingleEnded(uint8_t channel)
{
  if (channel > 3) {
    return 0;
  }
  // Start with default values
  uint16_t config =
    ADS1X15_REG_CONFIG_CQUE_NONE |    // Disable the comparator (default val)
    ADS1X15_REG_CONFIG_CLAT_NONLAT |  // Non-latching (default val)
    ADS1X15_REG_CONFIG_CPOL_ACTVLOW | // Alert/Rdy active low   (default val)
    ADS1X15_REG_CONFIG_CMODE_TRAD |   // Traditional comparator (default val)
    ADS1X15_REG_CONFIG_MODE_SINGLE;   // Single-shot mode (default)
  // Set PGA/voltage range
  config |= m_gain;
  // Set data rate
  config |= m_dataRate;
  // Set single-ended input channel
  switch (channel) {
    case (0):
      config |= ADS1X15_REG_CONFIG_MUX_SINGLE_0;
      break;
    case (1):
      config |= ADS1X15_REG_CONFIG_MUX_SINGLE_1;
      break;
    case (2):
      config |= ADS1X15_REG_CONFIG_MUX_SINGLE_2;
      break;
    case (3):
      config |= ADS1X15_REG_CONFIG_MUX_SINGLE_3;
      break;
  }
  // Set 'start single-conversion' bit
  config |= ADS1X15_REG_CONFIG_OS_SINGLE;
  // Write config register to the ADC
  ADS1115_Write_Reg(ADS1X15_REG_POINTER_CONFIG, config);
  // Wait for the conversion to complete
  while (!conversionComplete())
    ;
  // Read the conversion results
  return getLastConversionResults();
}
/**************************************************************
  Function Name   : computeVolts
  Description     :
  Input           :
  Return          :
**************************************************************/
float computeVolts(int16_t counts)
{
  // see data sheet Table 3
  float fsRange;
  switch (m_gain) {
    case GAIN_TWOTHIRDS:
      fsRange = 6.144f;
      break;
    case GAIN_ONE:
      fsRange = 4.096f;
      break;
    case GAIN_TWO:
      fsRange = 2.048f;
      break;
    case GAIN_FOUR:
      fsRange = 1.024f;
      break;
    case GAIN_EIGHT:
      fsRange = 0.512f;
      break;
    case GAIN_SIXTEEN:
      fsRange = 0.256f;
      break;
    default:
      fsRange = 0.0f;
  }
  return counts * (fsRange / (32768 >> m_bitShift));
}
/**************************************************************
  Function Name   : getLastConversionResults
  Description     :
  Input           :
  Return          :
**************************************************************/
int16_t getLastConversionResults()
{
  // Read the conversion results
  uint16_t res = ADS1115_Read_Reg(ADS1X15_REG_POINTER_CONVERT) >> m_bitShift;
  if (m_bitShift == 0) {
    return (int16_t)res;
  } else {
    // Shift 12-bit results right 4 bits for the ADS1015,
    // making sure we keep the sign bit intact
    if (res > 0x07FF) {
      // negative number - extend the sign to 16th bit
      res |= 0xF000;
    }
    return (int16_t)res;
  }
}
/**************************************************************
  Function Name   : conversionComplete
  Description     :
  Input           :
  Return          :
**************************************************************/
uint8_t conversionComplete()
{
  return (ADS1115_Read_Reg(ADS1X15_REG_POINTER_CONFIG) & 0x8000) != 0;
}
/**************************************************************
  Function Name   : ADS1115_Write_Reg
  Description     :
  Input           :
  Return          :
**************************************************************/
void ADS1115_Write_Reg(uint8_t reg, uint16_t value)
{
  uint8_t temp[4];
  temp[0] = reg;
  temp[1] = value >> 8;
  temp[2] = value & 0xFF;
  Wire.beginTransmission(i2c_address_adc);
  Wire.write(temp[0]);
  Wire.write(temp[1]);
  Wire.write(temp[2]);
  Wire.endTransmission();
}
/**************************************************************
  Function Name   : ADS1115_Read_Reg
  Description     :
  Input           :
  Return          :
**************************************************************/
uint16_t ADS1115_Read_Reg(uint8_t reg)
{
  uint8_t temp[4];
  temp[0] = reg;
  Wire.beginTransmission(i2c_address_adc);
  Wire.write(temp[0]);
  Wire.endTransmission();
  Wire.requestFrom(i2c_address_adc, 2);
  delayMicroseconds(10);
  temp[0] = Wire.read();
  temp[1] = Wire.read();
  return ((temp[0] << 8) | temp[1]);
}
//#############################################################
//######################   DS1307  ############################
//#############################################################
/**************************************************************
  Function Name   : decToBcd
  Description     :
  Input           :
  Return          :
**************************************************************/
byte decToBcd(byte val)
{
  return ( (val / 10 * 16) + (val % 10) );
}
/**************************************************************
  Function Name   : bcdToDec
  Description     :
  Input           :
  Return          :
**************************************************************/
byte bcdToDec(byte val)
{
  return ( (val / 16 * 10) + (val % 16) );
}
/**************************************************************
  Function Name   : set_time_ds1307
  Description     :
  Input           :
  Return          :
**************************************************************/
void set_time_ds1307(
  byte second,
  byte minute,
  byte hour,
  byte dayOfWeek,
  byte dayOfMonth,
  byte month,
  byte year)
{
  // sets time and date data to DS1307
  Wire.beginTransmission(ADDRESS_DS1307);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}
/**************************************************************
  Function Name   : read_time_ds1307
  Description     :
  Input           :
  Return          :
**************************************************************/
void read_time_ds1307(
  byte *second,
  byte *minute,
  byte *hour,
  byte *dayOfWeek,
  byte *dayOfMonth,
  byte *month,
  byte *year)
{
  Wire.beginTransmission(ADDRESS_DS1307);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(ADDRESS_DS1307, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second       =   bcdToDec(Wire.read() & 0x7f);
  *minute       =   bcdToDec(Wire.read());
  *hour         =   bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek    =   bcdToDec(Wire.read());
  *dayOfMonth   =   bcdToDec(Wire.read());
  *month        =   bcdToDec(Wire.read());
  *year         =   bcdToDec(Wire.read());
}
/**************************************************************
  Function Name   : ds1307_display_Time
  Description     :
  Input           :
  Return          :
**************************************************************/
void ds1307_display_Time()
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  // retrieve data from DS1307
  read_time_ds1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
                   &year);
  // send it to the serial monitor
  Serial.print(hour, DEC);
  // convert the byte variable to a decimal number when displayed
  Serial.print(":");
  if (minute < 10) {
    Serial.print("0");
  }
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second < 10) {
    Serial.print("0");
  }
  Serial.print(second, DEC);
  Serial.print(" ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.println(year, DEC);
  global_second       = second;
  global_minute       = minute;
  global_hour         = hour;
  global_dayOfMonth   = dayOfMonth;
  global_month        = month;
  global_year         = year;
  global_dayOfWeek = dayOfWeek;
  //Serial.print(" Day of week: ");
  /*Serial.print(" : ");
    switch (dayOfWeek) {
    case 1:
      Serial.println("Sunday");
      break;
    case 2:
      Serial.println("Monday");
      break;
    case 3:
      Serial.println("Tuesday");
      break;
    case 4:
      Serial.println("Wednesday");
      break;
    case 5:
      Serial.println("Thursday");
      break;
    case 6:
      Serial.println("Friday");
      break;
    case 7:
      Serial.println("Saturday");
      break;
    }*/
}
//#############################################################
//#################### MCP23017  ##############################
//#############################################################
/**************************************************************
  Function Name   : test_read_mcp23017
  Description     :
  Input           :
  Return          :
**************************************************************/
void test_read_mcp23017()
{
  gpio_in = Read_GPIO_MCP23017(ADDRESS_MCP23017);
  //Serial.println(gpio_in);
  //Serial.println("-------------------------------------");
  if (CHECKBIT(gpio_in, 0))
  {
    Serial.println(" Read Logic In GPA-0 P0 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-0 P0 = 0 ");
  }
  if (CHECKBIT(gpio_in, 1))
  {
    Serial.println(" Read Logic In GPA-1 P1 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-1 P1 = 0 ");
  }
  if (CHECKBIT(gpio_in, 2))
  {
    Serial.println(" Read Logic In GPA-2 P2 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-2 P2 = 0 ");
  }
  if (CHECKBIT(gpio_in, 3))
  {
    Serial.println(" Read Logic In GPA-3 P3 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-3 P3 = 0 ");
  }
  if (CHECKBIT(gpio_in, 4))
  {
    Serial.println(" Read Logic In GPA-4 P4 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-4 P4 = 0 ");
  }
  if (CHECKBIT(gpio_in, 5))
  {
    Serial.println(" Read Logic In GPA-5 P5 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-5 P5 = 0 ");
  }
  if (CHECKBIT(gpio_in, 6))
  {
    Serial.println(" Read Logic In GPA-6 P6 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-6 P6 = 0 ");
  }
  if (CHECKBIT(gpio_in, 7))
  {
    Serial.println(" Read Logic In GPA-7 P7 = 1 ");
  }
  else
  {
    Serial.println(" Read Logic In GPA-7 P7 = 0 ");
  }
  //Serial.println("-------------------------------------");
  delay(100);
}
/**************************************************************
  Function Name   : test_off_relay
  Description     :
  Input           :
  Return          :
**************************************************************/
void test_off_relay()
{
  delayx = 200;
  Relay_ON_OFF(RELAY1, OFF); delay(delayx);
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY2, OFF); delay(delayx);
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY3, OFF); delay(delayx);
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY4, OFF); delay(delayx);
  //test_read_mcp23017();
  //Relay_ON_OFF(RELAY5, OFF); delay(delayx);
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY6, OFF); delay(delayx);
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY7, OFF); delay(delayx);
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY8, OFF); delay(delayx);
  //test_read_mcp23017();
}
/**************************************************************
  Function Name   : test_on_relay
  Description     :
  Input           :
  Return          :
**************************************************************/
void test_on_relay()
{
  delayx = 200;
  Relay_ON_OFF(RELAY1, ON); delay(delayx);
  Serial.println("Relay 1 ");
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY2, ON); delay(delayx);
  Serial.println("Relay 2 ");
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY3, ON); delay(delayx);
  Serial.println("Relay 3 ");
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY4, ON); delay(delayx);
  Serial.println("Relay 4 ");
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY5, ON); delay(delayx);
  Serial.println("Relay 5 ");
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY6, ON); delay(delayx);
  Serial.println("Relay 6 ");
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY7, ON); delay(delayx);
  Serial.println("Relay 7 ");
  //test_read_mcp23017();
  Relay_ON_OFF(RELAY8, ON); delay(delayx);
  Serial.println("Relay 8 ");
  //test_read_mcp23017();
}
/**************************************************************
  Function Name   : Relay_ON_OFF
  Description     :
  Input           :
  Return          :
**************************************************************/
void Relay_ON_OFF(uint8_t relayx, uint8_t onoff)
{
  if (onoff)
  {
    SETBIT(Relay, relayx);
  }
  else
  {
    CLEARBIT(Relay, relayx);
  }
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_GPIOB, Relay);
}
/**************************************************************
  Function Name   : INIT_MCP23017
  Description     :
  Input           :
  Return          :
**************************************************************/
void INIT_MCP23017()
{
  // set defaults!
  // all inputs on port A and B
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_IODIRA, 0xFF);//input
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_IODIRB, 0x00);//Relay output
  // Turn off interrupt triggers
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_GPINTENA, 0x00);
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_GPINTENB, 0x00);
  // Turn off pull up resistors
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_GPPUA, 0x00);
  Write_MCP23017(ADDRESS_MCP23017, MCP23017_GPPUB, 0x00);
}
/**************************************************************
  Function Name   : Read_GPIO_MCP23017
  Description     :
  Input           :
  Return          :
**************************************************************/
uint8_t Read_GPIO_MCP23017(uint8_t addr)
{
  uint8_t x;
  Wire.beginTransmission(addr);
  Wire.write(MCP23017_GPIOA);
  Wire.endTransmission();
  delayMicroseconds(10);
  Wire.requestFrom(addr, 1);
  while (Wire.available())
  {
    x = Wire.read();
  }
  return x;
}
/**************************************************************
  Function Name   : Write_MCP23017
  Description     :
  Input           :
  Return          :
**************************************************************/
void Write_MCP23017(uint8_t addr, uint8_t reg, uint8_t dat)
{
  Wire.beginTransmission(addr); //begins talking to the slave device
  Wire.write(reg); //selects the IODIRA register
  Wire.write(dat); //this sets all port A pins to outputs
  Wire.endTransmission();
  delayMicroseconds(20);
}
/**************************************************************
  Function Name   : Show_1_OLED
  Description     :
  Input           :
  Return          :
**************************************************************/
void Show_1_OLED(void)
{
  drawLines();
  delay(1000);
  Heltec.display->clear();
  drawRect();
  delay(1000);
  Heltec.display->clear();
  fillRect();
  delay(1000);
  Heltec.display->clear();
  drawCircle();
  delay(1000);
  Heltec.display->clear();
  printBuffer();
  delay(1000);
  Heltec.display->clear();
  delay(1000);
}
/**************************************************************
  Function Name   : Show_2_OLED
  Description     :
  Input           :
  Return          :
**************************************************************/
void Show_2_OLED(void)
{
  uint16_t del = 500;
  for (int i = 0; i < 2; i++)
  {
    Heltec.display->setTextAlignment(TEXT_ALIGN_CENTER);
    Heltec.display->clear();
    Heltec.display->display();
    Heltec.display->screenRotate(ANGLE_0_DEGREE);
    Heltec.display->setFont(ArialMT_Plain_16);
    Heltec.display->drawString(64, 32 - 16 / 2, "ROTATE_0");
    Heltec.display->display();
    delay(del);
    Heltec.display->clear();
    Heltec.display->display();
    Heltec.display->screenRotate(ANGLE_90_DEGREE);
    Heltec.display->setFont(ArialMT_Plain_10);
    Heltec.display->drawString(32, 64 - 10 / 2, "ROTATE_90");
    Heltec.display->display();
    delay(del);
    Heltec.display->clear();
    Heltec.display->display();
    Heltec.display->screenRotate(ANGLE_180_DEGREE);
    Heltec.display->setFont(ArialMT_Plain_16);
    Heltec.display->drawString(64, 32 - 16 / 2, "ROTATE_180");
    Heltec.display->display();
    delay(del);
    Heltec.display->clear();
    Heltec.display->display();
    Heltec.display->screenRotate(ANGLE_270_DEGREE);
    Heltec.display->setFont(ArialMT_Plain_10);
    Heltec.display->drawString(32, 64 - 10 / 2, "ROTATE_270");
    Heltec.display->display();
    delay(del);
  }
  Heltec.display->setTextAlignment(TEXT_ALIGN_CENTER);
  Heltec.display->clear();
  Heltec.display->display();
  Heltec.display->screenRotate(ANGLE_0_DEGREE);
  Heltec.display->setFont(ArialMT_Plain_16);
  Heltec.display->drawString(64, 32 - 16 / 2, "ROTATE_0");
  Heltec.display->display();
  delay(del);
  delay(del);
  Heltec.display->clear();
  Heltec.display->display();
}
/**************************************************************
  Function Name   : drawLines
  Description     :
  Input           :
  Return          :
  // Adapted from Adafruit_SSD1306
**************************************************************/
void drawLines(void)
{
  for (int16_t i = 0; i < DISPLAY_WIDTH; i += 4) {
    Heltec.display->drawLine(0, 0, i, DISPLAY_HEIGHT - 1);
    Heltec.display->display();
    delay(20);
  }
  for (int16_t i = 0; i < DISPLAY_HEIGHT; i += 4) {
    Heltec.display->drawLine(0, 0, DISPLAY_WIDTH - 1, i);
    Heltec.display->display();
    delay(20);
  }
  delay(250);
  Heltec.display->clear();
  for (int16_t i = 0; i < DISPLAY_WIDTH; i += 4) {
    Heltec.display->drawLine(0, DISPLAY_HEIGHT - 1, i, 0);
    Heltec.display->display();
    delay(20);
  }
  for (int16_t i = DISPLAY_HEIGHT - 1; i >= 0; i -= 4) {
    Heltec.display->drawLine(0, DISPLAY_HEIGHT - 1, DISPLAY_WIDTH - 1, i);
    Heltec.display->display();
    delay(20);
  }
  delay(250);
  Heltec.display->clear();
  for (int16_t i = DISPLAY_WIDTH - 1; i >= 0; i -= 4) {
    Heltec.display->drawLine(DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, i, 0);
    Heltec.display->display();
    delay(20);
  }
  for (int16_t i = DISPLAY_HEIGHT - 1; i >= 0; i -= 4) {
    Heltec.display->drawLine(DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, 0, i);
    Heltec.display->display();
    delay(20);
  }
  delay(250);
  Heltec.display->clear();
  for (int16_t i = 0; i < DISPLAY_HEIGHT; i += 4) {
    Heltec.display->drawLine(DISPLAY_WIDTH - 1, 0, 0, i);
    Heltec.display->display();
    delay(20);
  }
  for (int16_t i = 0; i < DISPLAY_WIDTH; i += 4) {
    Heltec.display->drawLine(DISPLAY_WIDTH - 1, 0, i, DISPLAY_HEIGHT - 1);
    Heltec.display->display();
    delay(20);
  }
  delay(250);
}
/**************************************************************
  Function Name   : drawRect
  Description     :
  Input           :
  Return          :
  // Adapted from Adafruit_SSD1306
**************************************************************/
void drawRect(void)
{
  for (int16_t i = 0; i < DISPLAY_HEIGHT / 2; i += 2)
  {
    Heltec.display->drawRect(i, i, DISPLAY_WIDTH - 2 * i, DISPLAY_HEIGHT - 2 * i);
    Heltec.display->display();
    delay(20);
  }
}
/**************************************************************
  Function Name   : fillRect
  Description     :
  Input           :
  Return          :
  // Adapted from Adafruit_SSD1306
**************************************************************/
void fillRect(void) {
  uint8_t color = 1;
  for (int16_t i = 0; i < DISPLAY_HEIGHT / 2; i += 3)
  {
    Heltec.display->setColor((color % 2 == 0) ? BLACK : WHITE); // alternate colors
    Heltec.display->fillRect(i, i, DISPLAY_WIDTH - i * 2, DISPLAY_HEIGHT - i * 2);
    Heltec.display->display();
    delay(20);
    color++;
  }
  // Reset back to WHITE
  Heltec.display->setColor(WHITE);
}
/**************************************************************
  Function Name   : drawCircle
  Description     :
  Input           :
  Return          :
  // Adapted from Adafruit_SSD1306
**************************************************************/
void drawCircle(void) {
  for (int16_t i = 0; i < DISPLAY_HEIGHT; i += 2)
  {
    Heltec.display->drawCircle(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2, i);
    Heltec.display->display();
    delay(20);
  }
  delay(1000);
  Heltec.display->clear();
  // This will draw the part of the circel in quadrant 1
  // Quadrants are numberd like this:
  //   0010 | 0001
  //  ------|-----
  //   0100 | 1000
  //
  Heltec.display->drawCircleQuads(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2, DISPLAY_HEIGHT / 4, 0b00000001);
  Heltec.display->display();
  delay(200);
  Heltec.display->drawCircleQuads(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2, DISPLAY_HEIGHT / 4, 0b00000011);
  Heltec.display->display();
  delay(200);
  Heltec.display->drawCircleQuads(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2, DISPLAY_HEIGHT / 4, 0b00000111);
  Heltec.display->display();
  delay(200);
  Heltec.display->drawCircleQuads(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2, DISPLAY_HEIGHT / 4, 0b00001111);
  Heltec.display->display();
}
/**************************************************************
  Function Name   : printBuffer
  Description     :
  Input           :
  Return          :
**************************************************************/
void printBuffer(void) {
  // Initialize the log buffer
  // allocate memory to store 8 lines of text and 30 chars per line.
  Heltec.display->setLogBuffer(5, 30);
  // Some test data
  const char* test[] = {
    "Hello",
    "World" ,
    "----",
    "Show off",
    "how",
    "the log buffer",
    "is",
    "working.",
    "Even",
    "scrolling is",
    "working"
  };
  for (uint8_t i = 0; i < 11; i++)
  {
    Heltec.display->clear();
    // Print to the screen
    Heltec.display->println(test[i]);
    // Draw it to the internal screen buffer
    Heltec.display->drawLogBuffer(0, 0);
    // Display it on the screen
    Heltec.display->display();
    delay(500);
  }
}
/**************************************************************
  Function Name   : I2C_Address_Scan
  Description     :
  Input           :
  Return          :
**************************************************************/
void I2C_Address_Scan(void)
{
  uint8_t count, i;
  Serial.println();
  Serial.println();
  Serial.println("-------------------------------- -");
  count = 0;
  Serial.println ("i2c scanner. scanning ...");
  for (i = 1; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
    {
      Serial.print ("found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay(10);
    }
  }
  Serial.println ("done.");
  Serial.print ("found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
  Serial.println("-------------------------------- -");
  Serial.println ("");
  delay(100);
}
//***************************************************************************************
//***************************************************************************************
//***************************************************************************************
//***********************************************************************
//***********************************************************************
void Relay_Actuator()
{
  if(R1 == "1")
  {
    Relay_ON_OFF(RELAY1, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY1, OFF);
  }
  if(R2 == "1")
  {
    Relay_ON_OFF(RELAY2, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY2, OFF);
  }
  if(R3 == "1")
  {
    Relay_ON_OFF(RELAY3, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY3, OFF);
  }
  if(R4 == "1")
  {
    Relay_ON_OFF(RELAY4, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY4, OFF);
  }
  if(R5 == "1")
  {
    Relay_ON_OFF(RELAY5, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY5, OFF);
  }
  if(R6 == "1")
  {
    Relay_ON_OFF(RELAY6, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY6, OFF);
  }
  if(R7 == "1")
  {
    Relay_ON_OFF(RELAY7, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY7, OFF);
  }
  if(R8 == "1")
  {
    Relay_ON_OFF(RELAY8, ON);
  }
  else
  {
    Relay_ON_OFF(RELAY8, OFF);
  }
}
void Energy() {
    // Print the custom address of the PZEM
    Serial.print("Custom Address:");
    Serial.println(pzem.readAddress(), HEX);
    // Read the data from the sensor
    float voltage = pzem.voltage();
    float current = pzem.current();
    float power = pzem.power();
    float energy = pzem.energy();
    float frequency = pzem.frequency();
    float pf = pzem.pf();
    // Check if the data is valid
    if(isnan(voltage)){
        Serial.println("Error reading voltage");
    } else if (isnan(current)) {
        Serial.println("Error reading current");
    } else if (isnan(power)) {
        Serial.println("Error reading power");
    } else if (isnan(energy)) {
        Serial.println("Error reading energy");
    } else if (isnan(frequency)) {
        Serial.println("Error reading frequency");
    } else if (isnan(pf)) {
        Serial.println("Error reading power factor");
    } else {
        // Print the values to the Serial console
        Serial.print("Voltage: ");      Serial.print(voltage);      Serial.println("V");
        Serial.print("Current: ");      Serial.print(current);      Serial.println("A");
        Serial.print("Power: ");        Serial.print(power);        Serial.println("W");
        Serial.print("Energy: ");       Serial.print(energy,3);     Serial.println("kWh");
        Serial.print("Frequency: ");    Serial.print(frequency, 1); Serial.println("Hz");
        Serial.print("PF: ");           Serial.println(pf);
    }
    data4 = String(voltage);
    data5 = String(current);
    data6 = String(power);
    data7 = String(energy);
    data8 = String(frequency);
    data9 = String(pf);
    Serial.println();
}
void Soil_SensorTH_loop() {
   digitalWrite(MAX485_RE_NEG, RS485Transmit);     // init Transmit
  // byte RS485_request[8] = {0x01, 0x04, 0x00, 0x01, 0x00, 0x02, 0x20, 0x0B}; // XYMD02
  byte RS485_request[8] = {0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x65, 0xCB}; // standard soil temp& humid
  // byte RS485_request[8] = {0x01, 0x03, 0x00, 0x02, 0x00, 0x01, 0x25, 0xCA}; // standard soil humid( 3 ,4 )
  // Test
  // byte RS485_request[8] = {0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x25, 0xCA}; // failed
  // byte RS485_request[8] = {0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x65, 0xCB}; // failed
  // byte RS485_request[8] = {0x01, 0x03, 0x00, 0x03, 0x00, 0x02, 0x65, 0xCB}; // failed
  // byte RS485_request[8] = {0x01, 0x03, 0x00, 0x03, 0x00, 0x02, 0x25, 0xCA}; // failed
  //
   //to find out what I am sending
   for( byte a=0; a<8; a++ ) {
     Serial.print(RS485_request[a], HEX);
   }
   Serial.println();
   RS485Serial.write(RS485_request, sizeof(RS485_request));
   RS485Serial.flush();
   digitalWrite(MAX485_RE_NEG, RS485Receive);      // Init Receive
   byte RS485_received[13];// 13
   RS485Serial.readBytes(RS485_received, 13); // 13
   Serial.print("Result : "); // i max = 9 for XY-MD02 , change for other sensor
   //9
   for( byte i=0; i<13; i++ ) {
   float value_check = RS485_received[i];
   if (value_check < 16)
    {
     RS485_Data[i]="0"+String(RS485_received[i],HEX);
    }
   else
    {
     RS485_Data[i]=String(RS485_received[i],HEX);
    }
    Serial.print(RS485_Data[i]);
    Serial.print(" ");
   }
  RS485Serial.flush();
  Serial.println();
  String ttemp = String(RS485_Data[6])+String(RS485_Data[7]);  // 5, 6 หยิบค่า HEX มาต่อกันก่อนแปลง
  String hhumid = String(RS485_Data[3])+String(RS485_Data[4]); // humid เหมือน temp
  //Conversion from hexadecimal to decimal
  //data3 = String((float)convert.hexaToDecimal(ttemp)/10,2);  // แสดงค่า ทศนิยม 2 ตำแหน่ง
  data3 = String((float)convert.hexaToDecimal(hhumid)/10,2);  // แสดงค่า ทศนิยม 2 ตำแหน่ง
  //data3 = data4 ;
  //Serial.print(ttemp);Serial.print(" Temp : converted to decimal: ");
  //Serial.println(data3);
  Serial.print(hhumid);Serial.print(" Humid : converted to decimal: ");
  Serial.println(data3);
  Serial.println();
  // test_convert();  // ok work well
  delay(5000);
}
void test_convert() {
  int dec = 50;
  String hexa = "131";
  //Conversion from binary to decimal
  Serial.print("A converted to decimal: ");
  Serial.println(convert.hexaToDecimal(hexa));
  int deci = convert.hexaToDecimal(hexa);
  String bin = String(convert.decimalToBinary(deci));
  Serial.print("A = ");Serial.println(bin);
}
/*
void test_convert() {
  Serial.begin(9600);
  int dec = 50;
  //Conversion de decimal a binario
  Serial.print("50 converted to binary: ");
  Serial.println(convert.decimalToBinary(dec));
  //Conversion de decimal a hexadecimal
  Serial.print("50 converted to hexadecimal: ");
  Serial.println(convert.decimalToHexa(dec));
  //Conversion de decimal a octal
  Serial.print("50 converted to octal: ");
  Serial.println(convert.decimalToOctal(dec));
  String hex = "292";
  //Conversion from hexadecimal to decimal
  Serial.print("292 converted to decimal: ");
  Serial.println(convert.hexaToDecimal(hex));
  String bin = "11001111";
  //Conversion from binary to decimal
  Serial.print("11001111 converted to decimal: ");
  Serial.println(convert.binaryToDecimal(bin));
  String oct = "515";
  //Conversion from octal to decimal
  Serial.print("515 converted to decimal: ");
  Serial.println(convert.octalToDecimal(oct));
}
*/
void XYMD02_loop()
{
  Read_DATA_SHT20_XYMD02(1);//Modbus ID
  //Read_Hardware_Parameter_SHT20_XYMD02(1);//Modbus ID
  data3=dataa3;
  data4=dataa4;
  delay(1000);
  //Read_DATA_SHT20_XYMD02(2);//Modbus ID
  //Read_Hardware_Parameter_SHT20_XYMD02(2);//Modbus ID
}
/**************************************************************
  Function Name   : Change_Slave_ID
  Description     :
  Input           :
  Return          :
**************************************************************/
void Change_Slave_ID(uint8_t Slave_ID, uint8_t NEW_Slave_ID)
{
  uint8_t loop_poll;
  au16data[5] = NEW_Slave_ID;
  telegram[1].u8id         = Slave_ID; // slave addrss
  telegram[1].u8fct        = 6; // function code
  telegram[1].u16RegAdd    = 257; // start address in slave
  telegram[1].u16CoilsNo   = 1; // number of elements (coils or registers) to read
  telegram[1].au16reg      = au16data + 5; // pointer to a memory array in the Arduino
  master.query( telegram[1] ); // send query (only once)
  delay(5);
  loop_poll = 1;
  while (loop_poll)
  {
    master.poll(); // check incoming messages
    delay(2);
    if (master.getState() == COM_IDLE)
    {
      loop_poll = 0;
      if (master.getLastError() != 0)//ERROR
      {
        Serial.print(" ERROR Read >>  ");
        Serial.println(master.getLastError());
      }
      else
      {
        Serial.println("............ Write NEW Slave ID  OK ");
        Serial.println("............ Please Restart Sensor ");
      }
    }
  }
  delay(20);
}
/**************************************************************
  Function Name   : Read_DATA_SHT20_XYMD02
  Description     :
  Input           :
  Return          :
**************************************************************/
void Read_DATA_SHT20_XYMD02(uint8_t Slave_ID)//
{
  uint8_t loop_poll;
  telegram[2].u8id         = Slave_ID;   // slave address Read
  telegram[2].u8fct        = 4;          // function code (this one is registers read)
  telegram[2].u16RegAdd    = 0;          //257;//1; // start address in slave // 0 for SHT20
  telegram[2].u16CoilsNo   = 2;          // number of elements (coils or registers) to read
  telegram[2].au16reg      = au16data;   // pointer to a memory array in the Arduino
  master.query( telegram[2] );           // send query (only once)
  loop_poll = 1;
  delay(5);
  while (loop_poll)
  {
    master.poll(); // check incoming messages
    delay(2);
    if (master.getState() == COM_IDLE)
    {
      loop_poll = 0;
      if (master.getLastError() != 0)//ERROR
      {
        Serial.print(" ERROR Read >>  ");
        Serial.println(master.getLastError());
      }
      else
      {
        // Serial.println("************  READ  *************");//debug
        // Serial.println(au16data[0], DEC);//debug
        // Serial.println(au16data[1], DEC);//debug
        temperature   = float(au16data[0]) / 10;
        humidity      = float(au16data[1]) / 10;
        jdata3 = temperature;
        jdata4 = humidity;
        dataa3 = String(temperature);
        dataa4 = String(humidity);
        data5 = String(temperature);
        data6 = String(humidity);
        Serial.println("---------------------------------------------------");
        Serial.print(" Temperature      =  ");
        Serial.print(temperature);
        Serial.println("  *C");
        Serial.print(" Humidity         =  ");
        Serial.print(humidity);
        Serial.println("  %");
        //Serial.println("---------------------------------------------------");
      }
    }
  }
  delay(20);
}
/**************************************************************
  Function Name   : Read_Hardware_Parameter_SHT20_XYMD02
  Description     :
  Input           :
  Return          :
**************************************************************/
void Read_Hardware_Parameter_SHT20_XYMD02(uint8_t Slave_ID)
{
  uint8_t loop_poll;
  telegram[3].u8id         = Slave_ID;   // slave address Read
  telegram[3].u8fct        = 3;          // function code (this one is registers read)
  telegram[3].u16RegAdd    = 257;        // start address in slave
  telegram[3].u16CoilsNo   = 2;          // number of elements (coils or registers) to read
  telegram[3].au16reg      = au16data;   // pointer to a memory array in the Arduino
  master.query( telegram[3] );           // send query (only once)
  loop_poll = 1;
  delay(5);
  while (loop_poll)
  {
    master.poll(); // check incoming messages
    delay(2);
    if (master.getState() == COM_IDLE)
    {
      loop_poll = 0;
      if (master.getLastError() != 0)//ERROR
      {
        Serial.print(" ERROR Read >>  ");
        Serial.println(master.getLastError());
      }
      else
      {
        //Serial.println("************  READ  *************");//debug
        //Serial.println(au16data[0], DEC);//debug
        //Serial.println(au16data[1], DEC);//debug
        //Serial.println("---------------------------------------------------");
        Serial.print(" Modbus Slave ID  =  ");
        Serial.println(au16data[0], DEC);
        Serial.print(" Baud Rate        =  ");
        Serial.println(au16data[1], DEC);
        Serial.println("---------------------------------------------------");
      }
    }
  }
  delay(20);
}
//****************************************************************************
//****************************************************************************
//****************************************************************************
//****************************************************************************